livedatamanager.cpp 22.3 KB
Newer Older
1
/*
2
    SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3

4
    SPDX-License-Identifier: LGPL-2.0-or-later
5
6
7
8
*/

#include "livedatamanager.h"
#include "logging.h"
9
#include "notificationhelper.h"
10
#include "pkpassmanager.h"
11
#include "reservationhelper.h"
12
#include "reservationmanager.h"
13
#include "publictransport.h"
14

15
#include <KItinerary/BusTrip>
16
17
18
19
20
21
#include <KItinerary/LocationUtil>
#include <KItinerary/Place>
#include <KItinerary/Reservation>
#include <KItinerary/SortUtil>
#include <KItinerary/TrainTrip>

22
23
#include <KPublicTransport/JourneyReply>
#include <KPublicTransport/JourneyRequest>
24
25
#include <KPublicTransport/Location>
#include <KPublicTransport/Manager>
26
27
#include <KPublicTransport/StopoverReply>
#include <KPublicTransport/StopoverRequest>
28

29
30
31
32
#include <KNotifications/KNotification>

#include <KLocalizedString>

Volker Krause's avatar
Volker Krause committed
33
34
35
36
37
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
38
#include <QSettings>
Volker Krause's avatar
Volker Krause committed
39
#include <QStandardPaths>
40
41
42
43
44
45
#include <QVector>

using namespace KItinerary;

LiveDataManager::LiveDataManager(QObject *parent)
    : QObject(parent)
46
    , m_ptMgr(new KPublicTransport::Manager(this))
47
{
48
49
50
51
52
53
54
55
56
57
58
59
60
    QSettings settings;
    settings.beginGroup(QLatin1String("KPublicTransport"));
    m_ptMgr->setAllowInsecureBackends(settings.value(QLatin1String("AllowInsecureBackends"), false).toBool());
    m_ptMgr->setDisabledBackends(settings.value(QLatin1String("DisabledBackends"), QStringList()).toStringList());
    m_ptMgr->setEnabledBackends(settings.value(QLatin1String("EnabledBackends"), QStringList()).toStringList());
    connect(m_ptMgr, &KPublicTransport::Manager::configurationChanged, this, [this]() {
        QSettings settings;
        settings.beginGroup(QLatin1String("KPublicTransport"));
        settings.setValue(QLatin1String("AllowInsecureBackends"), m_ptMgr->allowInsecureBackends());
        settings.setValue(QLatin1String("DisabledBackends"), m_ptMgr->disabledBackends());
        settings.setValue(QLatin1String("EnabledBackends"), m_ptMgr->enabledBackends());
    });

61
62
    m_pollTimer.setSingleShot(true);
    connect(&m_pollTimer, &QTimer::timeout, this, &LiveDataManager::poll);
63
64
65
66
67
68
69
}

LiveDataManager::~LiveDataManager() = default;

void LiveDataManager::setReservationManager(ReservationManager *resMgr)
{
    m_resMgr = resMgr;
70
71
72
73
74
    connect(resMgr, &ReservationManager::batchAdded, this, &LiveDataManager::batchAdded);
    connect(resMgr, &ReservationManager::batchChanged, this, &LiveDataManager::batchChanged);
    connect(resMgr, &ReservationManager::batchContentChanged, this, &LiveDataManager::batchChanged);
    connect(resMgr, &ReservationManager::batchRenamed, this, &LiveDataManager::batchRenamed);
    connect(resMgr, &ReservationManager::batchRemoved, this, &LiveDataManager::batchRemoved);
75

76
    const auto resIds = resMgr->batches();
77
    for (const auto &resId : resIds) {
78
        if (!isRelevant(resId)) {
79
80
81
82
83
            continue;
        }
        m_reservations.push_back(resId);
    }

84
    m_pollTimer.setInterval(nextPollTime());
85
86
87
88
89
}

