diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 993c091..dd65057 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,13 +1,14 @@ add_definitions(-DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") ecm_add_test(pkpassmanagertest.cpp LINK_LIBRARIES Qt5::Test itinerary) ecm_add_test(reservationmanagertest.cpp LINK_LIBRARIES Qt5::Test itinerary) ecm_add_test(applicationcontrollertest.cpp LINK_LIBRARIES Qt5::Test itinerary) ecm_add_test(tripgrouptest.cpp LINK_LIBRARIES Qt5::Test itinerary) ecm_add_test(timelinemodeltest.cpp modelverificationpoint.cpp TEST_NAME timelinemodeltest LINK_LIBRARIES Qt5::Test itinerary) ecm_add_test(tripgroupproxytest.cpp modelverificationpoint.cpp TEST_NAME tripgroupproxytest LINK_LIBRARIES Qt5::Test itinerary) ecm_add_test(weathertest.cpp LINK_LIBRARIES Qt5::Test itinerary-weather) target_include_directories(weathertest PRIVATE ${CMAKE_BINARY_DIR}) ecm_add_test(navitiaparsertest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport) +ecm_add_test(publictransportmanagertest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport) diff --git a/autotests/publictransportmanagertest.cpp b/autotests/publictransportmanagertest.cpp new file mode 100644 index 0000000..b038601 --- /dev/null +++ b/autotests/publictransportmanagertest.cpp @@ -0,0 +1,61 @@ +/* + 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 +#include +#include +#include +#include + +#include +#include + +using namespace KPublicTransport; + +class PublicTransportManagerTest : public QObject +{ + Q_OBJECT +private slots: + void initTestCase() + { + qputenv("TZ", "UTC"); + } + + void testQueryJourney() + { + Manager mgr; + auto reply = mgr.queryJourney({}); + QSignalSpy spy(reply, &Reply::finished); + QVERIFY(spy.wait(100)); + QCOMPARE(spy.size(), 1); + delete reply; + } + + void testQueryDepartures() + { + Manager mgr; + auto reply = mgr.queryDeparture({}); + QSignalSpy spy(reply, &Reply::finished); + QVERIFY(spy.wait(100)); + QCOMPARE(spy.size(), 1); + delete reply; + } +}; + +QTEST_GUILESS_MAIN(PublicTransportManagerTest) + +#include "publictransportmanagertest.moc" diff --git a/src/publictransport/backends/navitiabackend.cpp b/src/publictransport/backends/navitiabackend.cpp index 8660f81..125bb55 100644 --- a/src/publictransport/backends/navitiabackend.cpp +++ b/src/publictransport/backends/navitiabackend.cpp @@ -1,128 +1,136 @@ /* 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 "navitiabackend.h" #include "logging.h" #include "navitiaparser.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPublicTransport; NavitiaBackend::NavitiaBackend() = default; bool NavitiaBackend::queryJourney(JourneyReply *reply, QNetworkAccessManager *nam) const { const auto req = reply->request(); + if (!req.from().hasCoordinate() || !req.to().hasCoordinate()) { + return false; + } + QUrl url; url.setScheme(QStringLiteral("https")); url.setHost(m_endpoint); url.setPath(QStringLiteral("/v1") + (m_coverage.isEmpty() ? QString() : (QStringLiteral("/coverage/") + m_coverage)) + QStringLiteral("/journeys")); QUrlQuery query; query.addQueryItem(QStringLiteral("from"), QString::number(req.from().latitude()) + QLatin1Char(';') + QString::number(req.from().longitude())); query.addQueryItem(QStringLiteral("to"), QString::number(req.to().latitude()) + QLatin1Char(';') + QString::number(req.to().longitude())); if (req.dateTime().isValid()) { query.addQueryItem(QStringLiteral("datetime"), req.dateTime().toString(QStringLiteral("yyyyMMddThhmmss"))); query.addQueryItem(QStringLiteral("datetime_represents"), req.dateTimeMode() == JourneyRequest::Arrival ? QStringLiteral("arrival") : QStringLiteral("departure")); } // TODO: disable reply parts we don't care about query.addQueryItem(QStringLiteral("disable_geojson"), QStringLiteral("true")); // ### seems to have no effect? query.addQueryItem(QStringLiteral("depth"), QStringLiteral("0")); // ### also has no effect? url.setQuery(query); QNetworkRequest netReq(url); netReq.setRawHeader("Authorization", m_auth.toUtf8()); qCDebug(Log) << "GET:" << url; auto netReply = nam->get(netReq); QObject::connect(netReply, &QNetworkReply::finished, [reply, netReply] { switch (netReply->error()) { case QNetworkReply::NoError: addResult(reply, NavitiaParser::parseJourneys(netReply->readAll())); break; case QNetworkReply::ContentNotFoundError: addError(reply, Reply::NotFoundError, NavitiaParser::parseErrorMessage(netReply->readAll())); break; default: addError(reply, Reply::NetworkError, netReply->errorString()); qCDebug(Log) << netReply->error() << netReply->errorString(); break; } netReply->deleteLater(); }); return true; } bool NavitiaBackend::queryDeparture(DepartureReply *reply, QNetworkAccessManager *nam) const { const auto req = reply->request(); + if (!req.stop().hasCoordinate()) { + return false; + } + QUrl url; url.setScheme(QStringLiteral("https")); url.setHost(m_endpoint); url.setPath( QStringLiteral("/v1/coverage/") + (m_coverage.isEmpty() ? QString::number(req.stop().latitude()) + QLatin1Char(';') + QString::number(req.stop().longitude()) : m_coverage) + QStringLiteral("/coord/") + QString::number(req.stop().latitude()) + QLatin1Char(';') + QString::number(req.stop().longitude()) + QStringLiteral("/departures") ); QUrlQuery query; query.addQueryItem(QStringLiteral("from_datetime"), req.dateTime().toString(QStringLiteral("yyyyMMddThhmmss"))); query.addQueryItem(QStringLiteral("disable_geojson"), QStringLiteral("true")); query.addQueryItem(QStringLiteral("depth"), QStringLiteral("0")); url.setQuery(query); QNetworkRequest netReq(url); netReq.setRawHeader("Authorization", m_auth.toUtf8()); qCDebug(Log) << "GET:" << url; auto netReply = nam->get(netReq); QObject::connect(netReply, &QNetworkReply::finished, [netReply, reply] { switch (netReply->error()) { case QNetworkReply::NoError: addResult(reply, NavitiaParser::parseDepartures(netReply->readAll())); break; case QNetworkReply::ContentNotFoundError: addError(reply, Reply::NotFoundError, NavitiaParser::parseErrorMessage(netReply->readAll())); break; default: addError(reply, Reply::NetworkError, netReply->errorString()); qCDebug(Log) << netReply->error() << netReply->errorString(); break; } netReply->deleteLater(); }); return true; } diff --git a/src/publictransport/datatypes/location.cpp b/src/publictransport/datatypes/location.cpp index 287db58..1f2c690 100644 --- a/src/publictransport/datatypes/location.cpp +++ b/src/publictransport/datatypes/location.cpp @@ -1,95 +1,100 @@ /* 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 "location.h" #include "datatypes_p.h" #include #include #include using namespace KPublicTransport; namespace KPublicTransport { class LocationPrivate : public QSharedData { public: QString name; float latitude = NAN; float longitude = NAN; QTimeZone timeZone; QHash ids; }; } KPUBLICTRANSPORT_MAKE_GADGET(Location) QString Location::name() const { return d->name; } void Location::setName(const QString &name) { d.detach(); d->name = name; } float Location::latitude() const { return d->latitude; } float Location::longitude() const { return d->longitude; } void Location::setCoordinate(float latitude, float longitude) { d.detach(); d->latitude = latitude; d->longitude = longitude; } +bool Location::hasCoordinate() const +{ + return !std::isnan(d->latitude) && !std::isnan(d->longitude); +} + QTimeZone Location::timeZone() const { return d->timeZone; } void Location::setTimeZone(const QTimeZone &tz) { d.detach(); d->timeZone = tz; } QString Location::identifier(const QString &identifierType) const { return d->ids.value(identifierType); } void Location::setIdentifier(const QString &identifierType, const QString &id) { d.detach(); d->ids.insert(identifierType, id); } #include "moc_location.cpp" diff --git a/src/publictransport/datatypes/location.h b/src/publictransport/datatypes/location.h index 5a55965..74c6a18 100644 --- a/src/publictransport/datatypes/location.h +++ b/src/publictransport/datatypes/location.h @@ -1,59 +1,60 @@ /* 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_LOCATION_H #define KPUBLICTRANSPORT_LOCATION_H #include "datatypes.h" class QTimeZone; namespace KPublicTransport { class LocationPrivate; /** A location. */ class Location { KPUBLICTRANSPORT_GADGET(Location) /** Human-readable name of the location. */ Q_PROPERTY(QString name READ name WRITE setName) // TODO: type, coords, id, address public: QString name() const; void setName(const QString &name); float latitude() const; float longitude() const; void setCoordinate(float latitude, float longitude); + bool hasCoordinate() const; /** The timezone this location is in, if known. */ QTimeZone timeZone() const; void setTimeZone(const QTimeZone &tz); /** Location identifiers. */ QString identifier(const QString &identifierType) const; void setIdentifier(const QString &identifierType, const QString &id); }; } Q_DECLARE_METATYPE(KPublicTransport::Location) #endif // KPUBLICTRANSPORT_LOCATION_H diff --git a/src/publictransport/manager.cpp b/src/publictransport/manager.cpp index b26c62d..d32b794 100644 --- a/src/publictransport/manager.cpp +++ b/src/publictransport/manager.cpp @@ -1,145 +1,154 @@ /* 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 "journeyreply.h" #include "logging.h" #include "backends/navitiabackend.h" #include #include #include #include #include using namespace KPublicTransport; static inline void initResources() { Q_INIT_RESOURCE(networks); } namespace KPublicTransport { class ManagerPrivate { public: QNetworkAccessManager* nam(); 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; }; } QNetworkAccessManager* ManagerPrivate::nam() { if (!m_nam) { m_nam = new QNetworkAccessManager; } 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; } - const auto doc = QJsonDocument::fromJson(f.readAll()); + 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) { 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); } + qCWarning(Log) << "Unknown backend type:" << type; return {}; } static void applyBackendOptions(void *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()); const auto mp = mo->property(idx); mp.writeOnGadget(backend, it.value().toVariant()); } } 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() : d(new ManagerPrivate) { initResources(); d->loadNetworks(); } Manager::Manager(Manager&&) noexcept = default; Manager::~Manager() = default; void Manager::setNetworkAccessManager(QNetworkAccessManager *nam) { // TODO delete d->nam if we created it ourselves d->m_nam = nam; } JourneyReply* Manager::queryJourney(const JourneyRequest &req) const { auto reply = new JourneyReply(req); int pendingOps = 0; for (const auto &backend : d->m_backends) { if (backend->queryJourney(reply, d->nam())) { ++pendingOps; } } reply->setPendingOps(pendingOps); return reply; } DepartureReply* Manager::queryDeparture(const DepartureRequest &req) const { auto reply = new DepartureReply(req); int pendingOps = 0; for (const auto &backend : d->m_backends) { if (backend->queryDeparture(reply, d->nam())) { ++pendingOps; } } reply->setPendingOps(pendingOps); return reply; }