KItinerary

file.cpp
1/*
2 SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "file.h"
8#include "jsonlddocument.h"
9#include "logging.h"
10
11#include <KItinerary/CreativeWork>
12
13#include <KPkPass/Pass>
14
15#include <KZip>
16
17#include <QDebug>
18#include <QJsonArray>
19#include <QJsonDocument>
20#include <QJsonObject>
21#include <QString>
22#include <QUuid>
23
24using namespace Qt::Literals::StringLiterals;
25using namespace KItinerary;
26
27namespace KItinerary {
28class FilePrivate
29{
30public:
31 QString fileName;
32 QIODevice *device = nullptr;
33 std::unique_ptr<KZip> zipFile;
34};
35}
36
37File::File()
38 : d(new FilePrivate)
39{
40}
41
42File::File(const QString &fileName)
43 : d(new FilePrivate)
44{
45 d->fileName = fileName;
46}
47
48File::File(QIODevice* device)
49 : d(new FilePrivate)
50{
51 d->device = device;
52}
53
54File::File(KItinerary::File &&) noexcept = default;
55
56File::~File()
57{
58 close();
59}
60
61File& KItinerary::File::operator=(KItinerary::File &&) noexcept = default;
62
63void File::setFileName(const QString &fileName)
64{
65 d->fileName = fileName;
66}
67
68bool File::open(File::OpenMode mode) const
69{
70 if (d->device) {
71 d->zipFile = std::make_unique<KZip>(d->device);
72 } else {
73 d->zipFile = std::make_unique<KZip>(d->fileName);
74 }
75
76 if (!d->zipFile->open(mode == File::Write ? QIODevice::WriteOnly : QIODevice::ReadOnly)) {
77 qCWarning(Log) << d->zipFile->errorString() << d->fileName;
78 return false;
79 }
80
81 return true;
82}
83
85{
86 if (d->zipFile && !d->zipFile->isOpen()) {
87 return d->zipFile->errorString();
88 }
89 return {};
90}
91
93{
94 if (d->zipFile) {
95 d->zipFile->close();
96 }
97 d->zipFile.reset();
98}
99
101 Q_ASSERT(d->zipFile);
102 const auto resDir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("reservations"_L1));
103 if (!resDir) {
104 return {};
105 }
106
107 const auto entries = resDir->entries();
108 QList<QString> res;
109 res.reserve(entries.size());
110 for (const auto &entry : entries) {
111 if (!entry.endsWith(".json"_L1)) {
112 continue;
113 }
114 res.push_back(entry.left(entry.size() - 5));
115 }
116
117 return res;
118}
119
121{
122 Q_ASSERT(d->zipFile);
123 const auto resDir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("reservations"_L1));
124 if (!resDir) {
125 return {};
126 }
127
128 const auto file = resDir->file(resId + ".json"_L1);
129 if (!file) {
130 qCDebug(Log) << "reservation not found" << resId;
131 return {};
132 }
133
134 const auto doc = QJsonDocument::fromJson(file->data());
135 if (doc.isArray()) {
136 const auto array = JsonLdDocument::fromJson(doc.array());
137 if (array.size() != 1) {
138 qCWarning(Log) << "reservation file for" << resId << "contains" << array.size() << "elements!";
139 return {};
140 }
141 return array.at(0);
142 }
143 if (doc.isObject()) {
144 return JsonLdDocument::fromJsonSingular(doc.object());
145 }
146 return {};
147}
148
153
154void File::addReservation(const QString &id, const QVariant &res)
155{
156 Q_ASSERT(d->zipFile);
157 d->zipFile->writeFile("reservations/"_L1 + id + ".json"_L1, QJsonDocument(JsonLdDocument::toJson(res)).toJson());
158}
159
161{
162 return passId(pass->passTypeIdentifier(), pass->serialNumber());
163}
164
165QString File::passId(const QString &passTypeIdenfier, const QString &serialNumber)
166{
167 if (passTypeIdenfier.isEmpty() || serialNumber.isEmpty()) {
168 return {};
169 }
170 // serialNumber can contain percent-encoding or slashes, ie stuff we don't want to have in file names
171 return passTypeIdenfier + '/'_L1 + QString::fromUtf8(serialNumber.toUtf8().toBase64(QByteArray::Base64UrlEncoding));
172}
173
174File::PkPassIdentifier File::decodePassId(QStringView passId)
175{
176 const auto idx = passId.lastIndexOf('/'_L1);
177 if (idx < 1 || idx >= passId.size() - 1) {
178 return {};
179 }
180
181 return { passId.left(idx).toString(), QString::fromUtf8(QByteArray::fromBase64(passId.mid(idx + 1).toUtf8(), QByteArray::Base64UrlEncoding)) };
182}
183
185 Q_ASSERT(d->zipFile);
186 const auto passDir = dynamic_cast<const KArchiveDirectory *>(
187 d->zipFile->directory()->entry("passes"_L1));
188 if (!passDir) {
189 return {};
190 }
191
192 const auto entries = passDir->entries();
193 QList<QString> passIds;
194 for (const auto &entry : entries) {
195 const auto subdir = dynamic_cast<const KArchiveDirectory*>(passDir->entry(entry));
196 if (!subdir) {
197 continue;
198 }
199
200 const auto subEntries = subdir->entries();
201 for (const auto &subEntry : subEntries) {
202 if (!subEntry.endsWith(".pkpass"_L1)) {
203 continue;
204 }
205 passIds.push_back(entry + '/'_L1 + QStringView(subEntry).left(subEntry.size() - 7));
206 }
207 }
208 return passIds;
209}
210
212{
213 Q_ASSERT(d->zipFile);
214 const auto passDir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("passes"_L1));
215 if (!passDir) {
216 return {};
217 }
218
219 const auto file = passDir->file(passId + ".pkpass"_L1);
220 if (!file) {
221 qCDebug(Log) << "pass not found" << passId;
222 return {};
223 }
224 return file->data();
225}
226
227void File::addPass(KPkPass::Pass* pass, const QByteArray& rawData)
228{
229 addPass(passId(pass), rawData);
230}
231
232void File::addPass(const QString &passId, const QByteArray& rawData)
233{
234 Q_ASSERT(d->zipFile);
235 d->zipFile->writeFile("passes/"_L1 + passId + ".pkpass"_L1, rawData);
236}
237
239{
240 const auto docDir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("documents"_L1));
241 if (!docDir) {
242 return {};
243 }
244
245 const auto entries = docDir->entries();
246 QList<QString> res;
247 res.reserve(entries.size());
248 for (const auto &entry : entries) {
249 if (docDir->entry(entry)->isDirectory()) {
250 res.push_back(entry);
251 }
252 }
253
254 return res;
255}
256
258{
259 Q_ASSERT(d->zipFile);
260 const auto dir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("documents/"_L1 + id));
261 if (!dir) {
262 return {};
263 }
264
265 const auto file = dir->file("meta.json"_L1);
266 if (!file) {
267 qCDebug(Log) << "document meta data not found" << id;
268 return {};
269 }
270
271 const auto doc = QJsonDocument::fromJson(file->data());
272 if (doc.isArray()) {
273 const auto array = JsonLdDocument::fromJson(doc.array());
274 if (array.size() != 1) {
275 qCWarning(Log) << "document meta data for" << id << "contains" << array.size() << "elements!";
276 return {};
277 }
278 return array.at(0);
279 }
280 if (doc.isObject()) {
281 return JsonLdDocument::fromJsonSingular(doc.object());
282 }
283 return {};
284}
285
287{
288 const auto meta = documentInfo(id);
290 return {};
291 }
292 const auto fileName = JsonLd::convert<CreativeWork>(meta).name();
293
294 const auto dir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("documents/"_L1 + id));
295 Q_ASSERT(dir); // checked by documentInfo already
296 const auto file = dir->file(fileName);
297 if (!file) {
298 qCWarning(Log) << "document data not found" << id << fileName;
299 return {};
300 }
301 return file->data();
302}
303
305{
306 auto fileName = name;
307 // normalize the filename to something we can safely deal with
308 auto idx = fileName.lastIndexOf('/'_L1);
309 if (idx >= 0) {
310 fileName = fileName.mid(idx + 1);
311 }
312 fileName.replace('?'_L1, '_'_L1);
313 fileName.replace('*'_L1, '_'_L1);
314 fileName.replace(' '_L1, '_'_L1);
315 fileName.replace('\\'_L1, '_'_L1);
316 if (fileName.isEmpty() || fileName == "meta.json"_L1) {
317 fileName = "file"_L1;
318 }
319 return fileName;
320}
321
322void File::addDocument(const QString &id, const QVariant &docInfo, const QByteArray &docData)
323{
324 Q_ASSERT(d->zipFile);
325 if (!JsonLd::canConvert<CreativeWork>(docInfo)) {
326 qCWarning(Log) << "Invalid document meta data" << docInfo;
327 return;
328 }
329 if (id.isEmpty()) {
330 qCWarning(Log) << "Trying to add a document with an empty identifier!";
331 return;
332 }
333
334 const auto fileName = normalizeDocumentFileName(JsonLdDocument::readProperty(docInfo, "name").toString());
335 auto normalizedDocInfo = docInfo;
336 JsonLdDocument::writeProperty(normalizedDocInfo, "name", fileName);
337
338 d->zipFile->writeFile("documents/"_L1 + id + "/meta.json"_L1, QJsonDocument(JsonLdDocument::toJson(normalizedDocInfo)).toJson());
339 d->zipFile->writeFile("documents/"_L1 + id + '/'_L1 + fileName, docData);
340}
341
343{
344 Q_ASSERT(d->zipFile);
345 const auto dir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("custom/"_L1 + scope));
346 if (!dir) {
347 return {};
348 }
349
350 const auto entries = dir->entries();
351 QList<QString> res;
352 res.reserve(entries.size());
353 std::copy(entries.begin(), entries.end(), std::back_inserter(res));
354 return res;
355}
356
357bool File::hasCustomData(QStringView scope, const QString &id) const
358{
359 Q_ASSERT(d->zipFile);
360 const auto dir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("custom/"_L1 + scope));
361 if (!dir) {
362 return false;
363 }
364
365 return dir->file(id);
366}
367
369{
370 Q_ASSERT(d->zipFile);
371 const auto dir = dynamic_cast<const KArchiveDirectory *>(d->zipFile->directory()->entry("custom/"_L1 + scope));
372 if (!dir) {
373 return {};
374 }
375
376 const auto file = dir->file(id);
377 if (!file) {
378 qCDebug(Log) << "custom data not found" << scope << id;
379 return {};
380 }
381 return file->data();
382}
383
384void File::addCustomData(QStringView scope, const QString &id, const QByteArray &data)
385{
386 Q_ASSERT(d->zipFile);
387 d->zipFile->writeFile("custom/"_L1 + scope + '/'_L1 + id, data);
388}
QStringList entries() const
const KArchiveFile * file(const QString &name) const
virtual QByteArray data() const
A file containing a bundle of reservations and associated documents.
Definition file.h:38
void addDocument(const QString &id, const QVariant &docInfo, const QByteArray &docData)
Adds a document and associated meta data to the file.
Definition file.cpp:322
QByteArray passData(const QString &passId) const
Pass data for the given pass id.
Definition file.cpp:211
static QString passId(const KPkPass::Pass *pass)
Returns the pass identifier used in here for pass.
Definition file.cpp:160
static QString normalizeDocumentFileName(const QString &name)
Makes sure the resulting file name is something that can safely be used without messing up the file s...
Definition file.cpp:304
QList< QString > reservations() const
Lists the identifiers of all reservations in this file.
Definition file.cpp:100
bool open(OpenMode mode) const
Open the file for reading or writing.
Definition file.cpp:68
QVariant reservation(const QString &resId) const
Loads the reservation with the given identifier.
Definition file.cpp:120
void addPass(KPkPass::Pass *pass, const QByteArray &rawData)
Add a pkpass file to this file.
Definition file.cpp:227
void addReservation(const QVariant &res)
Add a reservation to this file.
Definition file.cpp:149
QString errorString() const
Error message in case opening the file failed.
Definition file.cpp:84
bool hasCustomData(QStringView scope, const QString &id) const
Returns true if custom data with the given id exists in scope.
Definition file.cpp:357
void close()
Save and close the file.
Definition file.cpp:92
QByteArray customData(QStringView scope, const QString &id) const
Returns the custom data in the given namespace and with the given id.
Definition file.cpp:368
QVariant documentInfo(const QString &id) const
Loads the document meta data of document id.
Definition file.cpp:257
QList< QString > documents() const
Lists all document identifiers.
Definition file.cpp:238
QList< QString > passes() const
Lists all pkpass files in this file.
Definition file.cpp:184
void setFileName(const QString &fileName)
Sets the file name.
Definition file.cpp:63
QByteArray documentData(const QString &id) const
Loads the content of document id.
Definition file.cpp:286
QList< QString > listCustomData(QStringView scope) const
List custom data in the given namespace.
Definition file.cpp:342
void addCustomData(QStringView scope, const QString &id, const QByteArray &data)
Adds a custom data element with identifier id in to namespace scope.
Definition file.cpp:384
static void writeProperty(QVariant &obj, const char *name, const QVariant &value)
Set property name on object obj to value value.
static QVariant readProperty(const QVariant &obj, const char *name)
Read property name on object obj.
static QJsonArray toJson(const QList< QVariant > &data)
Serialize instantiated data types to JSON-LD.
static QList< QVariant > fromJson(const QJsonArray &array)
Convert JSON-LD array into instantiated data types.
static QVariant fromJsonSingular(const QJsonObject &obj)
Convert a single JSON-LD object into an instantiated data type.
bool canConvert(const QVariant &value)
Checks if the given value can be up-cast to T.
Definition datatypes.h:31
T convert(const QVariant &value)
Up-cast value to T.
Definition datatypes.h:47
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
QByteArray toBase64(Base64Options options) const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
void push_back(parameter_type value)
void reserve(qsizetype size)
QChar * data()
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype size() const const
QByteArray toUtf8() const const
QTextStream & left(QTextStream &stream)
QUuid createUuid()
Decodes an identifier returned by passId() again.
Definition file.h:77
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:52:35 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.