void LiveDataManager::setPkPassManager(PkPassManager *pkPassMgr)
{
    m_pkPassMgr = pkPassMgr;
90
    connect(m_pkPassMgr, &PkPassManager::passUpdated, this, &LiveDataManager::pkPassUpdated);
91
92
}

93
94
void LiveDataManager::setPollingEnabled(bool pollingEnabled)
{
95
96
97
98
99
100
    if (pollingEnabled) {
        m_pollTimer.setInterval(nextPollTime());
        m_pollTimer.start();
    } else {
        m_pollTimer.stop();
    }
101
102
}

103
104
105
106
107
void LiveDataManager::setShowNotificationsOnLockScreen(bool enabled)
{
    m_showNotificationsOnLockScreen = enabled;
}

108
KPublicTransport::Stopover LiveDataManager::arrival(const QString &resId) const
109
{
110
    return data(resId).arrival;
111
112
}

113
KPublicTransport::Stopover LiveDataManager::departure(const QString &resId) const
114
{
115
    return data(resId).departure;
116
}
117

118
119
120
121
122
KPublicTransport::JourneySection LiveDataManager::journey(const QString &resId) const
{
    return data(resId).journey;
}

123
124
125
126
127
128
129
130
131
132
133
void LiveDataManager::setJourney(const QString &resId, const KPublicTransport::JourneySection &journey)
{
    auto &ld = data(resId);
    ld.journey = journey;
    ld.journeyTimestamp = now();
    ld.departure = journey.departure();
    ld.departureTimestamp = now();
    ld.arrival = journey.arrival();
    ld.arrivalTimestamp = now();
    ld.store(resId, LiveData::AllTypes);

134
135
136
    Q_EMIT journeyUpdated(resId);
    Q_EMIT departureUpdated(resId);
    Q_EMIT arrivalUpdated(resId);
137
138
}

139
140
void LiveDataManager::checkForUpdates()
{
141
    pollForUpdates(true);
142
143
}

144
static bool isSameLine(const KPublicTransport::Line &lhs, const QString &trainName, const QString &trainNumber)
145
{
146
147
148
149
    KPublicTransport::Line rhs;
    rhs.setModeString(trainName);
    rhs.setName(trainNumber);
    return KPublicTransport::Line::isSame(lhs, rhs);
150
151
}

152
static bool isDepartureForReservation(const QVariant &res, const KPublicTransport::Stopover &dep)
153
{
154
155
156
157
    const auto lineData = ReservationHelper::lineNameAndNumber(res);
    return PublicTransport::isSameMode(res, dep.route().line().mode())
        && SortUtil::startDateTime(res) == dep.scheduledDepartureTime()
        && isSameLine(dep.route().line(), lineData.first, lineData.second);
158
159
}

160
static bool isArrivalForReservation(const QVariant &res, const KPublicTransport::Stopover &arr)
161
{
162
163
164
165
    const auto lineData = ReservationHelper::lineNameAndNumber(res);
    return PublicTransport::isSameMode(res, arr.route().line().mode())
        && SortUtil::endDateTime(res) == arr.scheduledArrivalTime()
        && isSameLine(arr.route().line(), lineData.first, lineData.second);
166
167
}

168
169
170
171
172
173
174
175
176
static bool isJourneyForReservation(const QVariant &res, const KPublicTransport::JourneySection &journey)
{
    const auto lineData = ReservationHelper::lineNameAndNumber(res);
    return PublicTransport::isSameMode(res, journey.route().line().mode())
        && SortUtil::startDateTime(res) == journey.scheduledDepartureTime()
        && SortUtil::endDateTime(res) == journey.scheduledArrivalTime()
        && isSameLine(journey.route().line(), lineData.first, lineData.second);
}

