diff --git a/src/backends/abstractbackend.h b/src/backends/abstractbackend.h index 4b2eecd..3011fdf 100644 --- a/src/backends/abstractbackend.h +++ b/src/backends/abstractbackend.h @@ -1,92 +1,92 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KPUBLICTRANSPORT_ABSTRACTBACKEND_H #define KPUBLICTRANSPORT_ABSTRACTBACKEND_H #include "reply.h" #include class QNetworkAccessManager; namespace KPublicTransport { class DepartureReply; class JourneyReply; class Location; class LocationReply; /** Abstract base class for transport provider backends. */ class AbstractBackend { Q_GADGET public: AbstractBackend(); virtual ~AbstractBackend(); - /** Identifer for this backend. + /** Identifier for this backend. * Use e.g. for distinguishing backend-specific cache locations etc. */ QString backendId() const; void setBackendId(const QString &id); /** Checks if this location has been filtered by the network configuration. */ bool isLocationExcluded(const Location &loc) const; /** Same as the above but just checking one specific geo coordinate. */ bool isCoordinateExcluded(float lat, float lon) const; void setGeoFilter(const QPolygonF &poly); /** Returns @c true if this backend uses transport encryption. */ virtual bool isSecure() const; /** Perform a journey query. * @return @c true if performing an async operation, @c false otherwise. */ virtual bool queryJourney(JourneyReply *reply, QNetworkAccessManager *nam) const; /** Perform a departure query. * @return @c true if performing an async operation, @c false otherwise. */ virtual bool queryDeparture(DepartureReply *reply, QNetworkAccessManager *nam) const; /** Perform a location query. * @return @c true if performing an async operation, @c false otherwise. */ virtual bool queryLocation(LocationReply *reply, QNetworkAccessManager *nam) const; protected: /** Helper function to call non-public Reply API. */ template inline static void addResult(T *reply, Args&&... args) { reply->addResult(std::forward(args)...); } inline static void addError(Reply *reply, Reply::Error error, const QString &errorMsg) { reply->addError(error, errorMsg); } private: Q_DISABLE_COPY(AbstractBackend) QString m_backendId; QPolygonF m_geoFilter; }; } #endif // KPUBLICTRANSPORT_ABSTRACTBACKEND_H diff --git a/src/backends/navitiaparser.cpp b/src/backends/navitiaparser.cpp index 293bc1c..b18e4ac 100644 --- a/src/backends/navitiaparser.cpp +++ b/src/backends/navitiaparser.cpp @@ -1,275 +1,275 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "navitiaparser.h" #include #include #include #include #include #include #include #include #include using namespace KPublicTransport; static QDateTime parseDateTime(const QJsonValue &v, const QTimeZone &tz) { auto dt = QDateTime::fromString(v.toString(), QStringLiteral("yyyyMMddTHHmmss")); if (tz.isValid()) { dt.setTimeZone(tz); } return dt; } struct { const char *name; Line::Mode mode; -} static const navitia_phyiscal_modes[] = { +} static const navitia_physical_modes[] = { { "Air", Line::Air }, { "Boat", Line::Boat }, { "Bus", Line::Bus }, { "BusRapidTransit", Line::BusRapidTransit }, { "Coach", Line::Coach }, { "Ferry", Line::Ferry }, { "Funicular", Line::Funicular }, { "LocalTrain", Line::LocalTrain }, { "LongDistanceTrain", Line::LongDistanceTrain }, { "Metro", Line::Metro }, { "RailShuttle", Line::RailShuttle }, { "RapidTransit", Line::RapidTransit }, { "Shuttle", Line::Shuttle }, { "Taxi", Line::Taxi }, { "Train", Line::Train }, { "Tramway", Line::Tramway } }; static Line::Mode parsePhysicalMode(const QString &mode) { const auto modeStr = mode.toLatin1(); if (!modeStr.startsWith("physical_mode:")) { return Line::Unknown; } - for (auto it = std::begin(navitia_phyiscal_modes); it != std::end(navitia_phyiscal_modes); ++it) { + for (auto it = std::begin(navitia_physical_modes); it != std::end(navitia_physical_modes); ++it) { if (strcmp(modeStr.constData() + 14, it->name) == 0) { return it->mode; } } return Line::Unknown; } static Location parseLocation(const QJsonObject &obj) { Location loc; loc.setName(obj.value(QLatin1String("label")).toString()); // TODO parse more fields const auto coord = obj.value(QLatin1String("coord")).toObject(); loc.setCoordinate(coord.value(QLatin1String("lat")).toString().toDouble(), coord.value(QLatin1String("lon")).toString().toDouble()); auto tz = obj.value(QLatin1String("timezone")).toString(); if (tz.isEmpty()) { tz = obj.value(QLatin1String("stop_area")).toObject().value(QLatin1String("timezone")).toString(); } if (!tz.isEmpty()) { loc.setTimeZone(QTimeZone(tz.toUtf8())); } return loc; } static Location parseWrappedLocation(const QJsonObject &obj) { auto loc = parseLocation(obj.value(obj.value(QLatin1String("embedded_type")).toString()).toObject()); loc.setName(obj.value(QLatin1String("name")).toString()); return loc; } static JourneySection parseJourneySection(const QJsonObject &obj) { const auto displayInfo = obj.value(QLatin1String("display_informations")).toObject(); Line line; line.setName(displayInfo.value(QLatin1String("label")).toString()); line.setColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("color")).toString())); line.setTextColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("text_color")).toString())); line.setModeString(displayInfo.value(QLatin1String("commercial_mode")).toString()); const auto links = obj.value(QLatin1String("links")).toArray(); for (const auto &v : links) { const auto link = v.toObject(); if (link.value(QLatin1String("type")).toString() != QLatin1String("physical_mode")) { continue; } line.setMode(parsePhysicalMode(link.value(QLatin1String("id")).toString())); break; } Route route; route.setDirection(displayInfo.value(QLatin1String("direction")).toString()); route.setLine(line); JourneySection section; section.setFrom(parseWrappedLocation(obj.value(QLatin1String("from")).toObject())); section.setTo(parseWrappedLocation(obj.value(QLatin1String("to")).toObject())); section.setRoute(route); section.setScheduledDepartureTime(parseDateTime(obj.value(QLatin1String("base_departure_date_time")), section.from().timeZone())); section.setScheduledArrivalTime(parseDateTime(obj.value(QLatin1String("base_arrival_date_time")), section.to().timeZone())); if (obj.value(QLatin1String("data_freshness")).toString() != QLatin1String("base_schedule")) { section.setScheduledArrivalTime(parseDateTime(obj.value(QLatin1String("arrival_date_time")), section.to().timeZone())); section.setScheduledDepartureTime(parseDateTime(obj.value(QLatin1String("departure_date_time")), section.from().timeZone())); } const auto typeStr = obj.value(QLatin1String("type")).toString(); if (typeStr == QLatin1String("public_transport")) { section.setMode(JourneySection::PublicTransport); } else if (typeStr == QLatin1String("transfer")) { section.setMode(JourneySection::Transfer); } else if (typeStr == QLatin1String("street_network") || typeStr == QLatin1String("walking") || typeStr == QLatin1String("crow_fly")) { section.setMode(JourneySection::Walking); } else if (typeStr == QLatin1String("waiting")) { section.setMode(JourneySection::Waiting); } return section; } static Journey parseJourney(const QJsonObject &obj) { Journey journey; const auto secArray = obj.value(QLatin1String("sections")).toArray(); std::vector sections; sections.reserve(secArray.size()); for (const auto &v : secArray) { sections.push_back(parseJourneySection(v.toObject())); } journey.setSections(std::move(sections)); return journey; } std::vector NavitiaParser::parseJourneys(const QByteArray &data) { const auto topObj = QJsonDocument::fromJson(data).object(); const auto journeys = topObj.value(QLatin1String("journeys")).toArray(); std::vector res; res.reserve(journeys.size()); for (const auto &v : journeys) { res.push_back(parseJourney(v.toObject())); } return res; } static Departure parseDeparture(const QJsonObject &obj) { // TODO remove code duplication with journey parsing const auto displayInfo = obj.value(QLatin1String("display_informations")).toObject(); Line line; line.setName(displayInfo.value(QLatin1String("label")).toString()); line.setColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("color")).toString())); line.setTextColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("text_color")).toString())); line.setModeString(displayInfo.value(QLatin1String("commercial_mode")).toString()); const auto links = obj.value(QLatin1String("links")).toArray(); for (const auto &v : links) { const auto link = v.toObject(); if (link.value(QLatin1String("type")).toString() != QLatin1String("physical_mode")) { continue; } line.setMode(parsePhysicalMode(link.value(QLatin1String("id")).toString())); break; } Route route; route.setDirection(displayInfo.value(QLatin1String("direction")).toString()); route.setLine(line); Departure departure; departure.setRoute(route); const auto dtObj = obj.value(QLatin1String("stop_date_time")).toObject(); departure.setStopPoint(parseLocation(obj.value(QLatin1String("stop_point")).toObject())); departure.setScheduledDepartureTime(parseDateTime(dtObj.value(QLatin1String("base_departure_date_time")), departure.stopPoint().timeZone())); departure.setScheduledArrivalTime(parseDateTime(dtObj.value(QLatin1String("base_arrival_date_time")), departure.stopPoint().timeZone())); if (dtObj.value(QLatin1String("data_freshness")).toString() != QLatin1String("base_schedule")) { departure.setExpectedDepartureTime(parseDateTime(dtObj.value(QLatin1String("departure_date_time")), departure.stopPoint().timeZone())); departure.setExpectedArrivalTime(parseDateTime(dtObj.value(QLatin1String("arrival_date_time")), departure.stopPoint().timeZone())); } return departure; } std::vector NavitiaParser::parseDepartures(const QByteArray &data) { const auto topObj = QJsonDocument::fromJson(data).object(); const auto departures = topObj.value(QLatin1String("departures")).toArray(); std::vector res; res.reserve(departures.size()); for (const auto &v : departures) { res.push_back(parseDeparture(v.toObject())); } return res; } std::vector NavitiaParser::parsePlacesNearby(const QByteArray &data) { const auto topObj = QJsonDocument::fromJson(data).object(); const auto placesNearby = topObj.value(QLatin1String("places_nearby")).toArray(); std::vector res; res.reserve(placesNearby.size()); for (const auto &v : placesNearby) { res.push_back(parseWrappedLocation(v.toObject())); } return res; } std::vector NavitiaParser::parsePlaces(const QByteArray &data) { const auto topObj = QJsonDocument::fromJson(data).object(); const auto placesNearby = topObj.value(QLatin1String("places")).toArray(); std::vector res; res.reserve(placesNearby.size()); for (const auto &v : placesNearby) { res.push_back(parseWrappedLocation(v.toObject())); } return res; } QString NavitiaParser::parseErrorMessage(const QByteArray &data) { const auto topObj = QJsonDocument::fromJson(data).object(); const auto errorObj = topObj.value(QLatin1String("error")).toObject(); // id field contains error enum, might also be useful return errorObj.value(QLatin1String("message")).toString(); } diff --git a/src/manager.cpp b/src/manager.cpp index 21ae1ee..4f6b3ee 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1,245 +1,245 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "manager.h" #include "departurereply.h" #include "departurerequest.h" #include "journeyreply.h" #include "journeyrequest.h" #include "locationreply.h" #include "locationrequest.h" #include "logging.h" #include #include "backends/cache.h" #include "backends/hafasmgatebackend.h" #include "backends/hafasquerybackend.h" #include "backends/navitiabackend.h" #include #include #include #include #include #include using namespace KPublicTransport; static inline void initResources() { Q_INIT_RESOURCE(networks); } namespace KPublicTransport { class ManagerPrivate { public: QNetworkAccessManager* nam(QObject *parent); void loadNetworks(); std::unique_ptr loadNetwork(const QJsonObject &obj); template std::unique_ptr loadNetwork(const QJsonObject &obj); QNetworkAccessManager *m_nam = nullptr; std::vector> m_backends; bool m_allowInsecure = false; }; } QNetworkAccessManager* ManagerPrivate::nam(QObject *parent) { if (!m_nam) { m_nam = new QNetworkAccessManager(parent); } return m_nam; } void ManagerPrivate::loadNetworks() { QDirIterator it(QStringLiteral(":/org.kde.pim/kpublictransport/networks")); while (it.hasNext()) { QFile f(it.next()); if (!f.open(QFile::ReadOnly)) { qCWarning(Log) << "Failed to open public transport network configuration:" << f.errorString(); continue; } QJsonParseError error; const auto doc = QJsonDocument::fromJson(f.readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(Log) << "Failed to parse public transport network configuration:" << error.errorString() << it.fileName(); continue; } auto net = loadNetwork(doc.object()); if (net) { net->setBackendId(it.fileInfo().baseName()); m_backends.push_back(std::move(net)); } else { qCWarning(Log) << "Failed to load public transport network configuration config:" << it.fileName(); } } qCDebug(Log) << m_backends.size() << "public transport network configurations loaded"; } std::unique_ptr ManagerPrivate::loadNetwork(const QJsonObject &obj) { const auto type = obj.value(QLatin1String("type")).toString(); if (type == QLatin1String("navitia")) { return loadNetwork(obj); } if (type == QLatin1String("hafas_mgate")) { return loadNetwork(obj); } if (type == QLatin1String("hafas_query")) { return loadNetwork(obj); } qCWarning(Log) << "Unknown backend type:" << type; return {}; } static void applyBackendOptions(AbstractBackend *backend, const QMetaObject *mo, const QJsonObject &obj) { const auto opts = obj.value(QLatin1String("options")).toObject(); for (auto it = opts.begin(); it != opts.end(); ++it) { const auto idx = mo->indexOfProperty(it.key().toUtf8().constData()); const auto mp = mo->property(idx); if (it.value().isObject()) { mp.writeOnGadget(backend, it.value().toObject()); } else { mp.writeOnGadget(backend, it.value().toVariant()); } } const auto filter = obj.value(QLatin1String("filter")).toObject(); const auto geoFilter = filter.value(QLatin1String("geo")).toArray(); if (!geoFilter.isEmpty()) { QPolygonF poly; poly.reserve(geoFilter.size()); for (const auto &coordV : geoFilter) { const auto coordA = coordV.toArray(); poly.push_back({coordA[0].toDouble(), coordA[1].toDouble()}); } backend->setGeoFilter(poly); } } template std::unique_ptr ManagerPrivate::loadNetwork(const QJsonObject &obj) { std::unique_ptr backend(new T); applyBackendOptions(backend.get(), &T::staticMetaObject, obj); return backend; } Manager::Manager(QObject *parent) : QObject(parent) , d(new ManagerPrivate) { initResources(); d->loadNetworks(); Cache::expire(); } Manager::~Manager() = default; void Manager::setNetworkAccessManager(QNetworkAccessManager *nam) { d->m_nam = nam; } void Manager::setAllowInsecureBackends(bool insecure) { d->m_allowInsecure = insecure; } JourneyReply* Manager::queryJourney(const JourneyRequest &req) const { auto reply = new JourneyReply(req, const_cast(this)); int pendingOps = 0; for (const auto &backend : d->m_backends) { if (backend->isLocationExcluded(req.from()) && backend->isLocationExcluded(req.to())) { - qCDebug(Log) << "Skiping backend based on location filter:" << backend->backendId(); + qCDebug(Log) << "Skipping backend based on location filter:" << backend->backendId(); continue; } if (!backend->isSecure() && !d->m_allowInsecure) { qCDebug(Log) << "Skipping insecure backend:" << backend->backendId(); continue; } if (backend->queryJourney(reply, d->nam(const_cast(this)))) { ++pendingOps; } } reply->setPendingOps(pendingOps); return reply; } DepartureReply* Manager::queryDeparture(const DepartureRequest &req) const { auto reply = new DepartureReply(req, const_cast(this)); int pendingOps = 0; for (const auto &backend : d->m_backends) { if (backend->isLocationExcluded(req.stop())) { - qCDebug(Log) << "Skiping backend based on location filter:" << backend->backendId(); + qCDebug(Log) << "Skipping backend based on location filter:" << backend->backendId(); continue; } if (!backend->isSecure() && !d->m_allowInsecure) { qCDebug(Log) << "Skipping insecure backend:" << backend->backendId(); continue; } if (backend->queryDeparture(reply, d->nam(const_cast(this)))) { ++pendingOps; } } reply->setPendingOps(pendingOps); return reply; } LocationReply* Manager::queryLocation(const LocationRequest &req) const { auto reply = new LocationReply(req, const_cast(this)); int pendingOps = 0; for (const auto &backend : d->m_backends) { if (req.hasCoordinate() && backend->isCoordinateExcluded(req.latitude(), req.longitude())) { - qCDebug(Log) << "Skiping backend based on location filter:" << backend->backendId(); + qCDebug(Log) << "Skipping backend based on location filter:" << backend->backendId(); continue; } if (!backend->isSecure() && !d->m_allowInsecure) { qCDebug(Log) << "Skipping insecure backend:" << backend->backendId(); continue; } auto cache = Cache::lookupLocation(backend->backendId(), req.cacheKey()); switch (cache.type) { case CacheHitType::Negative: qCDebug(Log) << "Negative cache hit for backend" << backend->backendId(); break; case CacheHitType::Positive: qCDebug(Log) << "Positive cache hit for backend" << backend->backendId(); reply->addResult(std::move(cache.data)); break; case CacheHitType::Miss: qCDebug(Log) << "Cache miss for backend" << backend->backendId(); if (backend->queryLocation(reply, d->nam(const_cast(this)))) { ++pendingOps; } break; } } reply->setPendingOps(pendingOps); return reply; }