File indexing completed on 2024-12-08 07:19:11

0001 /*
0002     SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "stopoverreply.h"
0008 #include "logging.h"
0009 #include "reply_p.h"
0010 #include "requestcontext_p.h"
0011 #include "stopoverrequest.h"
0012 #include "backends/abstractbackend.h"
0013 #include "backends/cache.h"
0014 #include "datatypes/stopoverutil_p.h"
0015 
0016 #include <KPublicTransport/Stopover>
0017 
0018 #include <QNetworkReply>
0019 
0020 using namespace KPublicTransport;
0021 
0022 namespace KPublicTransport {
0023 class StopoverReplyPrivate : public ReplyPrivate {
0024 public:
0025     void finalizeResult() override;
0026     bool needToWaitForAssets() const override;
0027 
0028     StopoverRequest request;
0029     StopoverRequest nextRequest;
0030     StopoverRequest prevRequest;
0031     std::vector<Stopover> result;
0032 };
0033 }
0034 
0035 void StopoverReplyPrivate::finalizeResult()
0036 {
0037     if (result.empty()) {
0038         return;
0039     }
0040     error = Reply::NoError;
0041     errorMsg.clear();
0042 
0043     std::sort(result.begin(), result.end(), [this](const auto &lhs, const auto &rhs) {
0044             return StopoverUtil::timeLessThan(request, lhs, rhs);
0045     });
0046 
0047     for (auto it = result.begin(); it != result.end(); ++it) {
0048         for (auto mergeIt = it + 1; mergeIt != result.end();) {
0049             if (!StopoverUtil::timeEqual(request, (*it), (*mergeIt))) {
0050                 break;
0051             }
0052 
0053             if (Stopover::isSame(*it, *mergeIt)) {
0054                 *it = Stopover::merge(*it, *mergeIt);
0055                 mergeIt = result.erase(mergeIt);
0056             } else {
0057                 ++mergeIt;
0058             }
0059         }
0060     }
0061 
0062     nextRequest.purgeLoops(request);
0063     prevRequest.purgeLoops(request);
0064 }
0065 
0066 bool StopoverReplyPrivate::needToWaitForAssets() const
0067 {
0068     return request.downloadAssets();
0069 }
0070 
0071 StopoverReply::StopoverReply(const StopoverRequest &req, QObject *parent)
0072     : Reply(new StopoverReplyPrivate, parent)
0073 {
0074     Q_D(StopoverReply);
0075     d->request = req;
0076     d->nextRequest = req;
0077     d->prevRequest = req;
0078 }
0079 
0080 StopoverReply::~StopoverReply() = default;
0081 
0082 StopoverRequest StopoverReply::request() const
0083 {
0084     Q_D(const StopoverReply);
0085     return d->request;
0086 }
0087 
0088 const std::vector<Stopover>& StopoverReply::result() const
0089 {
0090     Q_D(const StopoverReply);
0091     return d->result;
0092 }
0093 
0094 std::vector<Stopover>&& StopoverReply::takeResult()
0095 {
0096     Q_D(StopoverReply);
0097     return std::move(d->result);
0098 }
0099 
0100 void StopoverReply::addResult(const AbstractBackend *backend, std::vector<Stopover> &&res)
0101 {
0102     Q_D(StopoverReply);
0103     // update context for next/prev requests
0104     // do this first, before res gets moved from below
0105     if (d->request.mode() == StopoverRequest::QueryDeparture && !res.empty()) {
0106         // we create a context for later queries here in any case, since we can emulate that generically without backend support
0107         auto context = d->nextRequest.context(backend);
0108         context.type = RequestContext::Next;
0109         for (const auto &dep : res) {
0110             context.dateTime = std::max(context.dateTime, dep.scheduledDepartureTime());
0111         }
0112         d->nextRequest.setContext(backend, std::move(context));
0113     }
0114 
0115     // if this is a backend with a static timezone, apply this to the result
0116     if (backend->timeZone().isValid()) {
0117         for (auto &dep : res) {
0118             StopoverUtil::applyTimeZone(dep, backend->timeZone());
0119         }
0120     }
0121 
0122     // augment line information
0123     for (auto &dep : res) {
0124         dep.applyMetaData(request().downloadAssets());
0125     }
0126 
0127     // apply static attributions if @p backend contributed to the results
0128     addAttribution(backend->attribution());
0129 
0130     // cache negative hits, positive ones are too short-lived
0131     if (res.empty()) {
0132         Cache::addNegativeDepartureCacheEntry(backend->backendId(), request().cacheKey());
0133     }
0134 
0135     if (d->result.empty()) {
0136         d->result = std::move(res);
0137     } else {
0138         d->result.insert(d->result.end(), res.begin(), res.end());
0139     }
0140 
0141     d->pendingOps--;
0142     d->emitUpdated(this);
0143     d->emitFinishedIfDone(this);
0144 }
0145 
0146 StopoverRequest StopoverReply::nextRequest() const
0147 {
0148     Q_D(const StopoverReply);
0149     if (d->nextRequest.contexts().empty()) {
0150         return {};
0151     }
0152     return d->nextRequest;
0153 }
0154 
0155 StopoverRequest StopoverReply::previousRequest() const
0156 {
0157     Q_D(const StopoverReply);
0158     if (d->prevRequest.contexts().empty()) {
0159         return {};
0160     }
0161     return d->prevRequest;
0162 }
0163 
0164 void StopoverReply::setNextContext(const AbstractBackend *backend, const QVariant &data)
0165 {
0166     Q_D(StopoverReply);
0167     auto context = d->nextRequest.context(backend);
0168     context.type = RequestContext::Next;
0169     context.backendData = data;
0170     d->nextRequest.setContext(backend, std::move(context));
0171 }
0172 
0173 void StopoverReply::setPreviousContext(const AbstractBackend *backend, const QVariant &data)
0174 {
0175     Q_D(StopoverReply);
0176     auto context = d->prevRequest.context(backend);
0177     context.type = RequestContext::Previous;
0178     context.backendData = data;
0179     d->prevRequest.setContext(backend, std::move(context));
0180 }
0181 
0182 void StopoverReply::addError(const AbstractBackend *backend, Reply::Error error, const QString &errorMsg)
0183 {
0184     if (error == Reply::NotFoundError) {
0185         Cache::addNegativeDepartureCacheEntry(backend->backendId(), request().cacheKey());
0186     } else {
0187         qCDebug(Log) << backend->backendId() << error << errorMsg;
0188     }
0189     Reply::addError(error, errorMsg);
0190 }