177
void LiveDataManager::checkReservation(const QVariant &res, const QString& resId)
178
179
{
    using namespace KPublicTransport;
180
181
182
183
184
185
186
187
188
189
190
    const auto arrived = hasArrived(resId, res);

    // load full journey if we don't have one yet
    if (!arrived && data(resId).journey.mode() == JourneySection::Invalid) {
        const auto from = PublicTransport::locationFromPlace(LocationUtil::departureLocation(res), res);
        const auto to = PublicTransport::locationFromPlace(LocationUtil::arrivalLocation(res), res);
        JourneyRequest req(from, to);
        req.setDateTime(SortUtil::startDateTime(res));
        req.setDateTimeMode(JourneyRequest::Departure);
        req.setIncludeIntermediateStops(true);
        req.setIncludePaths(true);
191
        req.setModes(JourneySection::PublicTransport);
192
        PublicTransport::selectBackends(req, m_ptMgr, res);
193
194
195
196
        auto reply = m_ptMgr->queryJourney(req);
        connect(reply, &Reply::finished, this, [this, resId, reply]() { journeyQueryFinished(reply, resId); });
        return;
    }
197

198
    if (!hasDeparted(resId, res)) {
199
200
201
        StopoverRequest req(PublicTransport::locationFromPlace(LocationUtil::departureLocation(res), res));
        req.setMode(StopoverRequest::QueryDeparture);
        req.setDateTime(SortUtil::startDateTime(res));
202
        PublicTransport::selectBackends(req, m_ptMgr, res);
203
        auto reply = m_ptMgr->queryStopover(req);
204
        connect(reply, &Reply::finished, this, [this, resId, reply]() { stopoverQueryFinished(reply, LiveData::Departure, resId); });
205
    }
206

207
    if (!arrived) {
208
209
210
        StopoverRequest req(PublicTransport::locationFromPlace(LocationUtil::arrivalLocation(res), res));
        req.setMode(StopoverRequest::QueryArrival);
        req.setDateTime(SortUtil::endDateTime(res));
211
        PublicTransport::selectBackends(req, m_ptMgr, res);
212
        auto reply = m_ptMgr->queryStopover(req);
213
        connect(reply, &Reply::finished, this, [this, resId, reply]() { stopoverQueryFinished(reply, LiveData::Arrival, resId); });
214
    }
215
216
}

217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
void LiveDataManager::stopoverQueryFinished(KPublicTransport::StopoverReply* reply, LiveData::Type type, const QString& resId)
{
    reply->deleteLater();
    if (reply->error() != KPublicTransport::Reply::NoError) {
        qCDebug(Log) << reply->error() << reply->errorString();
        return;
    }
    stopoverQueryFinished(reply->takeResult(), type, resId);
}

void LiveDataManager::stopoverQueryFinished(std::vector<KPublicTransport::Stopover> &&result, LiveData::Type type, const QString& resId)
{
    const auto res = m_resMgr->reservation(resId);
    for (const auto &stop : result) {
        qCDebug(Log) << "Got stopover information:" << stop.route().line().name() << stop.scheduledDepartureTime();
        if (type == LiveData::Arrival ? isArrivalForReservation(res, stop) : isDepartureForReservation(res, stop)) {
            qCDebug(Log) << "Found stopover information:" << stop.route().line().name() << stop.expectedPlatform() << stop.expectedDepartureTime();
            updateStopoverData(stop, type, resId, res);
            return;
        }
    }
238
239
240

    // record this is a failed lookup so we don't try again
    data(resId).setTimestamp(type, now());
241
242
}

