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
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
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
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
qsizetype size() const const
QByteArray toUtf8() const const
QUuid createUuid()
Decodes an identifier returned by passId() again.
Definition file.h:77
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:09:58 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.