diff --git a/src/publictransport/backends/navitiaparser.cpp b/src/publictransport/backends/navitiaparser.cpp index 01b2b6a..ddcacec 100644 --- a/src/publictransport/backends/navitiaparser.cpp +++ b/src/publictransport/backends/navitiaparser.cpp @@ -1,170 +1,179 @@ /* 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 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[] = { { "Air", Line::Air }, { "Boat", Line::Boat }, { "Bus", Line::Bus }, { "BusRapidTransit", Line::RapidTransit }, { "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) { 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("name")).toString()); // TODO parse more fields const auto embObj = obj.value(obj.value(QLatin1String("embedded_type")).toString()).toObject(); const auto coord = embObj.value(QLatin1String("coord")).toObject(); loc.setCoordinate(coord.value(QLatin1String("lat")).toString().toDouble(), coord.value(QLatin1String("lon")).toString().toDouble()); auto tz = embObj.value(QLatin1String("timezone")).toString(); if (tz.isEmpty()) { tz = embObj.value(QLatin1String("stop_area")).toObject().value(QLatin1String("timezone")).toString(); } if (!tz.isEmpty()) { loc.setTimeZone(QTimeZone(tz.toUtf8())); } 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(parseLocation(obj.value(QLatin1String("from")).toObject())); section.setTo(parseLocation(obj.value(QLatin1String("to")).toObject())); section.setRoute(route); section.setDepartureTime(parseDateTime(obj.value(QLatin1String("departure_date_time")), section.from().timeZone())); section.setArrivalTime(parseDateTime(obj.value(QLatin1String("arrival_date_time")), section.to().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")) { 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; } + +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/publictransport/backends/navitiaparser.h b/src/publictransport/backends/navitiaparser.h index 70ae052..332233e 100644 --- a/src/publictransport/backends/navitiaparser.h +++ b/src/publictransport/backends/navitiaparser.h @@ -1,37 +1,40 @@ /* 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_NAVITIAPARSER_H #define KPUBLICTRANSPORT_NAVITIAPARSER_H #include class QByteArray; +class QString; namespace KPublicTransport { class Journey; /** Navitia REST response parser. */ namespace NavitiaParser { std::vector parseJourneys(const QByteArray &data); + + QString parseErrorMessage(const QByteArray &data); } } #endif // KPUBLICTRANSPORT_NAVITIAPARSER_H diff --git a/src/publictransport/journeyreply.cpp b/src/publictransport/journeyreply.cpp index b4dfe04..10b7b2e 100644 --- a/src/publictransport/journeyreply.cpp +++ b/src/publictransport/journeyreply.cpp @@ -1,62 +1,82 @@ /* 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 "journeyreply.h" #include "journeyrequest.h" +#include "logging.h" #include "backends/navitiaclient.h" #include "backends/navitiaparser.h" #include #include #include #include using namespace KPublicTransport; namespace KPublicTransport { class JourneyReplyPrivate { public: std::vector journeys; + QString errorMsg; + JourneyReply::Error error = JourneyReply::NoError; }; } JourneyReply::JourneyReply(const JourneyRequest &req, QNetworkAccessManager *nam) : d(new JourneyReplyPrivate) { auto reply = NavitiaClient::findJourney(req, nam); connect(reply, &QNetworkReply::finished, [reply, this] { - if (reply->error() != QNetworkReply::NoError) { - qDebug() << reply->errorString(); - // TODO - } else { - d->journeys = NavitiaParser::parseJourneys(reply->readAll()); + switch (reply->error()) { + case QNetworkReply::NoError: + d->journeys = NavitiaParser::parseJourneys(reply->readAll()); + break; + case QNetworkReply::ContentNotFoundError: + d->error = NotFoundError; + d->errorMsg = NavitiaParser::parseErrorMessage(reply->readAll()); + break; + default: + d->error = NetworkError; + d->errorMsg = reply->errorString(); + qCDebug(Log) << reply->error() << reply->errorString(); } emit finished(); deleteLater(); }); } JourneyReply::~JourneyReply() = default; std::vector JourneyReply::journeys() const { // TODO avoid the copy here return d->journeys; } + +JourneyReply::Error JourneyReply::error() const +{ + return d->error; +} + +QString JourneyReply::errorString() const +{ + return d->errorMsg; +} diff --git a/src/publictransport/journeyreply.h b/src/publictransport/journeyreply.h index cfe7186..17582df 100644 --- a/src/publictransport/journeyreply.h +++ b/src/publictransport/journeyreply.h @@ -1,57 +1,68 @@ /* 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_JOURNEYREPLY_H #define KPUBLICTRANSPORT_JOURNEYREPLY_H #include #include #include class QNetworkAccessManager; namespace KPublicTransport { class Journey; class JourneyReplyPrivate; class JourneyRequest; /** Journey query response. */ class JourneyReply : public QObject { Q_OBJECT public: ~JourneyReply(); /** Returns the found journeys. */ std::vector journeys() const; - // TODO error messages + /** Error types. */ + enum Error { + NoError, ///< Nothing went wrong. + NetworkError, ///< Error during network operations. + NotFoundError, ///< The requested journey could not be found. + UnknownError ///< Anything else. + }; + /** Error code. */ + Error error() const; + /** Textual error message. */ + QString errorString() const; + Q_SIGNALS: /** Emitted whenever the journey search has been completed. */ void finished(); private: friend class Manager; explicit JourneyReply(const JourneyRequest &req, QNetworkAccessManager *nam); std::unique_ptr d; }; } #endif // KPUBLICTRANSPORT_JOURNEYREPLY_H diff --git a/tests/journeyquery.cpp b/tests/journeyquery.cpp index 0bd74b8..8067403 100644 --- a/tests/journeyquery.cpp +++ b/tests/journeyquery.cpp @@ -1,113 +1,125 @@ /* 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 #include #include #include #include #include #include using namespace KPublicTransport; class QueryManager : public QObject { Q_OBJECT Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged) public: QueryManager() { ptMgr.setNetworkAccessManager(&nam); } Q_INVOKABLE void findJourney(double fromLat, double fromLon, double toLat, double toLon) { engine->rootContext()->setContextProperty(QStringLiteral("_journeys"), QVariantList()); m_loading = true; emit loadingChanged(); + m_errorMsg.clear(); + emit errorMessageChanged(); Location from; from.setCoordinate(fromLat, fromLon); Location to; to.setCoordinate(toLat, toLon); auto reply = ptMgr.findJourney({from, to}); QObject::connect(reply, &JourneyReply::finished, [reply, this]{ - const auto res = reply->journeys(); - QVariantList l; - l.reserve(res.size()); - std::transform(res.begin(), res.end(), std::back_inserter(l), [](const auto &journey) { return QVariant::fromValue(journey); }); - engine->rootContext()->setContextProperty(QStringLiteral("_journeys"), l); m_loading = false; emit loadingChanged(); - for (const auto &journey : res) { - qDebug() << journey.sections().size(); - for (const auto §ion : journey.sections()) { - qDebug() << " From" << section.from().name() << section.departureTime(); - qDebug() << " Mode" << section.mode() << section.route().line().name() << section.route().direction() << section.route().line().modeString(); - qDebug() << " To" << section.to().name() << section.arrivalTime(); + if (reply->error() == JourneyReply::NoError) { + const auto res = reply->journeys(); + QVariantList l; + l.reserve(res.size()); + std::transform(res.begin(), res.end(), std::back_inserter(l), [](const auto &journey) { return QVariant::fromValue(journey); }); + engine->rootContext()->setContextProperty(QStringLiteral("_journeys"), l); + + for (const auto &journey : res) { + qDebug() << journey.sections().size(); + for (const auto §ion : journey.sections()) { + qDebug() << " From" << section.from().name() << section.departureTime(); + qDebug() << " Mode" << section.mode() << section.route().line().name() << section.route().direction() << section.route().line().modeString(); + qDebug() << " To" << section.to().name() << section.arrivalTime(); + } } + } else { + m_errorMsg = reply->errorString(); + emit errorMessageChanged(); } }); } bool loading() const { return m_loading; } + QString errorMessage() const { return m_errorMsg; } QQmlEngine *engine = nullptr; signals: void loadingChanged(); + void errorMessageChanged(); private: QNetworkAccessManager nam; Manager ptMgr; + QString m_errorMsg; bool m_loading = false; }; int main(int argc, char **argv) { QCoreApplication::setApplicationName(QStringLiteral("journeyquery")); QCoreApplication::setOrganizationName(QStringLiteral("KDE")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QGuiApplication app(argc, argv); qmlRegisterUncreatableType("org.kde.kpublictransport", 1, 0, "Line", {}); qmlRegisterUncreatableType("org.kde.kpublictransport", 1, 0, "JourneySection", {}); QueryManager mgr; QQmlApplicationEngine engine; mgr.engine = &engine; engine.rootContext()->setContextProperty(QStringLiteral("_queryMgr"), &mgr); engine.load(QStringLiteral("qrc:/journeyquery.qml")); return app.exec(); } #include "journeyquery.moc" diff --git a/tests/journeyquery.qml b/tests/journeyquery.qml index fd79c0b..84835ae 100644 --- a/tests/journeyquery.qml +++ b/tests/journeyquery.qml @@ -1,186 +1,194 @@ /* 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 . */ import QtQuick 2.5 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 as QQC2 import org.kde.kirigami 2.0 as Kirigami import org.kde.kpublictransport 1.0 Kirigami.ApplicationWindow { title: "Journey Query" reachableModeEnabled: false width: 480 height: 720 pageStack.initialPage: journyQueryPage ListModel { id: exampleModel ListElement { name: "CDG -> Gare de Lyon" fromLat: 2.57110 fromLon: 49.00406 toLat: 2.37708 toLon: 48.84388 } ListElement { name: "ZRH -> Randa" fromLat: 8.56275 fromLon: 47.45050 toLat: 7.78315 toLon: 46.09901 } ListElement { name: "Gare de Midi -> Fosdem" fromLat: 4.33620 fromLon: 50.83588 toLat: 4.38116 toLon: 50.81360 } ListElement { name: "VIE -> Akademy 2018 Accomodation" fromLat: 16.56312 fromLon: 48.12083 toLat: 16.37859 toLon: 48.18282 } ListElement { name: "Akademy 2018 BBQ -> Accomodation" fromLat: 16.43191 fromLon: 48.21612 toLat: 16.37859 toLon: 48.18282 } ListElement { name: "LEI -> Akademy 2017 Accomodation" fromLat: -2.37251 fromLon: 36.84774 toLat: -2.44788 toLon: 36.83731 } ListElement { name: "Akademy 2017 Venue -> Accomodation" fromLat: -2.40377 fromLon: 36.82784 toLat: -2.44788 toLon: 36.83731 } ListElement { name: "TXL -> Akademy 2016" fromLat: 13.29281 fromLon: 52.55420 toLat: 13.41644 toLon: 52.52068 } ListElement { name: "SXF -> Akademy 2016" fromLat: 13.51870 fromLon: 52.38841 toLat: 13.41644 toLon: 52.52068 } ListElement { name: "Brno central station -> Akademy 2014 venue" fromLat: 16.61287 fromLon: 49.19069 toLat: 16.57564 toLon: 49.22462 } } function displayDuration(dur) { if (dur < 60) return "<1min"; if (dur < 3600) return Math.floor(dur/60) + "min"; return Math.floor(dur/3600) + ":" + Math.floor((dur % 3600)/60) } Component { id: journyQueryPage Kirigami.Page { ColumnLayout { anchors.fill: parent QQC2.ComboBox { id: exampleSelector Layout.fillWidth: true model: exampleModel textRole: "name" onCurrentIndexChanged: { var obj = exampleModel.get(currentIndex); _queryMgr.findJourney(obj.fromLat, obj.fromLon, obj.toLat, obj.toLon); } } QQC2.ComboBox { id: journeySelector Layout.fillWidth: true model: _journeys } ListView { Layout.fillHeight: true Layout.fillWidth: true model: _journeys[journeySelector.currentIndex].sections delegate: Item { implicitHeight: delegateLayout.implicitHeight implicitWidth: delegateLayout.implicitWidth ColumnLayout { id: delegateLayout Text { text: "From: " + modelData.from.name visible: index == 0 } Text { text: { switch (modelData.mode) { case JourneySection.PublicTransport: return modelData.route.line.modeString + " " + modelData.route.line.name + " " + displayDuration(modelData.duration); case JourneySection.Walking: return "Walk " + displayDuration(modelData.duration) case JourneySection.Transfer: return "Transfer " + displayDuration(modelData.duration) case JourneySection.Waiting: return "Wait " + displayDuration(modelData.duration) return "???"; }} } Text { text: "To: " + modelData.to.name } } Rectangle { anchors.left: parent.left anchors.leftMargin: -8 height: parent.height width: 4 color: modelData.route.line.color } } QQC2.BusyIndicator { anchors.centerIn: parent running: _queryMgr.loading } + + QQC2.Label { + anchors.centerIn: parent + width: parent.width + text: _queryMgr.errorMessage + color: Kirigami.Theme.negativeTextColor + wrapMode: Text.Wrap + } } } } } }