1 /*
2 SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "deutschebahnbackend.h"
8 #include "deutschebahnvehiclelayoutparser.h"
9 #include "cache.h"
10
11 #include <KPublicTransport/Stopover>
12 #include <KPublicTransport/VehicleLayoutReply>
13 #include <KPublicTransport/VehicleLayoutRequest>
14
15 #include <QDebug>
16 #include <QNetworkAccessManager>
17 #include <QNetworkRequest>
18 #include <QNetworkReply>
19 #include <QRegularExpression>
20 #include <QUrl>
21
22 using namespace KPublicTransport;
23
extractTrainNumber(const Line & line)24 static QString extractTrainNumber(const Line &line)
25 {
26 qDebug() << line.modeString() << line.name();
27 QRegularExpression regex(QStringLiteral("(?:ICE|IC|EC)\\s*(\\d+)"));
28 const auto match = regex.match(line.modeString() + line.name());
29 if (match.hasMatch()) {
30 return match.captured(1);
31 }
32 return {};
33 }
34
queryVehicleLayout(const VehicleLayoutRequest & request,VehicleLayoutReply * reply,QNetworkAccessManager * nam) const35 bool DeutscheBahnBackend::queryVehicleLayout(const VehicleLayoutRequest &request, VehicleLayoutReply *reply, QNetworkAccessManager *nam) const
36 {
37 // unlike the rest of the DB API, this only works in Germany, so do our own geo filtering here.
38 const auto germanyBBox = QPolygonF({ {5.56384, 55.0492}, {6.131, 47.2565}, {15.4307, 47.4737}, {14.6794, 54.7568} });
39 if (!germanyBBox.containsPoint({request.stopover().stopPoint().longitude(), request.stopover().stopPoint().latitude()}, Qt::WindingFill)) {
40 qDebug() << "request outside of bounding box";
41 return false;
42 }
43
44 // we need two parameters for the online API: the train number (numeric only), and the departure time
45 // note: data is only available withing the upcoming 24h
46 // checking this early is useful as the error response from the online service is extremely verbose...
47 auto dt = request.stopover().scheduledDepartureTime().isValid() ? request.stopover().scheduledDepartureTime() : request.stopover().scheduledArrivalTime();
48 const auto trainNum = extractTrainNumber(request.stopover().route().line());
49 if (!dt.isValid() || trainNum.isEmpty()) {
50 return false;
51 }
52
53 // there are only valid results for a 24h time window, so try to adjust the date accordingly
54 const auto now = QDateTime::currentDateTime();
55 if (dt.daysTo(now) > 1 || dt.daysTo(now) < -1) {
56 qDebug() << "adjusting departure time to today:" << dt;
57 dt.setDate(QDate::currentDate());
58 }
59
60 QUrl url;
61 url.setScheme(QStringLiteral("https"));
62 url.setHost(QStringLiteral("www.apps-bahn.de"));
63 url.setPath(QLatin1String("/wr/wagenreihung/1.0/") + trainNum + QLatin1Char('/') + dt.toString(QStringLiteral("yyyyMMddhhmm")));
64
65 QNetworkRequest netReq(url);
66 logRequest(request, netReq);
67 auto netReply = nam->get(netReq);
68
69 QObject::connect(netReply, &QNetworkReply::finished, reply, [this, reply, netReply] {
70 const auto data = netReply->readAll();
71 logReply(reply, netReply, data);
72
73 if (netReply->error() == QNetworkReply::NoError) {
74 DeutscheBahnVehicleLayoutParser p;
75 if (p.parse(data)) {
76 Cache::addVehicleLayoutCacheEntry(backendId(), reply->request().cacheKey(), p.stopover, {}, std::chrono::minutes(2));
77 addResult(reply, p.stopover);
78 } else {
79 addError(reply, p.error, p.errorMessage);
80 if (p.error == Reply::NotFoundError) {
81 Cache::addNegativeVehicleLayoutCacheEntry(backendId(), reply->request().cacheKey(), std::chrono::hours(24));
82 }
83 }
84 } else {
85 addError(reply, Reply::NetworkError, reply->errorString());
86 }
87 netReply->deleteLater();
88 });
89
90 return true;
91 }
92