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 KItinerary;
25
26namespace KItinerary {
27class FilePrivate
28{
29public:
30 QString fileName;
31 QIODevice *device = nullptr;
32 std::unique_ptr<KZip> zipFile;
33};
34}
35
36File::File()
37 : d(new FilePrivate)
38{
39}
40
41File::File(const QString &fileName)
42 : d(new FilePrivate)
43{
44 d->fileName = fileName;
45}
46
47File::File(QIODevice* device)
48 : d(new FilePrivate)
49{
50 d->device = device;
51}
52
53File::File(KItinerary::File &&) = default;
54
55File::~File()
56{
57 close();
58}
59
60File& KItinerary::File::operator=(KItinerary::File &&) = default;
61
62void File::setFileName(const QString &fileName)
63{
64 d->fileName = fileName;
65}
66
67bool File::open(File::OpenMode mode) const
68{
69 if (d->device) {
70 d->zipFile = std::make_unique<KZip>(d->device);
71 } else {
72 d->zipFile = std::make_unique<KZip>(d->fileName);
73 }
74
75 if (!d->zipFile->open(mode == File::Write ? QIODevice::WriteOnly : QIODevice::ReadOnly)) {
76 qCWarning(Log) << d->zipFile->errorString() << d->fileName;
77 return false;
78 }
79
80 return true;
81}
82
84{
85 if (d->zipFile && !d->zipFile->isOpen()) {
86 return d->zipFile->errorString();
87 }
88 return {};
89}
90
92{
93 if (d->zipFile) {
94 d->zipFile->close();
95 }
96 d->zipFile.reset();
97}
98
100 Q_ASSERT(d->zipFile);
101 const auto resDir = dynamic_cast<const KArchiveDirectory *>(
102 d->zipFile->directory()->entry(QLatin1StringView("reservations")));
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(QLatin1StringView(".json"))) {
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 *>(
124 d->zipFile->directory()->entry(QLatin1StringView("reservations")));
125 if (!resDir) {
126 return {};
127 }
128
129 const auto file = resDir->file(resId + QLatin1StringView(".json"));
130 if (!file) {
131 qCDebug(Log) << "reservation not found" << resId;
132 return {};
133 }
134
135 const auto doc = QJsonDocument::fromJson(file->data());
136 if (doc.isArray()) {
137 const auto array = JsonLdDocument::fromJson(doc.array());
138 if (array.size() != 1) {
139 qCWarning(Log) << "reservation file for" << resId << "contains" << array.size() << "elements!";
140 return {};
141 }
142 return array.at(0);
143 } else 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(QLatin1StringView("reservations/") + id +
158 QLatin1String(".json"),
160}
161
163{
164 return passId(pass->passTypeIdentifier(), pass->serialNumber());
165}
166
167QString File::passId(const QString &passTypeIdenfier, const QString &serialNumber)
168{
169 if (passTypeIdenfier.isEmpty() || serialNumber.isEmpty()) {
170 return {};
171 }
172 // serialNumber can contain percent-encoding or slashes, ie stuff we don't want to have in file names
173 return passTypeIdenfier + QLatin1Char('/') + QString::fromUtf8(serialNumber.toUtf8().toBase64(QByteArray::Base64UrlEncoding));
174}
175
177 Q_ASSERT(d->zipFile);
178 const auto passDir = dynamic_cast<const KArchiveDirectory *>(
179 d->zipFile->directory()->entry(QLatin1StringView("passes")));
180 if (!passDir) {
181 return {};
182 }
183
184 const auto entries = passDir->entries();
185 QList<QString> passIds;
186 for (const auto &entry : entries) {
187 const auto subdir = dynamic_cast<const KArchiveDirectory*>(passDir->entry(entry));
188 if (!subdir) {
189 continue;
190 }
191
192 const auto subEntries = subdir->entries();
193 for (const auto &subEntry : subEntries) {
194 if (!subEntry.endsWith(QLatin1StringView(".pkpass"))) {
195 continue;
196 }
197 passIds.push_back(entry + QLatin1Char('/') + QStringView(subEntry).left(subEntry.size() - 7));
198 }
199 }
200 return passIds;
201}
202
204{
205 Q_ASSERT(d->zipFile);
206 const auto passDir = dynamic_cast<const KArchiveDirectory *>(
207 d->zipFile->directory()->entry(QLatin1StringView("passes")));
208 if (!passDir) {
209 return {};
210 }
211
212 const auto file = passDir->file(passId + QLatin1StringView(".pkpass"));
213 if (!file) {
214 qCDebug(Log) << "pass not found" << passId;
215 return {};
216 }
217 return file->data();
218}
219
220void File::addPass(KPkPass::Pass* pass, const QByteArray& rawData)
221{
222 addPass(passId(pass), rawData);
223}
224
225void File::addPass(const QString &passId, const QByteArray& rawData)
226{
227 Q_ASSERT(d->zipFile);
228 d->zipFile->writeFile(QLatin1StringView("passes/") + passId +
229 QLatin1String(".pkpass"),
230 rawData);
231}
232
234 const auto docDir = dynamic_cast<const KArchiveDirectory *>(
235 d->zipFile->directory()->entry(QLatin1StringView("documents")));
236 if (!docDir) {
237 return {};
238 }
239
240 const auto entries = docDir->entries();
241 QList<QString> res;
242 res.reserve(entries.size());
243 for (const auto &entry : entries) {
244 if (docDir->entry(entry)->isDirectory()) {
245 res.push_back(entry);
246 }
247 }
248
249 return res;
250}
251
253{
254 Q_ASSERT(d->zipFile);
255 const auto dir = dynamic_cast<const KArchiveDirectory *>(
256 d->zipFile->directory()->entry(QLatin1StringView("documents/") + id));
257 if (!dir) {
258 return {};
259 }
260
261 const auto file = dir->file(QStringLiteral("meta.json"));
262 if (!file) {
263 qCDebug(Log) << "document meta data not found" << id;
264 return {};
265 }
266
267 const auto doc = QJsonDocument::fromJson(file->data());
268 if (doc.isArray()) {
269 const auto array = JsonLdDocument::fromJson(doc.array());
270 if (array.size() != 1) {
271 qCWarning(Log) << "document meta data for" << id << "contains" << array.size() << "elements!";
272 return {};
273 }
274 return array.at(0);
275 } else if (doc.isObject()) {
276 return JsonLdDocument::fromJsonSingular(doc.object());
277 }
278 return {};
279}
280
282{
283 const auto meta = documentInfo(id);
285 return {};
286 }
287 const auto fileName = JsonLd::convert<CreativeWork>(meta).name();
288
289 const auto dir = dynamic_cast<const KArchiveDirectory *>(
290 d->zipFile->directory()->entry(QLatin1StringView("documents/") + id));
291 Q_ASSERT(dir); // checked by documentInfo already
292 const auto file = dir->file(fileName);
293 if (!file) {
294 qCWarning(Log) << "document data not found" << id << fileName;
295 return {};
296 }
297 return file->data();
298}
299
301{
302 auto fileName = name;
303 // normalize the filename to something we can safely deal with
304 auto idx = fileName.lastIndexOf(QLatin1Char('/'));
305 if (idx >= 0) {
306 fileName = fileName.mid(idx + 1);
307 }
308 fileName.replace(QLatin1Char('?'), QLatin1Char('_'));
309 fileName.replace(QLatin1Char('*'), QLatin1Char('_'));
310 fileName.replace(QLatin1Char(' '), QLatin1Char('_'));
311 fileName.replace(QLatin1Char('\\'), QLatin1Char('_'));
312 if (fileName.isEmpty() || fileName == QLatin1StringView("meta.json")) {
313 fileName = QStringLiteral("file");
314 }
315 return fileName;
316}
317
318void File::addDocument(const QString &id, const QVariant &docInfo, const QByteArray &docData)
319{
320 Q_ASSERT(d->zipFile);
321 if (!JsonLd::canConvert<CreativeWork>(docInfo)) {
322 qCWarning(Log) << "Invalid document meta data" << docInfo;
323 return;
324 }
325 if (id.isEmpty()) {
326 qCWarning(Log) << "Trying to add a document with an empty identifier!";
327 return;
328 }
329
330 const auto fileName = normalizeDocumentFileName(JsonLdDocument::readProperty(docInfo, "name").toString());
331 auto normalizedDocInfo = docInfo;
332 JsonLdDocument::writeProperty(normalizedDocInfo, "name", fileName);
333
334 d->zipFile->writeFile(
335 QLatin1StringView("documents/") + id + QLatin1String("/meta.json"),
336 QJsonDocument(JsonLdDocument::toJson(normalizedDocInfo)).toJson());
337 d->zipFile->writeFile(QLatin1StringView("documents/") + id +
338 QLatin1Char('/') + fileName,
339 docData);
340}
341
343 Q_ASSERT(d->zipFile);
344 const auto dir = dynamic_cast<const KArchiveDirectory *>(
345 d->zipFile->directory()->entry(QLatin1StringView("custom/") + 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
357QByteArray File::customData(const QString& scope, const QString &id) const
358{
359 Q_ASSERT(d->zipFile);
360 const auto dir = dynamic_cast<const KArchiveDirectory *>(
361 d->zipFile->directory()->entry(QLatin1StringView("custom/") + scope));
362 if (!dir) {
363 return {};
364 }
365
366 const auto file = dir->file(id);
367 if (!file) {
368 qCDebug(Log) << "custom data not found" << scope << id;
369 return {};
370 }
371 return file->data();
372}
373
374void File::addCustomData(const QString &scope, const QString &id, const QByteArray &data)
375{
376 Q_ASSERT(d->zipFile);
377 d->zipFile->writeFile(
378 QLatin1StringView("custom/") + scope + QLatin1Char('/') + id, data);
379}
QStringList entries() const
const KArchiveFile * file(const QString &name) 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:318
QByteArray passData(const QString &passId) const
Pass data for the given pass id.
Definition file.cpp:203
static QString passId(const KPkPass::Pass *pass)
Returns the pass identifier used in here for pass.
Definition file.cpp:162
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:300
QList< QString > reservations() const
Lists the identifiers of all reservations in this file.
Definition file.cpp:99
bool open(OpenMode mode) const
Open the file for reading or writing.
Definition file.cpp:67
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:220
void addReservation(const QVariant &res)
Add a reservation to this file.
Definition file.cpp:149
QByteArray customData(const QString &scope, const QString &id) const
Returns the custom data in the given namespace and with the given id.
Definition file.cpp:357
QString errorString() const
Error message in case opening the file failed.
Definition file.cpp:83
void close()
Save and close the file.
Definition file.cpp:91
void addCustomData(const QString &scope, const QString &id, const QByteArray &data)
Adds a custom data element with identifier id in to namespace scope.
Definition file.cpp:374
QVariant documentInfo(const QString &id) const
Loads the document meta data of document id.
Definition file.cpp:252
QList< QString > documents() const
Lists all document identifiers.
Definition file.cpp:233
QList< QString > passes() const
Lists all pkpass files in this file.
Definition file.cpp:176
void setFileName(const QString &fileName)
Sets the file name.
Definition file.cpp:62
QByteArray documentData(const QString &id) const
Loads the content of document id.
Definition file.cpp:281
QList< QString > listCustomData(const QString &scope) const
List custom data in the given namespace.
Definition file.cpp:342
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
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
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 size() const const
QByteArray toUtf8() const const
QStringView left(qsizetype length) const const
QUuid createUuid()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:49 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.