1 /*
2     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "livedata.h"
8 #include "logging.h"
9 
10 #include <QDir>
11 #include <QDirIterator>
12 #include <QFile>
13 #include <QJsonDocument>
14 #include <QJsonObject>
15 #include <QStandardPaths>
16 
basePath(LiveData::Type type)17 static QString basePath(LiveData::Type type)
18 {
19     QString typeStr;
20     switch (type) {
21         case LiveData::Departure:
22             typeStr = QStringLiteral("departure");
23             break;
24         case LiveData::Arrival:
25             typeStr = QStringLiteral("arrival");
26             break;
27         case LiveData::Journey:
28             typeStr = QStringLiteral("journey");
29             break;
30         default:
31             assert(false);
32     }
33     return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/publictransport/") + typeStr + QLatin1Char('/');
34 }
35 
loadOne(const QString & resId,LiveData::Type type,QDateTime & timestamp)36 static QJsonObject loadOne(const QString &resId, LiveData::Type type, QDateTime &timestamp)
37 {
38     const auto path = basePath(type);
39 
40     QFile f(path + resId + QLatin1String(".json"));
41     if (!f.open(QFile::ReadOnly)) {
42         timestamp = {};
43         return {};
44     }
45 
46     timestamp = f.fileTime(QFile::FileModificationTime);
47     return QJsonDocument::fromJson(f.readAll()).object();
48 }
49 
stopover(LiveData::Type type) const50 KPublicTransport::Stopover LiveData::stopover(LiveData::Type type) const
51 {
52     assert(type == Arrival || type == Departure);
53     return type == Arrival ? arrival : departure;
54 }
55 
setStopover(LiveData::Type type,const KPublicTransport::Stopover & stop)56 void LiveData::setStopover(LiveData::Type type, const KPublicTransport::Stopover &stop)
57 {
58     assert(type == Arrival || type == Departure);
59     type == Arrival ? arrival = stop : departure = stop;
60 }
61 
setTimestamp(LiveData::Type type,const QDateTime & dt)62 void LiveData::setTimestamp(LiveData::Type type, const QDateTime &dt)
63 {
64     switch (type) {
65         case LiveData::Departure: departureTimestamp = dt; break;
66         case LiveData::Arrival: arrivalTimestamp = dt; break;
67         case LiveData::Journey: journeyTimestamp = dt; break;
68         default: assert(false);
69     }
70 }
71 
load(const QString & resId)72 LiveData LiveData::load(const QString &resId)
73 {
74     LiveData ld;
75     auto obj = loadOne(resId, Departure, ld.departureTimestamp);
76     ld.departure = KPublicTransport::Stopover::fromJson(obj);
77     obj = loadOne(resId, Arrival, ld.arrivalTimestamp);
78     ld.arrival = KPublicTransport::Stopover::fromJson(obj);
79     obj = loadOne(resId, Journey, ld.journeyTimestamp);
80     ld.journey = KPublicTransport::JourneySection::fromJson(obj);
81     return ld;
82 }
83 
storeOne(const QString & resId,LiveData::Type type,const QJsonObject & obj,const QDateTime & dt)84 static void storeOne(const QString &resId, LiveData::Type type, const QJsonObject &obj, const QDateTime &dt)
85 {
86     const auto path = basePath(type);
87     QDir().mkpath(path);
88 
89     const QString fileName = path + resId + QLatin1String(".json");
90 
91     if (obj.isEmpty()) {
92         QFile::remove(fileName);
93     } else {
94         QFile file(fileName);
95         if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
96             qCWarning(Log) << "Failed to open public transport cache file:" << file.fileName() << file.errorString();
97             return;
98         }
99         file.write(QJsonDocument(obj).toJson());
100         file.close();
101 
102         // mtime changes need to be done without content changes to take effect
103         file.open(QFile::WriteOnly | QFile::Append);
104         file.setFileTime(dt, QFile::FileModificationTime);
105         file.close();
106     }
107 }
108 
store(const QString & resId,int types) const109 void LiveData::store(const QString &resId, int types) const
110 {
111     if (types & Departure) {
112         storeOne(resId, Departure, KPublicTransport::Stopover::toJson(departure), departureTimestamp);
113     }
114     if (types & Arrival) {
115         storeOne(resId, Arrival, KPublicTransport::Stopover::toJson(arrival), arrivalTimestamp);
116     }
117     if (types & Journey) {
118         storeOne(resId, Journey, KPublicTransport::JourneySection::toJson(journey), journeyTimestamp);
119     }
120 }
121 
remove(const QString & resId)122 void LiveData::remove(const QString& resId)
123 {
124     for (auto type : { Departure, Arrival, Journey }) {
125         storeOne(resId, type, {}, {});
126     }
127 }
128 
listOne(LiveData::Type type,std::vector<QString> & ids)129 static void listOne(LiveData::Type type, std::vector<QString> &ids)
130 {
131     QDir dir(basePath(type));
132     QDirIterator it(basePath(type), QDir::Files);
133     while (it.hasNext()) {
134         it.next();
135         const auto id = it.fileInfo().baseName();
136         const auto idIt = std::lower_bound(ids.begin(), ids.end(), id);
137         if (idIt != ids.end() && (*idIt) == id) {
138             continue;
139         }
140         ids.insert(idIt, id);
141     }
142 }
143 
listAll()144 std::vector<QString> LiveData::listAll()
145 {
146     std::vector<QString> ids;
147     for (auto type : { Departure, Arrival, Journey }) {
148         listOne(type, ids);
149     }
150     return ids;
151 }
152 
clearStorage()153 void LiveData::clearStorage()
154 {
155     for (auto type : { Departure, Arrival, Journey }) {
156         const auto path = basePath(type);
157         if (path.isEmpty()) {
158             continue; // just to not accidentally kill everything...
159         }
160         QDir(path).removeRecursively();
161     }
162 }
163