243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
void LiveDataManager::journeyQueryFinished(KPublicTransport::JourneyReply *reply, const QString &resId)
{
    reply->deleteLater();
    if (reply->error() != KPublicTransport::Reply::NoError) {
        qCDebug(Log) << reply->error() << reply->errorString();
        return;
    }

    using namespace KPublicTransport;
    const auto res = m_resMgr->reservation(resId);
    for (const auto &journey : reply->result()) {
        if (std::count_if(journey.sections().begin(), journey.sections().end(), [](const auto &sec) { return sec.mode() == JourneySection::PublicTransport; }) != 1) {
            continue;
        }
        const auto it = std::find_if(journey.sections().begin(), journey.sections().end(), [](const auto &sec) { return sec.mode() == JourneySection::PublicTransport; });
        assert(it != journey.sections().end());
        qCDebug(Log) << "Got journey information:" << (*it).route().line().name() << (*it).scheduledDepartureTime();
        if (isJourneyForReservation(res, (*it))) {
            qCDebug(Log) << "Found journey information:" << (*it).route().line().name() << (*it).expectedDeparturePlatform() << (*it).expectedDepartureTime();
            updateJourneyData((*it), resId, res);
            return;
        }
    }

    // record this is a failed lookup so we don't try again
    data(resId).setTimestamp(LiveData::Arrival, now());
    data(resId).setTimestamp(LiveData::Departure, now());
}

272
273
void LiveDataManager::updateStopoverData(const KPublicTransport::Stopover &stop, LiveData::Type type, const QString &resId, const QVariant &res)
{
274
275
276
277
278
279
    auto &ld = data(resId);
    const auto oldStop = ld.stopover(type);
    ld.setStopover(type, stop);
    ld.setTimestamp(type, now());
    ld.store(resId);

280
281
282
283
284
285
286
    // update reservation with live data
    const auto newRes = type == LiveData::Arrival ? PublicTransport::mergeArrival(res, stop) : PublicTransport::mergeDeparture(res, stop);
    if (!ReservationHelper::equals(res, newRes)) {
        m_resMgr->updateReservation(resId, newRes);
    }

    // emit update signals
287
    Q_EMIT type == LiveData::Arrival ? arrivalUpdated(resId) : departureUpdated(resId);
288
289

    // check if we need to notify
290
291
    if (NotificationHelper::shouldNotify(oldStop, stop, type)) {
        showNotification(resId, ld);
292
    }
293
}
294

295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
void LiveDataManager::updateJourneyData(const KPublicTransport::JourneySection &journey, const QString &resId, const QVariant &res)
{
    auto &ld = data(resId);
    const auto oldDep = ld.stopover(LiveData::Departure);
    const auto oldArr = ld.stopover(LiveData::Arrival);
    ld.journey = journey;
    ld.journeyTimestamp = now();
    ld.departure = journey.departure();
    ld.departureTimestamp = now();
    ld.arrival = journey.arrival();
    ld.arrivalTimestamp = now();
    ld.store(resId, LiveData::AllTypes);

    // update reservation with live data
    const auto newRes = PublicTransport::mergeJourney(res, journey);
    if (!ReservationHelper::equals(res, newRes)) {
        m_resMgr->updateReservation(resId, newRes);
    }

    // emit update signals
    Q_EMIT journeyUpdated(resId);
    Q_EMIT departureUpdated(resId);
    Q_EMIT arrivalUpdated(resId);

    // check if we need to notify
    if (NotificationHelper::shouldNotify(oldDep, journey.departure(), LiveData::Departure) ||
        NotificationHelper::shouldNotify(oldArr, journey.arrival(), LiveData::Arrival)) {
        showNotification(resId, ld);
    }
}

326
327
void LiveDataManager::showNotification(const QString &resId, const LiveData &ld)
{
328
329
    // check if we still have an active notification, if so, update that one
    const auto it = m_notifications.constFind(resId);
330
    if (it == m_notifications.cend() || !it.value()) {
Volker Krause's avatar
Volker Krause committed
331
        auto n = new KNotification(QStringLiteral("disruption"));
332
        fillNotification(n, ld);
333
        m_notifications.insert(resId, n);
Volker Krause's avatar
Volker Krause committed
334
        n->sendEvent();
335
    } else {
336
        fillNotification(it.value(), ld);
337
338
        it.value()->update();
    }
339
340
}

