File indexing completed on 2025-02-02 05:02:30

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "livedata.h"
0008 
0009 #include "jsonio.h"
0010 #include "logging.h"
0011 
0012 #include <QDir>
0013 #include <QDirIterator>
0014 #include <QFile>
0015 #include <QJsonObject>
0016 #include <QStandardPaths>
0017 
0018 static QString basePath(LiveData::Type type)
0019 {
0020     QString typeStr;
0021     switch (type) {
0022         case LiveData::Departure:
0023             typeStr = QStringLiteral("departure");
0024             break;
0025         case LiveData::Arrival:
0026             typeStr = QStringLiteral("arrival");
0027             break;
0028         case LiveData::Journey:
0029             typeStr = QStringLiteral("journey");
0030             break;
0031         default:
0032             assert(false);
0033     }
0034     return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1StringView("/publictransport/") + typeStr + QLatin1Char('/');
0035 }
0036 
0037 static QJsonObject loadOne(const QString &resId, LiveData::Type type, QDateTime &timestamp)
0038 {
0039     const auto path = basePath(type);
0040 
0041     QFile f(path + resId + QLatin1StringView(".json"));
0042     if (!f.open(QFile::ReadOnly)) {
0043         timestamp = {};
0044         return {};
0045     }
0046 
0047     timestamp = f.fileTime(QFile::FileModificationTime);
0048     return JsonIO::read(f.readAll()).toObject();
0049 }
0050 
0051 KPublicTransport::Stopover LiveData::stopover(LiveData::Type type) const
0052 {
0053     assert(type == Arrival || type == Departure);
0054     return type == Arrival ? arrival : departure;
0055 }
0056 
0057 void LiveData::setStopover(LiveData::Type type, const KPublicTransport::Stopover &stop)
0058 {
0059     assert(type == Arrival || type == Departure);
0060     type == Arrival ? arrival = stop : departure = stop;
0061 }
0062 
0063 void LiveData::setTimestamp(LiveData::Type type, const QDateTime &dt)
0064 {
0065     switch (type) {
0066         case LiveData::Departure: departureTimestamp = dt; break;
0067         case LiveData::Arrival: arrivalTimestamp = dt; break;
0068         case LiveData::Journey: journeyTimestamp = dt; break;
0069         default: assert(false);
0070     }
0071 }
0072 
0073 bool LiveData::isEmpty() const
0074 {
0075     return departure.stopPoint().isEmpty() && arrival.stopPoint().isEmpty() && journey.from().isEmpty() && journey.to().isEmpty();
0076 }
0077 
0078 LiveData LiveData::load(const QString &resId)
0079 {
0080     LiveData ld;
0081     auto obj = loadOne(resId, Departure, ld.departureTimestamp);
0082     ld.departure = KPublicTransport::Stopover::fromJson(obj);
0083     obj = loadOne(resId, Arrival, ld.arrivalTimestamp);
0084     ld.arrival = KPublicTransport::Stopover::fromJson(obj);
0085     obj = loadOne(resId, Journey, ld.journeyTimestamp);
0086     ld.journey = KPublicTransport::JourneySection::fromJson(obj);
0087     return ld;
0088 }
0089 
0090 static void storeOne(const QString &resId, LiveData::Type type, const QJsonObject &obj, const QDateTime &dt)
0091 {
0092     const auto path = basePath(type);
0093     QDir().mkpath(path);
0094 
0095     const QString fileName = path + resId + QLatin1StringView(".json");
0096 
0097     if (obj.isEmpty()) {
0098         QFile::remove(fileName);
0099     } else {
0100         QFile file(fileName);
0101         if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
0102             qCWarning(Log) << "Failed to open public transport cache file:" << file.fileName() << file.errorString();
0103             return;
0104         }
0105         file.write(JsonIO::write(obj));
0106         file.close();
0107 
0108         // mtime changes need to be done without content changes to take effect
0109         file.open(QFile::WriteOnly | QFile::Append);
0110         file.setFileTime(dt, QFile::FileModificationTime);
0111         file.close();
0112     }
0113 }
0114 
0115 void LiveData::store(const QString &resId, int types) const
0116 {
0117     if (types & Departure) {
0118         storeOne(resId, Departure, KPublicTransport::Stopover::toJson(departure), departureTimestamp);
0119     }
0120     if (types & Arrival) {
0121         storeOne(resId, Arrival, KPublicTransport::Stopover::toJson(arrival), arrivalTimestamp);
0122     }
0123     if (types & Journey) {
0124         storeOne(resId, Journey, KPublicTransport::JourneySection::toJson(journey), journeyTimestamp);
0125     }
0126 }
0127 
0128 void LiveData::remove(const QString& resId)
0129 {
0130     for (auto type : { Departure, Arrival, Journey }) {
0131         storeOne(resId, type, {}, {});
0132     }
0133 }
0134 
0135 static void listOne(LiveData::Type type, std::vector<QString> &ids)
0136 {
0137     QDir dir(basePath(type));
0138     QDirIterator it(basePath(type), QDir::Files);
0139     while (it.hasNext()) {
0140         it.next();
0141         const auto id = it.fileInfo().baseName();
0142         const auto idIt = std::lower_bound(ids.begin(), ids.end(), id);
0143         if (idIt != ids.end() && (*idIt) == id) {
0144             continue;
0145         }
0146         ids.insert(idIt, id);
0147     }
0148 }
0149 
0150 std::vector<QString> LiveData::listAll()
0151 {
0152     std::vector<QString> ids;
0153     for (auto type : { Departure, Arrival, Journey }) {
0154         listOne(type, ids);
0155     }
0156     return ids;
0157 }
0158 
0159 void LiveData::clearStorage()
0160 {
0161     for (auto type : { Departure, Arrival, Journey }) {
0162         const auto path = basePath(type);
0163         if (path.isEmpty()) {
0164             continue; // just to not accidentally kill everything...
0165         }
0166         QDir(path).removeRecursively();
0167     }
0168 }