File indexing completed on 2024-05-12 04:42:33

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "deutschebahnbackend.h"
0008 #include "deutschebahnvehiclelayoutparser.h"
0009 #include "cache.h"
0010 
0011 #include <KPublicTransport/Stopover>
0012 #include <KPublicTransport/VehicleLayoutReply>
0013 #include <KPublicTransport/VehicleLayoutRequest>
0014 
0015 #include <QDebug>
0016 #include <QNetworkAccessManager>
0017 #include <QNetworkRequest>
0018 #include <QNetworkReply>
0019 #include <QRegularExpression>
0020 #include <QUrl>
0021 
0022 using namespace KPublicTransport;
0023 
0024 static QString extractTrainNumber(const Route &route)
0025 {
0026     if (!route.name().isEmpty()) {
0027         QRegularExpression regex(QStringLiteral("(?:[A-Z]+)?\\s*(\\d+)"));
0028         const auto match = regex.match(route.name());
0029         if (match.hasMatch()) {
0030             return match.captured(1);
0031         }
0032     }
0033 
0034     const auto line = route.line();
0035     QRegularExpression regex(QStringLiteral("(?:ICE|IC|EC|RJ|NJ)\\s*(\\d+)"));
0036     const auto match = regex.match(line.modeString() + line.name());
0037     if (match.hasMatch()) {
0038         return match.captured(1);
0039     }
0040 
0041     return {};
0042 }
0043 
0044 bool DeutscheBahnBackend::queryVehicleLayout(const VehicleLayoutRequest &request, VehicleLayoutReply *reply, QNetworkAccessManager *nam) const
0045 {
0046     // unlike the rest of the DB API, this only works in Germany, so do our own geo filtering here.
0047     const auto germanyBBox = QPolygonF({ {5.56384, 55.0492}, {6.131, 47.2565}, {15.4307, 47.4737}, {14.6794, 54.7568} });
0048     if (!germanyBBox.containsPoint({request.stopover().stopPoint().longitude(), request.stopover().stopPoint().latitude()}, Qt::WindingFill)) {
0049         qDebug() << "request outside of bounding box";
0050         return false;
0051     }
0052 
0053     // we need two parameters for the online API: the train number (numeric only), and the departure time
0054     // note: data is only available withing the upcoming 24h
0055     // checking this early is useful as the error response from the online service is extremely verbose...
0056     auto dt = request.stopover().scheduledDepartureTime().isValid() ? request.stopover().scheduledDepartureTime() : request.stopover().scheduledArrivalTime();
0057     const auto trainNum = extractTrainNumber(request.stopover().route());
0058     if (!dt.isValid() || trainNum.isEmpty()) {
0059         return false;
0060     }
0061 
0062     // there are only valid results for a 24h time window, so try to adjust the date accordingly
0063     const auto now = QDateTime::currentDateTime();
0064     if (dt.daysTo(now) > 1 || dt.daysTo(now) < -1) {
0065         qDebug() << "adjusting departure time to today:" << dt;
0066         dt.setDate(QDate::currentDate());
0067     }
0068 
0069     QUrl url;
0070     url.setScheme(QStringLiteral("https"));
0071     url.setHost(QStringLiteral("ist-wr.noncd.db.de"));
0072     url.setPath(QLatin1String("/wagenreihung/1.0/") + trainNum + QLatin1Char('/') + dt.toString(QStringLiteral("yyyyMMddhhmm")));
0073 
0074     QNetworkRequest netReq(url);
0075     logRequest(request, netReq);
0076     auto netReply = nam->get(netReq);
0077     netReply->setParent(reply);
0078 
0079     QObject::connect(netReply, &QNetworkReply::finished, reply, [this, reply, netReply] {
0080         const auto data = netReply->readAll();
0081         logReply(reply, netReply, data);
0082 
0083         if (netReply->error() == QNetworkReply::NoError) {
0084             DeutscheBahnVehicleLayoutParser p;
0085             if (p.parse(data)) {
0086                 Cache::addVehicleLayoutCacheEntry(backendId(), reply->request().cacheKey(), p.stopover, {}, std::chrono::minutes(2));
0087                 addResult(reply, p.stopover);
0088             } else {
0089                 addError(reply, p.error, p.errorMessage);
0090                 if (p.error == Reply::NotFoundError) {
0091                     Cache::addNegativeVehicleLayoutCacheEntry(backendId(), reply->request().cacheKey(), std::chrono::hours(24));
0092                 }
0093             }
0094         } else {
0095             addError(reply, Reply::NetworkError, reply->errorString());
0096         }
0097         netReply->deleteLater();
0098     });
0099 
0100     return true;
0101 }