341
342
343
344
345
346
void LiveDataManager::fillNotification(KNotification* n, const LiveData& ld) const
{
    n->setTitle(NotificationHelper::title(ld));
    n->setText(NotificationHelper::message(ld));
    n->setIconName(QLatin1String("clock"));
    if (m_showNotificationsOnLockScreen) {
347
        n->setHint(QStringLiteral("x-kde-visibility"), QStringLiteral("public"));
348
349
350
    }
}

351
352
353
354
355
356
void LiveDataManager::showNotification(const QString &resId)
{
    // this is only meant for testing!
    showNotification(resId, data(resId));
}

357
358
359
360
361
362
363
364
365
366
367
void LiveDataManager::cancelNotification(const QString &resId)
{
    const auto nIt = m_notifications.find(resId);
    if (nIt != m_notifications.end()) {
        if (nIt.value()) {
            nIt.value()->close();
        }
        m_notifications.erase(nIt);
    }
}

368
QDateTime LiveDataManager::departureTime(const QString &resId, const QVariant &res) const
369
{
370
    if (JsonLd::isA<TrainReservation>(res)) {
371
        const auto &dep = departure(resId);
372
        if (dep.hasExpectedDepartureTime()) {
373
            return dep.expectedDepartureTime();
374
375
376
        }
    }

377
    return SortUtil::startDateTime(res);
378
379
}

380
QDateTime LiveDataManager::arrivalTime(const QString &resId, const QVariant &res) const
381
{
382
    if (JsonLd::isA<TrainReservation>(res)) {
383
        const auto &arr = arrival(resId);
384
        if (arr.hasExpectedArrivalTime()) {
385
            return arr.expectedArrivalTime();
386
387
388
        }
    }

Volker Krause's avatar
Volker Krause committed
389
    return SortUtil::endDateTime(res);
390
391
392
393
}

bool LiveDataManager::hasDeparted(const QString &resId, const QVariant &res) const
{
394
    return departureTime(resId, res) < now();
395
396
397
398
}

bool LiveDataManager::hasArrived(const QString &resId, const QVariant &res) const
{
399
    return arrivalTime(resId, res) < now();
400
401
}

402
LiveData& LiveDataManager::data(const QString &resId) const
Volker Krause's avatar
Volker Krause committed
403
{
404
405
406
    auto it = m_data.find(resId);
    if (it != m_data.end()) {
        return it.value();
Volker Krause's avatar
Volker Krause committed
407
408
    }

409
410
    it = m_data.insert(resId, LiveData::load(resId));
    return it.value();
411
412
}

Volker Krause's avatar
Volker Krause committed
413
414
415
416
void LiveDataManager::importData(const QString& resId, LiveData &&data)
{
    // we don't need to store data, Importer already does that
    m_data[resId] = std::move(data);
417
418
419
    Q_EMIT journeyUpdated(resId);
    Q_EMIT departureUpdated(resId);
    Q_EMIT arrivalUpdated(resId);
Volker Krause's avatar
Volker Krause committed
420
421
}

422
423
424
bool LiveDataManager::isRelevant(const QString &resId) const
{
    const auto res = m_resMgr->reservation(resId);
425
426
    // we only care about transit reservations
    if (!JsonLd::canConvert<Reservation>(res) || !LocationUtil::isLocationChange(res)) {
427
428
        return false;
    }
429
430
    // we don't care about past events
    if (hasArrived(resId, res)) {
431
432
        return false;
    }
433
434
435
436

    // TODO: we could discard non-train trips without a pkpass in their batch here?

    return true;
437
438
}

439
void LiveDataManager::batchAdded(const QString &resId)
440
441
442
443
444
{
    if (!isRelevant(resId)) {
        return;
    }

445
446
    m_reservations.push_back(resId);
    m_pollTimer.setInterval(nextPollTime());
447
448
}

