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 ×tamp) 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 }