Krita Source Code Documentation
Loading...
Searching...
No Matches
KisResourceStorage.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
9
10#include <QApplication>
11#include <QDebug>
12#include <QFileInfo>
13#include <QUuid>
14#include <QtMath>
15#include <QRegularExpression>
16
17#include <cmath>
18#include <quazip.h>
19#include <boost/optional.hpp>
20
21#include <kis_debug.h>
22#include <kis_pointer_utils.h>
23
24
25#include "KisFolderStorage.h"
26#include "KisBundleStorage.h"
27#include "KisMemoryStorage.h"
28
29
30const QString KisResourceStorage::s_xmlns_meta("urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
31const QString KisResourceStorage::s_xmlns_dc("http://purl.org/dc/elements/1.1");
32
33const QString KisResourceStorage::s_meta_generator("meta:generator");
34const QString KisResourceStorage::s_meta_author("dc:author");
35const QString KisResourceStorage::s_meta_title("dc:title");
36const QString KisResourceStorage::s_meta_description("dc:description");
37const QString KisResourceStorage::s_meta_initial_creator("meta:initial-creator");
38const QString KisResourceStorage::s_meta_creator("dc:creator");
39const QString KisResourceStorage::s_meta_creation_date("meta:creation-date");
40const QString KisResourceStorage::s_meta_dc_date("meta:dc-date");
41const QString KisResourceStorage::s_meta_user_defined("meta:meta-userdefined");
42const QString KisResourceStorage::s_meta_name("meta:name");
43const QString KisResourceStorage::s_meta_value("meta:value");
44const QString KisResourceStorage::s_meta_version("meta:bundle-version");
45const QString KisResourceStorage::s_meta_email("meta:email");
46const QString KisResourceStorage::s_meta_license("meta:license");
47const QString KisResourceStorage::s_meta_website("meta:website");
48
50
57
62
67
72
77
87
89 : d(new Private())
90{
91 d->location = location;
92 d->name = QFileInfo(d->location).fileName();
93
94 QFileInfo fi(d->location);
95 if (fi.isDir()) {
96 d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Folder]->create(location));
97 d->storageType = StorageType::Folder;
98 d->valid = fi.isWritable();
99 }
100 else if (d->name.endsWith(".bundle", Qt::CaseInsensitive)) {
101 d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Bundle]->create(location));
102 d->storageType = StorageType::Bundle;
103 // XXX: should we also check whether there's a valid metadata entry? Or is this enough?
104 d->valid = (fi.isReadable() && QuaZip(d->location).open(QuaZip::mdUnzip));
105 }
106 else if (d->name.endsWith(".abr", Qt::CaseInsensitive)) {
108 d->storageType = StorageType::AdobeBrushLibrary;
109 d->valid = fi.isReadable();
110 }
111 else if (d->name.endsWith(".asl", Qt::CaseInsensitive)) {
113 d->storageType = StorageType::AdobeStyleLibrary;
114 d->valid = d->storagePlugin->isValid();
115 }
116 else if (d->location == "fontregistry") {
118 if (factory) {
119 auto storage = factory->create(location);
120 d->storagePlugin.reset(storage);
121 d->valid = true;
122 }
123 else {
124 d->valid = false;
125 }
126 d->name = location;
127 d->storageType = StorageType::FontStorage;
128
129 }
130 else if (d->location == "memory" || !QUuid::fromString(d->location).isNull() || (!d->location.isEmpty() && !fi.exists())) {
131 d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Memory]->create(location));
132 d->name = location;
133 d->storageType = StorageType::Memory;
134 d->valid = true;
135 } else {
136 // we create a fake memory storage to make sure methods like `timestamp()` still work
137 d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Memory]->create(location));
138 d->valid = false;
139 }
140}
141
145
147 : d(new Private)
148{
149 *this = rhs;
150}
151
153{
154 if (this != &rhs) {
155 d->name = rhs.d->name;
156 d->location = rhs.d->location;
157 d->storageType = rhs.d->storageType;
158 if (d->storageType == StorageType::Memory) {
159 const QSharedPointer<KisMemoryStorage> memoryStorage = rhs.d->storagePlugin.dynamicCast<KisMemoryStorage>();
160 KIS_ASSERT(memoryStorage);
161 d->storagePlugin = QSharedPointer<KisMemoryStorage>(new KisMemoryStorage(*memoryStorage));
162 }
163 else {
164 d->storagePlugin = rhs.d->storagePlugin;
165 }
166 d->valid = false;
167 }
168 return *this;
169}
170
175
177{
178 return d->name;
179}
180
182{
183 return d->location;
184}
185
187{
188 return d->storageType;
189}
190
192{
193 return d->storagePlugin->thumbnail();
194}
195
197{
198 return d->storagePlugin->timestamp();
199}
200
201QDateTime KisResourceStorage::timeStampForResource(const QString &resourceType, const QString &filename) const
202{
203 QFileInfo li(d->location);
204 if (li.suffix().toLower() == "bundle") {
205 QFileInfo bf(d->location + "_modified/" + resourceType + "/" + filename);
206 if (bf.exists()) {
207 return bf.lastModified();
208 }
209 } else if (QFileInfo(d->location + "/" + resourceType + "/" + filename).exists()) {
210 return QFileInfo(d->location + "/" + resourceType + "/" + filename).lastModified();
211 }
212 return this->timestamp();
213}
214
216{
217 return d->storagePlugin->resourceItem(url);
218}
219
221{
222 return d->storagePlugin->resource(url);
223}
224
225QString KisResourceStorage::resourceMd5(const QString &url)
226{
227 return d->storagePlugin->resourceMd5(url);
228}
229
230QString KisResourceStorage::resourceFilePath(const QString &url)
231{
232 return d->storagePlugin->resourceFilePath(url);
233}
234
236{
237 return d->storagePlugin->resources(resourceType);
238}
239
241{
242 return d->storagePlugin->tags(resourceType);
243}
244
246{
247 if (!resource) return false;
248
249 return d->storagePlugin->saveAsNewVersion(resource->resourceType().first, resource);
250}
251
253{
254 if (!resource) return false;
255
256 return d->storagePlugin->addResource(resource->resourceType().first, resource);
257}
258
259bool KisResourceStorage::importResource(const QString &url, QIODevice *device)
260{
261 return d->storagePlugin->importResource(url, device);
262}
263
264bool KisResourceStorage::exportResource(const QString &url, QIODevice *device)
265{
266 return d->storagePlugin->exportResource(url, device);
267}
268
270{
271 return d->storagePlugin->supportsVersioning();
272}
273
275{
276 return d->storagePlugin->loadVersionedResource(resource);
277}
278
279void KisResourceStorage::setMetaData(const QString &key, const QVariant &value)
280{
281 d->storagePlugin->setMetaData(key, value);
282}
283
285{
286 return d->valid;
287}
288
290{
291 return d->storagePlugin->metaDataKeys();
292}
293
294QVariant KisResourceStorage::metaData(const QString &key) const
295{
296 return d->storagePlugin->metaData(key);
297}
298
300{
301 d->storageId = storageId;
302}
303
305{
306 return d->storageId;
307}
308
310{
311 return d->storagePlugin.data();
312}
313
315{
316 QString basename;
317 int version = 0;
318 QString suffix;
319};
320
321boost::optional<VersionedFileParts> guessFilenameParts(const QString &filename)
322{
323 QRegularExpression exp("^(.*)\\.(\\d\\d*)\\.(.+)$");
324
325 QRegularExpressionMatch res = exp.match(filename);
326
327 if (res.hasMatch()) {
328 return VersionedFileParts({res.captured(1), res.captured(2).toInt(), res.captured(3)});
329 }
330
331 return boost::none;
332}
333
334VersionedFileParts guessFileNamePartsLazy(const QString &filename, int minVersion)
335{
336 boost::optional<VersionedFileParts> guess = guessFilenameParts(filename);
337 if (guess) {
338 guess->version = qMax(guess->version, minVersion);
339 } else {
340 QFileInfo info(filename);
341 guess = VersionedFileParts();
342 guess->basename = info.baseName();
343 guess->version = minVersion;
344 guess->suffix = info.completeSuffix();
345 }
346
347 return *guess;
348}
349
351 int minVersion,
352 std::function<bool(QString)> checkExists)
353{
354 int version = qMax(resource->version(), minVersion);
355
356 VersionedFileParts parts = guessFileNamePartsLazy(resource->filename(), version);
357 version = parts.version;
358
359 QString newFilename;
360
361 while (1) {
362 int numPlaceholders = 4;
363
364 if (version > 9999) {
365 numPlaceholders = qFloor(std::log10(version)) + 1;
366 }
367
368 QString versionString = QString("%1").arg(version, numPlaceholders, 10, QChar('0'));
369
370 // XXX: Temporary, until I've fixed the tests
371 if (versionString == "0000" && qApp->applicationName() == "krita") {
372 newFilename = resource->filename();
373 }
374 else {
375 newFilename = parts.basename +
376 "."
377 + versionString
378 + "."
379 + parts.suffix;
380 }
381 if (checkExists(newFilename)) {
382 version++;
383 if (version == std::numeric_limits<int>::max()) {
384 return QString();
385 }
386 continue;
387 }
388
389 break;
390 }
391
392 return newFilename;
393}
394
396{
397 for (auto it = allFiles.begin(); it != allFiles.end(); ++it) {
398 VersionedFileParts parts = guessFileNamePartsLazy(it->filename, -1);
399 it->guessedKey = parts.basename + parts.suffix;
400 it->guessedVersion = parts.version;
401 }
402
403 std::sort(allFiles.begin(), allFiles.end(), VersionedResourceEntry::KeyVersionLess());
404
405 boost::optional<QString> lastResourceKey;
406 int availableVersion = 0;
407 for (auto it = allFiles.begin(); it != allFiles.end(); ++it) {
408 if (!lastResourceKey || *lastResourceKey != it->guessedKey) {
409 availableVersion = 0;
410 lastResourceKey = it->guessedKey;
411 }
412
413 if (it->guessedVersion < availableVersion) {
414 it->guessedVersion = availableVersion;
415 }
416
417 availableVersion = it->guessedVersion + 1;
418 }
419}
420
422 KoResourceSP resource,
423 int minVersion)
424{
425 int version = qMax(resource->version(), minVersion);
426
427 VersionedFileParts parts = guessFileNamePartsLazy(resource->filename(), version);
428 version = parts.version;
429
430 QString newFilename =
431 chooseUniqueName(resource, minVersion,
432 [saveLocation] (const QString &filename) {
433 return QFileInfo(saveLocation + "/" + filename).exists();
434 });
435
436 if (newFilename.isEmpty()) return false;
437
438 QFile file(saveLocation + "/" + newFilename);
439 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!file.exists(), false);
440
441 if (!file.open(QFile::WriteOnly)) {
442 qWarning() << "Could not open resource file for writing" << newFilename;
443 return false;
444 }
445
446 if (!resource->saveToDevice(&file)) {
447 qWarning() << "Could not save resource file" << newFilename;
448 return false;
449 }
450
451 resource->setFilename(newFilename);
452 file.close();
453
454 if (!resource->thumbnailPath().isEmpty()) {
455 // hack for MyPaint brush presets thumbnails
456 // note: for all versions of the preset, it will try to save in the same place
457 if (!QFileInfo(saveLocation + "/" + resource->thumbnailPath()).exists()) {
458 QImage thumbnail = resource->thumbnail();
459 thumbnail.save(saveLocation + "/" + resource->thumbnailPath());
460 }
461 }
462
463 return true;
464}
465
466
468{
469 struct DumbIterator : public ResourceIterator
470 {
471 public:
472 DumbIterator(const ResourceIterator *parent)
473 : m_parent(parent)
474 {
475 }
476
477 bool hasNext() const override {
478 return !m_isStarted;
479 }
480
481 void next() override {
482 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_isStarted);
483 m_isStarted = true;
484 }
485 QString url() const override
486 {
487 return m_parent->url();
488 }
489
490 QString type() const override
491 {
492 return m_parent->type();
493 }
494
495 QDateTime lastModified() const override
496 {
497 return m_parent->lastModified();
498 }
499
500 int guessedVersion() const override
501 {
502 return m_parent->guessedVersion();
503 }
504
506 {
507 return toQShared(new DumbIterator(m_parent));
508 }
509
510 protected:
511 KoResourceSP resourceImpl() const override
512 {
513 return m_parent->resource();
514 }
515
516 private:
517 bool m_isStarted = false;
518 const ResourceIterator *m_parent;
519 };
520
521 return QSharedPointer<KisResourceStorage::ResourceIterator>(new DumbIterator(this));
522}
523
525{
526 if (m_cachedResource && m_cachedResourceUrl == url()) {
527 return m_cachedResource;
528 }
529
530 m_cachedResource = resourceImpl();
531 m_cachedResourceUrl = url();
532
533 return m_cachedResource;
534}
535
537 : q(_q)
538 , m_entries(entries)
539 , m_begin(m_entries.constBegin())
540 , m_end(m_entries.constEnd())
541
542{
543 // ENTER_FUNCTION() << ppVar(std::distance(m_begin, m_end));
544 // for (auto it = m_begin; it != m_end; ++it) {
545 // qDebug() << ppVar(it->filename) << ppVar(it->guessedVersion);
546 // }
547}
548
553 : q(_q)
554 , m_entries(entries)
555 , m_begin(begin)
556 , m_end(end)
557{
558// ENTER_FUNCTION() << ppVar(std::distance(m_begin, m_end));
559// for (auto it = m_begin; it != m_end; ++it) {
560// qDebug() << ppVar(it->filename) << ppVar(it->guessedVersion);
561// }
562}
563
565{
566 return (!m_isStarted && m_begin != m_end) ||
567 (m_isStarted && std::next(m_it) != m_end);
568}
569
571{
572
573 if (!m_isStarted) {
574 m_isStarted = true;
575 m_it = m_begin;
576 } else {
577 ++m_it;
578 }
579
581
582 auto nextChunk = std::upper_bound(m_it, m_end, *m_it, VersionedResourceEntry::KeyLess());
584 m_it = std::prev(nextChunk);
585}
586
588{
589 return m_it->resourceType + "/" + m_it->filename;
590}
591
593{
594 return m_it->resourceType;
595}
596
598{
599 return m_it->lastModified;
600}
601
606
608{
609 return m_it->guessedVersion;
610}
611
613{
614 struct VersionsIterator : public KisVersionedStorageIterator
615 {
616 VersionsIterator(const QVector<VersionedResourceEntry> &entries,
620 : KisVersionedStorageIterator(entries, begin, end, _q)
621 {
622 }
623
624 void next() override {
625 if (!m_isStarted) {
626 m_isStarted = true;
627 m_it = m_begin;
628 } else {
629 ++m_it;
630 }
631 }
632
634 return toQShared(new VersionsIterator(m_entries, m_it, std::next(m_it), q));
635 }
636 };
637
638 return toQShared(new VersionsIterator(m_entries, m_chunkStart, std::next(m_it), q));
639}
float value(const T *src, size_t ch)
VersionedFileParts guessFileNamePartsLazy(const QString &filename, int minVersion)
boost::optional< VersionedFileParts > guessFilenameParts(const QString &filename)
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
QSharedPointer< KisResourceStorage > KisResourceStorageSP
The KisMemoryStorage class stores the temporary resources that are not saved to disk or bundle....
QSharedPointer< KisStoragePlugin > storagePlugin
KisResourceStorage::StorageType storageType
virtual QSharedPointer< KisResourceStorage::ResourceIterator > versions() const
KoResourceSP resource(const QString &url)
The loaded resource for an entry in the storage.
QDateTime timestamp() const
KisResourceStorageSP clone() const
KisResourceStorage & operator=(const KisResourceStorage &rhs)
QVariant metaData(const QString &key) const
static const QString s_meta_author
static const QString s_meta_title
QSharedPointer< ResourceIterator > resources(const QString &resourceType) const
An iterator over all the resources in the storage.
static const QString s_meta_user_defined
static const QString s_meta_creation_date
static const QString s_meta_value
static const QString s_meta_email
bool valid() const
true if the storage exists and can be used
bool exportResource(const QString &url, QIODevice *device)
static const QString s_meta_license
static const QString s_xmlns_meta
bool addResource(KoResourceSP resource)
void setMetaData(const QString &key, const QVariant &value)
bool loadVersionedResource(KoResourceSP resource)
Reloads the given resource from the persistent storage.
QString location() const
The absolute location of the storage.
static const QString s_meta_description
QSharedPointer< TagIterator > tags(const QString &resourceType) const
An iterator over all the tags in the resource.
KisStoragePlugin * testingGetStoragePlugin()
static const QString s_meta_dc_date
QStringList metaDataKeys() const
ResourceItem resourceItem(const QString &url)
And entry in the storage; this is not the loaded resource.
void setStorageId(int storageId)
bool saveAsNewVersion(KoResourceSP resource)
Creates a new version of the given resource.
static const QString s_meta_website
static const QString s_meta_generator
StorageType type() const
The type of the storage.
static const QString s_meta_version
static const QString s_meta_name
QString resourceMd5(const QString &url)
The MD5 checksum of the resource in the storage.
QScopedPointer< Private > d
QDateTime timeStampForResource(const QString &resourceType, const QString &filename) const
static const QString s_xmlns_dc
static const QString s_meta_initial_creator
static const QString s_meta_creator
bool importResource(const QString &url, QIODevice *device)
QImage thumbnail() const
The icond for the storage.
KisResourceStorage(const QString &location)
QString resourceFilePath(const QString &url)
void addStoragePluginFactory(KisResourceStorage::StorageType storageType, KisStoragePluginFactoryBase *factory)
QList< KisResourceStorage::StorageType > storageTypes() const
static KisStoragePluginRegistry * instance()
QMap< KisResourceStorage::StorageType, KisStoragePluginFactoryBase * > m_storageFactoryMap
virtual KoResourceSP resource(const QString &url)
static void detectFileVersions(QVector< VersionedResourceEntry > &allFiles)
static QString chooseUniqueName(KoResourceSP resource, int minVersion, std::function< bool(QString)> checkExists)
static bool addVersionedResource(const QString &saveLocation, KoResourceSP resource, int minVersion)
int guessedVersion() const override
const QVector< VersionedResourceEntry > m_entries
QString url() const override
QVector< VersionedResourceEntry >::const_iterator m_it
QDateTime lastModified() const override
KisVersionedStorageIterator(const QVector< VersionedResourceEntry > &entries, KisStoragePlugin *_q)
QVector< VersionedResourceEntry >::const_iterator m_begin
void next() override
The iterator is only valid if next() has been called at least once.
QVector< VersionedResourceEntry >::const_iterator m_chunkStart
QVector< VersionedResourceEntry >::const_iterator m_end
QString type() const override
QSharedPointer< KisResourceStorage::ResourceIterator > versions() const override
KoResourceSP resourceImpl() const override
This only loads the resource when called.
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
QSharedPointer< T > toQShared(T *ptr)
A resource item is simply an entry in the storage,.