449
void LiveDataManager::batchChanged(const QString &resId)
450
{
451
452
453
454
455
    const auto it = std::find(m_reservations.begin(), m_reservations.end(), resId);
    const auto relevant = isRelevant(resId);

    if (it == m_reservations.end() && relevant) {
        m_reservations.push_back(resId);
456
    } else if (it != m_reservations.end() && !relevant) {
457
458
459
        m_reservations.erase(it);
    }

460
461
    // check if existing updates still apply, and remove them otherwise!
    const auto res = m_resMgr->reservation(resId);
462
463
    const auto dataIt = m_data.find(resId);
    if (dataIt != m_data.end()) {
464
        if ((*dataIt).departureTimestamp.isValid() && !isDepartureForReservation(res, (*dataIt).departure)) {
465
466
467
            (*dataIt).departure = {};
            (*dataIt).departureTimestamp = {};
            (*dataIt).store(resId, LiveData::Departure);
468
            Q_EMIT departureUpdated(resId);
469
        }
470
        if ((*dataIt).arrivalTimestamp.isValid() && !isArrivalForReservation(res, (*dataIt).arrival)) {
471
472
473
            (*dataIt).arrival = {};
            (*dataIt).arrivalTimestamp = {};
            (*dataIt).store(resId, LiveData::Arrival);
474
            Q_EMIT arrivalUpdated(resId);
475
        }
476

477
478
479
480
481
482
        if ((*dataIt).journeyTimestamp.isValid() && !isJourneyForReservation(res, (*dataIt).journey)) {
            (*dataIt).journey = {};
            (*dataIt).journeyTimestamp = {};
            (*dataIt).store(resId, LiveData::Journey);
            Q_EMIT journeyUpdated(resId);
        }
483
484
    }

485
    m_pollTimer.setInterval(nextPollTime());
486
487
}

488
489
void LiveDataManager::batchRenamed(const QString &oldBatchId, const QString &newBatchId)
{
490
491
492
493
    const auto it = std::find(m_reservations.begin(), m_reservations.end(), oldBatchId);
    if (it != m_reservations.end()) {
        *it = newBatchId;
    }
494
495
496
}

void LiveDataManager::batchRemoved(const QString &resId)
497
498
499
500
501
{
    const auto it = std::find(m_reservations.begin(), m_reservations.end(), resId);
    if (it != m_reservations.end()) {
        m_reservations.erase(it);
    }
502

503
    cancelNotification(resId);
504
505
    LiveData::remove(resId);
    m_data.remove(resId);
506
507
}

508
509
510
void LiveDataManager::poll()
{
    qCDebug(Log);
511
512
513
514
515
516
517
518
519
520
521
522
523
524
    pollForUpdates(false);

    m_pollTimer.setInterval(std::max(nextPollTime(), 60 * 1000)); // we pool everything that happens within a minute here
    m_pollTimer.start();
}

void LiveDataManager::pollForUpdates(bool force)
{
    for (auto it = m_reservations.begin(); it != m_reservations.end();) {
        const auto batchId = *it;
        const auto res = m_resMgr->reservation(*it);

        // clean up obsolete stuff
        if (hasArrived(*it, res)) {
525
            cancelNotification(*it);
526
527
528
529
530
531
532
            it = m_reservations.erase(it);
            continue;
        }
        ++it;

        if (!force && nextPollTimeForReservation(batchId) > 60 * 1000) {
            // data is still "fresh" according to the poll policy
533
534
            continue;
        }
535

536
        if (JsonLd::isA<TrainReservation>(res)) {
537
            checkReservation(res, batchId);
538
539
        }

540
541
542
543
544
545
546
547
548
549
        // check for pkpass updates, for each element in this batch
        const auto resIds = m_resMgr->reservationsForBatch(batchId);
        for (const auto &resId : resIds) {
            const auto res = m_resMgr->reservation(resId);
            const auto passId = m_pkPassMgr->passId(res);
            if (!passId.isEmpty()) {
                m_pkPassMgr->updatePass(passId);
            }
        }
    }
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
}

int LiveDataManager::nextPollTime() const
{
    int t = std::numeric_limits<int>::max();
    for (const auto &resId : m_reservations) {
        t = std::min(t, nextPollTimeForReservation(resId));
    }
    qCDebug(Log) << "next auto-update in" << (t/1000) << "secs";
    return t;
}

