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
30namespace {
31KisResourceStorage::StorageType autoDetectStorageType(const QString &location) {
32 QFileInfo fi(location);
33 if (fi.isDir()) {
35 } else if (location.endsWith(".bundle", Qt::CaseInsensitive)) {
37 } else if (location.endsWith(".abr", Qt::CaseInsensitive)) {
39 } else if (location.endsWith(".asl", Qt::CaseInsensitive)) {
41 } else if (location == "fontregistry") {
43 } else if (location == "memory" || !QUuid::fromString(location).isNull() || (!location.isEmpty() && !fi.exists())) {
45 }
46
48}
49} // namespace
50
51const QString KisResourceStorage::s_xmlns_meta("urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
52const QString KisResourceStorage::s_xmlns_dc("http://purl.org/dc/elements/1.1");
53
54const QString KisResourceStorage::s_meta_generator("meta:generator");
55const QString KisResourceStorage::s_meta_author("dc:author");
56const QString KisResourceStorage::s_meta_title("dc:title");
57const QString KisResourceStorage::s_meta_description("dc:description");
58const QString KisResourceStorage::s_meta_initial_creator("meta:initial-creator");
59const QString KisResourceStorage::s_meta_creator("dc:creator");
60const QString KisResourceStorage::s_meta_creation_date("meta:creation-date");
61const QString KisResourceStorage::s_meta_dc_date("meta:dc-date");
62const QString KisResourceStorage::s_meta_user_defined("meta:meta-userdefined");
63const QString KisResourceStorage::s_meta_name("meta:name");
64const QString KisResourceStorage::s_meta_value("meta:value");
65const QString KisResourceStorage::s_meta_version("meta:bundle-version");
66const QString KisResourceStorage::s_meta_email("meta:email");
67const QString KisResourceStorage::s_meta_license("meta:license");
68const QString KisResourceStorage::s_meta_website("meta:website");
69
71
78
83
88
93
98
108
110 : d(new Private())
111{
112 d->location = location;
113
114 auto createMemoryStorage = [this] (const QString &location, bool isValid) {
115 d->name = location;
116 d->storageType = StorageType::Memory;
118 d->valid = isValid;
119 };
120
121 switch (storageType) {
122 case StorageType::Folder: {
123 QFileInfo fi(d->location);
124 if (fi.isDir()) {
125 d->name = fi.fileName();
126 d->storageType = StorageType::Folder;
127 d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Folder]->create(location));
128 d->valid = fi.isWritable();
129 } else {
130 createMemoryStorage(location, false);
131 }
132 break;
133 }
134 case StorageType::Bundle: {
135 QFileInfo fi(d->location);
136 d->name = fi.fileName();
137 d->storageType = StorageType::Bundle;
138 d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Bundle]->create(location));
139 // XXX: should we also check whether there's a valid metadata entry? Or is this enough?
140 d->valid = (fi.isReadable() && QuaZip(d->location).open(QuaZip::mdUnzip));
141 break;
142 }
144 QFileInfo fi(d->location);
145 d->name = fi.fileName();
146 d->storageType = StorageType::AdobeBrushLibrary;
147 d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::AdobeBrushLibrary]->create(location));
148 d->valid = fi.isReadable();
149 break;
150 }
152 QFileInfo fi(d->location);
153 d->name = fi.fileName();
154 d->storageType = StorageType::AdobeStyleLibrary;
155 d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::AdobeStyleLibrary]->create(location));
156 d->valid = d->storagePlugin->isValid();
157 break;
158 }
160 createMemoryStorage(location, false);
161 break;
163 createMemoryStorage(location, true);
164 break;
167 if (factory) {
168 d->name = location;
169 d->storageType = StorageType::FontStorage;
170 d->storagePlugin.reset(factory->create(location));
171 d->valid = true;
172 } else {
173 createMemoryStorage(location, false);
174 }
175 break;
176 }
177 }
178}
179
181 : KisResourceStorage(location, autoDetectStorageType(location))
182{
183}
184
188
190 : d(new Private)
191{
192 *this = rhs;
193}
194
196{
197 if (this != &rhs) {
198 d->name = rhs.d->name;
199 d->location = rhs.d->location;
200 d->storageType = rhs.d->storageType;
201 if (d->storageType == StorageType::Memory) {
202 const QSharedPointer<KisMemoryStorage> memoryStorage = rhs.d->storagePlugin.dynamicCast<KisMemoryStorage>();
203 KIS_ASSERT(memoryStorage);
204 d->storagePlugin = QSharedPointer<KisMemoryStorage>(new KisMemoryStorage(*memoryStorage));
205 }
206 else {
207 d->storagePlugin = rhs.d->storagePlugin;
208 }
209 d->valid = false;
210 }
211 return *this;
212}
213
218
220{
221 return d->name;
222}
223
225{
226 return d->location;
227}
228
230{
231 return d->storageType;
232}
233
235{
236 return d->storagePlugin->thumbnail();
237}
238
240{
241 return d->storagePlugin->timestamp();
242}
243
244QDateTime KisResourceStorage::timeStampForResource(const QString &resourceType, const QString &filename) const
245{
246 QFileInfo li(d->location);
247 if (li.suffix().toLower() == "bundle") {
248 QFileInfo bf(d->location + "_modified/" + resourceType + "/" + filename);
249 if (bf.exists()) {
250 return bf.lastModified();
251 }
252 } else if (QFileInfo(d->location + "/" + resourceType + "/" + filename).exists()) {
253 return QFileInfo(d->location + "/" + resourceType + "/" + filename).lastModified();
254 }
255 return this->timestamp();
256}
257
259{
260 return d->storagePlugin->resourceItem(url);
261}
262
264{
265 return d->storagePlugin->resource(url);
266}
267
268QString KisResourceStorage::resourceMd5(const QString &url)
269{
270 return d->storagePlugin->resourceMd5(url);
271}
272
273QString KisResourceStorage::resourceFilePath(const QString &url)
274{
275 return d->storagePlugin->resourceFilePath(url);
276}
277
279{
280 return d->storagePlugin->resources(resourceType);
281}
282
284{
285 return d->storagePlugin->tags(resourceType);
286}
287
289{
290 if (!resource) return false;
291
292 return d->storagePlugin->saveAsNewVersion(resource->resourceType().first, resource);
293}
294
296{
297 if (!resource) return false;
298
299 return d->storagePlugin->addResource(resource->resourceType().first, resource);
300}
301
302bool KisResourceStorage::importResource(const QString &url, QIODevice *device)
303{
304 return d->storagePlugin->importResource(url, device);
305}
306
307bool KisResourceStorage::exportResource(const QString &url, QIODevice *device)
308{
309 return d->storagePlugin->exportResource(url, device);
310}
311
313{
314 return d->storagePlugin->supportsVersioning();
315}
316
318{
319 return d->storagePlugin->loadVersionedResource(resource);
320}
321
322void KisResourceStorage::setMetaData(const QString &key, const QVariant &value)
323{
324 d->storagePlugin->setMetaData(key, value);
325}
326
328{
329 return d->valid;
330}
331
333{
334 return d->storagePlugin->metaDataKeys();
335}
336
337QVariant KisResourceStorage::metaData(const QString &key) const
338{
339 return d->storagePlugin->metaData(key);
340}
341
343{
344 d->storageId = storageId;
345}
346
348{
349 return d->storageId;
350}
351
353{
354 return d->storagePlugin.data();
355}
356
358{
359 QString basename;
360 int version = 0;
361 QString suffix;
362};
363
364boost::optional<VersionedFileParts> guessFilenameParts(const QString &filename)
365{
366 QRegularExpression exp("^(.*)\\.(\\d\\d*)\\.(.+)$");
367
368 QRegularExpressionMatch res = exp.match(filename);
369
370 if (res.hasMatch()) {
371 return VersionedFileParts({res.captured(1), res.captured(2).toInt(), res.captured(3)});
372 }
373
374 return boost::none;
375}
376
377VersionedFileParts guessFileNamePartsLazy(const QString &filename, int minVersion)
378{
379 boost::optional<VersionedFileParts> guess = guessFilenameParts(filename);
380 if (guess) {
381 guess->version = qMax(guess->version, minVersion);
382 } else {
383 QFileInfo info(filename);
384 guess = VersionedFileParts();
385 guess->basename = info.baseName();
386 guess->version = minVersion;
387 guess->suffix = info.completeSuffix();
388 }
389
390 return *guess;
391}
392
394 int minVersion,
395 std::function<bool(QString)> checkExists)
396{
397 int version = qMax(resource->version(), minVersion);
398
399 VersionedFileParts parts = guessFileNamePartsLazy(resource->filename(), version);
400 version = parts.version;
401
402 QString newFilename;
403
404 while (1) {
405 int numPlaceholders = 4;
406
407 if (version > 9999) {
408 numPlaceholders = qFloor(std::log10(version)) + 1;
409 }
410
411 QString versionString = QString("%1").arg(version, numPlaceholders, 10, QChar('0'));
412
413 // XXX: Temporary, until I've fixed the tests
414 if (versionString == "0000" && qApp->applicationName() == "krita") {
415 newFilename = resource->filename();
416 }
417 else {
418 newFilename = parts.basename +
419 "."
420 + versionString
421 + "."
422 + parts.suffix;
423 }
424 if (checkExists(newFilename)) {
425 version++;
426 if (version == std::numeric_limits<int>::max()) {
427 return QString();
428 }
429 continue;
430 }
431
432 break;
433 }
434
435 return newFilename;
436}
437
439{
440 for (auto it = allFiles.begin(); it != allFiles.end(); ++it) {
441 VersionedFileParts parts = guessFileNamePartsLazy(it->filename, -1);
442 it->guessedKey = parts.basename + parts.suffix;
443 it->guessedVersion = parts.version;
444 }
445
446 std::sort(allFiles.begin(), allFiles.end(), VersionedResourceEntry::KeyVersionLess());
447
448 boost::optional<QString> lastResourceKey;
449 int availableVersion = 0;
450 for (auto it = allFiles.begin(); it != allFiles.end(); ++it) {
451 if (!lastResourceKey || *lastResourceKey != it->guessedKey) {
452 availableVersion = 0;
453 lastResourceKey = it->guessedKey;
454 }
455
456 if (it->guessedVersion < availableVersion) {
457 it->guessedVersion = availableVersion;
458 }
459
460 availableVersion = it->guessedVersion + 1;
461 }
462}
463
465 KoResourceSP resource,
466 int minVersion)
467{
468 int version = qMax(resource->version(), minVersion);
469
470 VersionedFileParts parts = guessFileNamePartsLazy(resource->filename(), version);
471 version = parts.version;
472
473 QString newFilename =
474 chooseUniqueName(resource, minVersion,
475 [saveLocation] (const QString &filename) {
476 return QFileInfo(saveLocation + "/" + filename).exists();
477 });
478
479 if (newFilename.isEmpty()) return false;
480
481 QFile file(saveLocation + "/" + newFilename);
482 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!file.exists(), false);
483
484 if (!file.open(QFile::WriteOnly)) {
485 qWarning() << "Could not open resource file for writing" << newFilename;
486 return false;
487 }
488
489 if (!resource->saveToDevice(&file)) {
490 qWarning() << "Could not save resource file" << newFilename;
491 return false;
492 }
493
494 resource->setFilename(newFilename);
495 file.close();
496
497 if (!resource->thumbnailPath().isEmpty()) {
498 // hack for MyPaint brush presets thumbnails
499 // note: for all versions of the preset, it will try to save in the same place
500 if (!QFileInfo(saveLocation + "/" + resource->thumbnailPath()).exists()) {
501 QImage thumbnail = resource->thumbnail();
502 thumbnail.save(saveLocation + "/" + resource->thumbnailPath());
503 }
504 }
505
506 return true;
507}
508
509
511{
512 struct DumbIterator : public ResourceIterator
513 {
514 public:
515 DumbIterator(const ResourceIterator *parent)
516 : m_parent(parent)
517 {
518 }
519
520 bool hasNext() const override {
521 return !m_isStarted;
522 }
523
524 void next() override {
525 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_isStarted);
526 m_isStarted = true;
527 }
528 QString url() const override
529 {
530 return m_parent->url();
531 }
532
533 QString type() const override
534 {
535 return m_parent->type();
536 }
537
538 QDateTime lastModified() const override
539 {
540 return m_parent->lastModified();
541 }
542
543 int guessedVersion() const override
544 {
545 return m_parent->guessedVersion();
546 }
547
549 {
550 return toQShared(new DumbIterator(m_parent));
551 }
552
553 protected:
554 KoResourceSP resourceImpl() const override
555 {
556 return m_parent->resource();
557 }
558
559 private:
560 bool m_isStarted = false;
561 const ResourceIterator *m_parent;
562 };
563
564 return QSharedPointer<KisResourceStorage::ResourceIterator>(new DumbIterator(this));
565}
566
568{
569 if (m_cachedResource && m_cachedResourceUrl == url()) {
570 return m_cachedResource;
571 }
572
573 m_cachedResource = resourceImpl();
574 m_cachedResourceUrl = url();
575
576 return m_cachedResource;
577}
578
580 : q(_q)
581 , m_entries(entries)
582 , m_begin(m_entries.constBegin())
583 , m_end(m_entries.constEnd())
584
585{
586 // ENTER_FUNCTION() << ppVar(std::distance(m_begin, m_end));
587 // for (auto it = m_begin; it != m_end; ++it) {
588 // qDebug() << ppVar(it->filename) << ppVar(it->guessedVersion);
589 // }
590}
591
596 : q(_q)
597 , m_entries(entries)
598 , m_begin(begin)
599 , m_end(end)
600{
601// ENTER_FUNCTION() << ppVar(std::distance(m_begin, m_end));
602// for (auto it = m_begin; it != m_end; ++it) {
603// qDebug() << ppVar(it->filename) << ppVar(it->guessedVersion);
604// }
605}
606
608{
609 return (!m_isStarted && m_begin != m_end) ||
610 (m_isStarted && std::next(m_it) != m_end);
611}
612
614{
615
616 if (!m_isStarted) {
617 m_isStarted = true;
618 m_it = m_begin;
619 } else {
620 ++m_it;
621 }
622
624
625 auto nextChunk = std::upper_bound(m_it, m_end, *m_it, VersionedResourceEntry::KeyLess());
627 m_it = std::prev(nextChunk);
628}
629
631{
632 return m_it->resourceType + "/" + m_it->filename;
633}
634
636{
637 return m_it->resourceType;
638}
639
641{
642 return m_it->lastModified;
643}
644
649
651{
652 return m_it->guessedVersion;
653}
654
656{
657 struct VersionsIterator : public KisVersionedStorageIterator
658 {
659 VersionsIterator(const QVector<VersionedResourceEntry> &entries,
663 : KisVersionedStorageIterator(entries, begin, end, _q)
664 {
665 }
666
667 void next() override {
668 if (!m_isStarted) {
669 m_isStarted = true;
670 m_it = m_begin;
671 } else {
672 ++m_it;
673 }
674 }
675
677 return toQShared(new VersionsIterator(m_entries, m_it, std::next(m_it), q));
678 }
679 };
680
681 return toQShared(new VersionsIterator(m_entries, m_chunkStart, std::next(m_it), q));
682}
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)
KisResourceStorage(const QString &location, KisResourceStorage::StorageType storageType)
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.
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,.