Warning, file /pim/kitinerary/src/lib/file.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "file.h" 0008 #include "jsonlddocument.h" 0009 #include "logging.h" 0010 0011 #include <KItinerary/CreativeWork> 0012 0013 #include <KPkPass/Pass> 0014 0015 #include <KZip> 0016 0017 #include <QDebug> 0018 #include <QJsonArray> 0019 #include <QJsonDocument> 0020 #include <QJsonObject> 0021 #include <QString> 0022 #include <QUuid> 0023 0024 using namespace KItinerary; 0025 0026 namespace KItinerary { 0027 class FilePrivate 0028 { 0029 public: 0030 QString fileName; 0031 QIODevice *device = nullptr; 0032 std::unique_ptr<KZip> zipFile; 0033 }; 0034 } 0035 0036 File::File() 0037 : d(new FilePrivate) 0038 { 0039 } 0040 0041 File::File(const QString &fileName) 0042 : d(new FilePrivate) 0043 { 0044 d->fileName = fileName; 0045 } 0046 0047 File::File(QIODevice* device) 0048 : d(new FilePrivate) 0049 { 0050 d->device = device; 0051 } 0052 0053 File::File(KItinerary::File &&) = default; 0054 0055 File::~File() 0056 { 0057 close(); 0058 } 0059 0060 File& KItinerary::File::operator=(KItinerary::File &&) = default; 0061 0062 void File::setFileName(const QString &fileName) 0063 { 0064 d->fileName = fileName; 0065 } 0066 0067 bool File::open(File::OpenMode mode) const 0068 { 0069 if (d->device) { 0070 d->zipFile = std::make_unique<KZip>(d->device); 0071 } else { 0072 d->zipFile = std::make_unique<KZip>(d->fileName); 0073 } 0074 0075 if (!d->zipFile->open(mode == File::Write ? QIODevice::WriteOnly : QIODevice::ReadOnly)) { 0076 qCWarning(Log) << d->zipFile->errorString() << d->fileName; 0077 return false; 0078 } 0079 0080 return true; 0081 } 0082 0083 QString File::errorString() const 0084 { 0085 if (d->zipFile && !d->zipFile->isOpen()) { 0086 return d->zipFile->errorString(); 0087 } 0088 return {}; 0089 } 0090 0091 void File::close() 0092 { 0093 if (d->zipFile) { 0094 d->zipFile->close(); 0095 } 0096 d->zipFile.reset(); 0097 } 0098 0099 QList<QString> File::reservations() const { 0100 Q_ASSERT(d->zipFile); 0101 const auto resDir = dynamic_cast<const KArchiveDirectory *>( 0102 d->zipFile->directory()->entry(QLatin1StringView("reservations"))); 0103 if (!resDir) { 0104 return {}; 0105 } 0106 0107 const auto entries = resDir->entries(); 0108 QList<QString> res; 0109 res.reserve(entries.size()); 0110 for (const auto &entry : entries) { 0111 if (!entry.endsWith(QLatin1StringView(".json"))) { 0112 continue; 0113 } 0114 res.push_back(entry.left(entry.size() - 5)); 0115 } 0116 0117 return res; 0118 } 0119 0120 QVariant File::reservation(const QString &resId) const 0121 { 0122 Q_ASSERT(d->zipFile); 0123 const auto resDir = dynamic_cast<const KArchiveDirectory *>( 0124 d->zipFile->directory()->entry(QLatin1StringView("reservations"))); 0125 if (!resDir) { 0126 return {}; 0127 } 0128 0129 const auto file = resDir->file(resId + QLatin1StringView(".json")); 0130 if (!file) { 0131 qCDebug(Log) << "reservation not found" << resId; 0132 return {}; 0133 } 0134 0135 const auto doc = QJsonDocument::fromJson(file->data()); 0136 if (doc.isArray()) { 0137 const auto array = JsonLdDocument::fromJson(doc.array()); 0138 if (array.size() != 1) { 0139 qCWarning(Log) << "reservation file for" << resId << "contains" << array.size() << "elements!"; 0140 return {}; 0141 } 0142 return array.at(0); 0143 } else if (doc.isObject()) { 0144 return JsonLdDocument::fromJsonSingular(doc.object()); 0145 } 0146 return {}; 0147 } 0148 0149 void File::addReservation(const QVariant &res) 0150 { 0151 addReservation(QUuid::createUuid().toString(QUuid::WithoutBraces), res); 0152 } 0153 0154 void File::addReservation(const QString &id, const QVariant &res) 0155 { 0156 Q_ASSERT(d->zipFile); 0157 d->zipFile->writeFile(QLatin1StringView("reservations/") + id + 0158 QLatin1String(".json"), 0159 QJsonDocument(JsonLdDocument::toJson(res)).toJson()); 0160 } 0161 0162 QString File::passId(const KPkPass::Pass *pass) 0163 { 0164 return passId(pass->passTypeIdentifier(), pass->serialNumber()); 0165 } 0166 0167 QString File::passId(const QString &passTypeIdenfier, const QString &serialNumber) 0168 { 0169 if (passTypeIdenfier.isEmpty() || serialNumber.isEmpty()) { 0170 return {}; 0171 } 0172 // serialNumber can contain percent-encoding or slashes, ie stuff we don't want to have in file names 0173 return passTypeIdenfier + QLatin1Char('/') + QString::fromUtf8(serialNumber.toUtf8().toBase64(QByteArray::Base64UrlEncoding)); 0174 } 0175 0176 QList<QString> File::passes() const { 0177 Q_ASSERT(d->zipFile); 0178 const auto passDir = dynamic_cast<const KArchiveDirectory *>( 0179 d->zipFile->directory()->entry(QLatin1StringView("passes"))); 0180 if (!passDir) { 0181 return {}; 0182 } 0183 0184 const auto entries = passDir->entries(); 0185 QList<QString> passIds; 0186 for (const auto &entry : entries) { 0187 const auto subdir = dynamic_cast<const KArchiveDirectory*>(passDir->entry(entry)); 0188 if (!subdir) { 0189 continue; 0190 } 0191 0192 const auto subEntries = subdir->entries(); 0193 for (const auto &subEntry : subEntries) { 0194 if (!subEntry.endsWith(QLatin1StringView(".pkpass"))) { 0195 continue; 0196 } 0197 passIds.push_back(entry + QLatin1Char('/') + QStringView(subEntry).left(subEntry.size() - 7)); 0198 } 0199 } 0200 return passIds; 0201 } 0202 0203 QByteArray File::passData(const QString& passId) const 0204 { 0205 Q_ASSERT(d->zipFile); 0206 const auto passDir = dynamic_cast<const KArchiveDirectory *>( 0207 d->zipFile->directory()->entry(QLatin1StringView("passes"))); 0208 if (!passDir) { 0209 return {}; 0210 } 0211 0212 const auto file = passDir->file(passId + QLatin1StringView(".pkpass")); 0213 if (!file) { 0214 qCDebug(Log) << "pass not found" << passId; 0215 return {}; 0216 } 0217 return file->data(); 0218 } 0219 0220 void File::addPass(KPkPass::Pass* pass, const QByteArray& rawData) 0221 { 0222 addPass(passId(pass), rawData); 0223 } 0224 0225 void File::addPass(const QString &passId, const QByteArray& rawData) 0226 { 0227 Q_ASSERT(d->zipFile); 0228 d->zipFile->writeFile(QLatin1StringView("passes/") + passId + 0229 QLatin1String(".pkpass"), 0230 rawData); 0231 } 0232 0233 QList<QString> File::documents() const { 0234 const auto docDir = dynamic_cast<const KArchiveDirectory *>( 0235 d->zipFile->directory()->entry(QLatin1StringView("documents"))); 0236 if (!docDir) { 0237 return {}; 0238 } 0239 0240 const auto entries = docDir->entries(); 0241 QList<QString> res; 0242 res.reserve(entries.size()); 0243 for (const auto &entry : entries) { 0244 if (docDir->entry(entry)->isDirectory()) { 0245 res.push_back(entry); 0246 } 0247 } 0248 0249 return res; 0250 } 0251 0252 QVariant File::documentInfo(const QString &id) const 0253 { 0254 Q_ASSERT(d->zipFile); 0255 const auto dir = dynamic_cast<const KArchiveDirectory *>( 0256 d->zipFile->directory()->entry(QLatin1StringView("documents/") + id)); 0257 if (!dir) { 0258 return {}; 0259 } 0260 0261 const auto file = dir->file(QStringLiteral("meta.json")); 0262 if (!file) { 0263 qCDebug(Log) << "document meta data not found" << id; 0264 return {}; 0265 } 0266 0267 const auto doc = QJsonDocument::fromJson(file->data()); 0268 if (doc.isArray()) { 0269 const auto array = JsonLdDocument::fromJson(doc.array()); 0270 if (array.size() != 1) { 0271 qCWarning(Log) << "document meta data for" << id << "contains" << array.size() << "elements!"; 0272 return {}; 0273 } 0274 return array.at(0); 0275 } else if (doc.isObject()) { 0276 return JsonLdDocument::fromJsonSingular(doc.object()); 0277 } 0278 return {}; 0279 } 0280 0281 QByteArray File::documentData(const QString &id) const 0282 { 0283 const auto meta = documentInfo(id); 0284 if (!JsonLd::canConvert<CreativeWork>(meta)) { 0285 return {}; 0286 } 0287 const auto fileName = JsonLd::convert<CreativeWork>(meta).name(); 0288 0289 const auto dir = dynamic_cast<const KArchiveDirectory *>( 0290 d->zipFile->directory()->entry(QLatin1StringView("documents/") + id)); 0291 Q_ASSERT(dir); // checked by documentInfo already 0292 const auto file = dir->file(fileName); 0293 if (!file) { 0294 qCWarning(Log) << "document data not found" << id << fileName; 0295 return {}; 0296 } 0297 return file->data(); 0298 } 0299 0300 QString File::normalizeDocumentFileName(const QString &name) 0301 { 0302 auto fileName = name; 0303 // normalize the filename to something we can safely deal with 0304 auto idx = fileName.lastIndexOf(QLatin1Char('/')); 0305 if (idx >= 0) { 0306 fileName = fileName.mid(idx + 1); 0307 } 0308 fileName.replace(QLatin1Char('?'), QLatin1Char('_')); 0309 fileName.replace(QLatin1Char('*'), QLatin1Char('_')); 0310 fileName.replace(QLatin1Char(' '), QLatin1Char('_')); 0311 fileName.replace(QLatin1Char('\\'), QLatin1Char('_')); 0312 if (fileName.isEmpty() || fileName == QLatin1StringView("meta.json")) { 0313 fileName = QStringLiteral("file"); 0314 } 0315 return fileName; 0316 } 0317 0318 void File::addDocument(const QString &id, const QVariant &docInfo, const QByteArray &docData) 0319 { 0320 Q_ASSERT(d->zipFile); 0321 if (!JsonLd::canConvert<CreativeWork>(docInfo)) { 0322 qCWarning(Log) << "Invalid document meta data" << docInfo; 0323 return; 0324 } 0325 if (id.isEmpty()) { 0326 qCWarning(Log) << "Trying to add a document with an empty identifier!"; 0327 return; 0328 } 0329 0330 const auto fileName = normalizeDocumentFileName(JsonLdDocument::readProperty(docInfo, "name").toString()); 0331 auto normalizedDocInfo = docInfo; 0332 JsonLdDocument::writeProperty(normalizedDocInfo, "name", fileName); 0333 0334 d->zipFile->writeFile( 0335 QLatin1StringView("documents/") + id + QLatin1String("/meta.json"), 0336 QJsonDocument(JsonLdDocument::toJson(normalizedDocInfo)).toJson()); 0337 d->zipFile->writeFile(QLatin1StringView("documents/") + id + 0338 QLatin1Char('/') + fileName, 0339 docData); 0340 } 0341 0342 QList<QString> File::listCustomData(const QString &scope) const { 0343 Q_ASSERT(d->zipFile); 0344 const auto dir = dynamic_cast<const KArchiveDirectory *>( 0345 d->zipFile->directory()->entry(QLatin1StringView("custom/") + scope)); 0346 if (!dir) { 0347 return {}; 0348 } 0349 0350 const auto entries = dir->entries(); 0351 QList<QString> res; 0352 res.reserve(entries.size()); 0353 std::copy(entries.begin(), entries.end(), std::back_inserter(res)); 0354 return res; 0355 } 0356 0357 QByteArray File::customData(const QString& scope, const QString &id) const 0358 { 0359 Q_ASSERT(d->zipFile); 0360 const auto dir = dynamic_cast<const KArchiveDirectory *>( 0361 d->zipFile->directory()->entry(QLatin1StringView("custom/") + scope)); 0362 if (!dir) { 0363 return {}; 0364 } 0365 0366 const auto file = dir->file(id); 0367 if (!file) { 0368 qCDebug(Log) << "custom data not found" << scope << id; 0369 return {}; 0370 } 0371 return file->data(); 0372 } 0373 0374 void File::addCustomData(const QString &scope, const QString &id, const QByteArray &data) 0375 { 0376 Q_ASSERT(d->zipFile); 0377 d->zipFile->writeFile( 0378 QLatin1StringView("custom/") + scope + QLatin1Char('/') + id, data); 0379 }