struct {
    int distance; // secs
    int pollInterval; // secs
} static const pollIntervalTable[] = {
    { 3600, 5*60 }, // for <1h we poll every 5 minutes
    { 4 * 3600, 15 * 60 }, // for <4h we poll every 15 minutes
    { 24 * 3600, 3600 }, // for <1d we poll once per hour
    { 4 * 24 * 3600, 24 * 3600 }, // for <4d we poll once per day
};

int LiveDataManager::nextPollTimeForReservation(const QString& resId) const
{
    const auto res = m_resMgr->reservation(resId);

576
    const auto now = this->now();
577
    auto dist = now.secsTo(departureTime(resId, res));
578
    if (dist < 0) {
579
        dist = now.secsTo(arrivalTime(resId, res));
580
581
582
583
584
585
586
587
588
589
590
591
    }
    if (dist < 0) {
        return std::numeric_limits<int>::max();
    }

    const auto it = std::lower_bound(std::begin(pollIntervalTable), std::end(pollIntervalTable), dist, [](const auto &lhs, const auto rhs) {
        return lhs.distance < rhs;
    });
    if (it == std::end(pollIntervalTable)) {
        return std::numeric_limits<int>::max();
    }

592
    // check last poll time for this reservation
593
594
    const auto &ld = data(resId);
    const auto lastArrivalPoll = ld.arrivalTimestamp;
595
    const auto lastDeparturePoll = lastDeparturePollTime(resId, res);
596
597
598
599
600
601
602
603
    auto lastRelevantPoll = lastArrivalPoll;
    // ignore departure if we have already departed
    if (!hasDeparted(resId, res) && lastDeparturePoll.isValid()) {
        if (!lastArrivalPoll.isValid() || lastArrivalPoll > lastDeparturePoll) {
            lastRelevantPoll = lastDeparturePoll;
        }
    }
    const int lastPollDist = !lastRelevantPoll.isValid()
604
        ? (24 * 3600) // no poll yet == long time ago
605
606
        : lastRelevantPoll.secsTo(now);
    return std::max((it->pollInterval - lastPollDist) * 1000, 0); // we need msecs
607
608
}

609
610
QDateTime LiveDataManager::lastDeparturePollTime(const QString &batchId, const QVariant &res) const
{
611
    auto dt = data(batchId).departureTimestamp;
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
    if (dt.isValid()) {
        return dt;
    }

    // check for pkpass updates
    const auto resIds = m_resMgr->reservationsForBatch(batchId);
    for (const auto &resId : resIds) {
        const auto res = m_resMgr->reservation(resId);
        const auto passId = m_pkPassMgr->passId(res);
        if (!passId.isEmpty()) {
            dt = m_pkPassMgr->updateTime(passId);
        }
        if (dt.isValid()) {
            return dt;
        }
    }

    return dt;
}

632
633
void LiveDataManager::pkPassUpdated(const QString &passId, const QStringList &changes)
{
Laurent Montel's avatar
Laurent Montel committed
634
    Q_UNUSED(passId)
635
636
    // ### to provide more context, we need to have a passId -> batchId map here eventually

Volker Krause's avatar
Volker Krause committed
637
638
639
    if (!changes.isEmpty()) {
        KNotification::event(KNotification::Notification, i18n("Itinerary change"), changes.join(QLatin1Char('\n')), QLatin1String("clock"));
    }
640
641
}

642
KPublicTransport::Manager* LiveDataManager::publicTransportManager() const
643
644
645
646
{
    return m_ptMgr;
}

647
648
649
650
651
652
653
654
QDateTime LiveDataManager::now() const
{
    if (Q_UNLIKELY(m_unitTestTime.isValid())) {
        return m_unitTestTime;
    }
    return QDateTime::currentDateTime();
}

655
#include "moc_livedatamanager.cpp"