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