File indexing completed on 2024-05-12 15:59:50

0001 /*
0002  * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "KisResourceLocator.h"
0008 
0009 #include <QApplication>
0010 #include <QDebug>
0011 #include <QList>
0012 #include <QDir>
0013 #include <QDirIterator>
0014 #include <QFileInfo>
0015 #include <QMessageBox>
0016 #include <QVersionNumber>
0017 #include <QElapsedTimer>
0018 #include <QSqlQuery>
0019 #include <QSqlError>
0020 #include <QBuffer>
0021 
0022 #include <kconfig.h>
0023 #include <kconfiggroup.h>
0024 #include <ksharedconfig.h>
0025 #include <klocalizedstring.h>
0026 
0027 #include <KritaVersionWrapper.h>
0028 #include <KisMimeDatabase.h>
0029 #include <kis_assert.h>
0030 #include <kis_debug.h>
0031 #include <KisUsageLogger.h>
0032 
0033 #include "KoResourcePaths.h"
0034 #include "KisResourceStorage.h"
0035 #include "KisResourceCacheDb.h"
0036 #include "KisResourceLoaderRegistry.h"
0037 #include "KisMemoryStorage.h"
0038 #include "KisResourceModelProvider.h"
0039 #include <KisGlobalResourcesInterface.h>
0040 #include <KisStorageModel.h>
0041 #include <KoMD5Generator.h>
0042 #include <KoResourceLoadResult.h>
0043 
0044 #include "ResourceDebug.h"
0045 
0046 const QString KisResourceLocator::resourceLocationKey {"ResourceDirectory"};
0047 
0048 class KisResourceLocator::Private {
0049 public:
0050     QString resourceLocation;
0051     QMap<QString, KisResourceStorageSP> storages;
0052     QHash<QPair<QString, QString>, KoResourceSP> resourceCache;
0053     QMap<QPair<QString, QString>, QImage> thumbnailCache;
0054     QMap<QPair<QString, QString>, KisTagSP> tagCache;
0055     QStringList errorMessages;
0056 };
0057 
0058 KisResourceLocator::KisResourceLocator(QObject *parent)
0059     : QObject(parent)
0060     , d(new Private())
0061 {
0062 }
0063 
0064 KisResourceLocator *KisResourceLocator::instance()
0065 {
0066     // Not a regular Q_GLOBAL_STATIC, because we want this deleted as
0067     // part of the app destructor.
0068     KisResourceLocator *locator = qApp->findChild<KisResourceLocator *>(QString());
0069     if (!locator) {
0070         locator = new KisResourceLocator(qApp);
0071     }
0072     return locator;
0073 }
0074 
0075 KisResourceLocator::~KisResourceLocator()
0076 {
0077 }
0078 
0079 KisResourceLocator::LocatorError KisResourceLocator::initialize(const QString &installationResourcesLocation)
0080 {
0081     InitializationStatus initializationStatus = InitializationStatus::Unknown;
0082 
0083     d->resourceLocation = KoResourcePaths::getAppDataLocation();
0084 
0085     if (!d->resourceLocation.endsWith('/')) d->resourceLocation += '/';
0086 
0087     QFileInfo fi(d->resourceLocation);
0088 
0089     if (!fi.exists()) {
0090         if (!QDir().mkpath(d->resourceLocation)) {
0091             d->errorMessages << i18n("1. Could not create the resource location at %1.", d->resourceLocation);
0092             return LocatorError::CannotCreateLocation;
0093         }
0094         initializationStatus = InitializationStatus::FirstRun;
0095     }
0096 
0097     if (!fi.isWritable()) {
0098         d->errorMessages << i18n("2. The resource location at %1 is not writable.", d->resourceLocation);
0099         return LocatorError::LocationReadOnly;
0100     }
0101 
0102     // Check whether we're updating from an older version
0103     if (initializationStatus != InitializationStatus::FirstRun) {
0104         QFile fi(d->resourceLocation + '/' + "KRITA_RESOURCE_VERSION");
0105         if (!fi.exists()) {
0106             initializationStatus = InitializationStatus::FirstUpdate;
0107         }
0108         else {
0109             fi.open(QFile::ReadOnly);
0110             QVersionNumber resource_version = QVersionNumber::fromString(QString::fromUtf8(fi.readAll()));
0111             QVersionNumber krita_version = QVersionNumber::fromString(KritaVersionWrapper::versionString());
0112             if (krita_version > resource_version) {
0113                 initializationStatus = InitializationStatus::Updating;
0114             }
0115             else {
0116                 initializationStatus = InitializationStatus::Initialized;
0117             }
0118         }
0119     }
0120 
0121     if (initializationStatus != InitializationStatus::Initialized) {
0122         KisResourceLocator::LocatorError res = firstTimeInstallation(initializationStatus, installationResourcesLocation);
0123         if (res != LocatorError::Ok) {
0124             return res;
0125         }
0126         initializationStatus = InitializationStatus::Initialized;
0127     }
0128 
0129     if (!synchronizeDb()) {
0130         return LocatorError::CannotSynchronizeDb;
0131     }
0132 
0133     return LocatorError::Ok;
0134 }
0135 
0136 QStringList KisResourceLocator::errorMessages() const
0137 {
0138     return d->errorMessages;
0139 }
0140 
0141 QString KisResourceLocator::resourceLocationBase() const
0142 {
0143     return d->resourceLocation;
0144 }
0145 
0146 bool KisResourceLocator::resourceCached(QString storageLocation, const QString &resourceType, const QString &filename) const
0147 {
0148     storageLocation = makeStorageLocationAbsolute(storageLocation);
0149     QPair<QString, QString> key = QPair<QString, QString> (storageLocation, resourceType + "/" + filename);
0150 
0151     return d->resourceCache.contains(key);
0152 }
0153 
0154 void KisResourceLocator::cacheThumbnail(QString storageLocation, const QString &resourceType, const QString &filename,
0155                                         const QImage &img) {
0156     storageLocation = makeStorageLocationAbsolute(storageLocation);
0157     QPair<QString, QString> key = QPair<QString, QString> (storageLocation, resourceType + "/" + filename);
0158 
0159     d->thumbnailCache[key] = img;
0160 }
0161 
0162 QImage KisResourceLocator::thumbnailCached(QString storageLocation, const QString &resourceType, const QString &filename)
0163 {
0164     storageLocation = makeStorageLocationAbsolute(storageLocation);
0165     QPair<QString, QString> key = QPair<QString, QString> (storageLocation, resourceType + "/" + filename);
0166     if (d->thumbnailCache.contains(key)) {
0167         return d->thumbnailCache[key];
0168     }
0169     return QImage();
0170 }
0171 
0172 void KisResourceLocator::loadRequiredResources(KoResourceSP resource)
0173 {
0174     QList<KoResourceLoadResult> requiredResources = resource->requiredResources(KisGlobalResourcesInterface::instance());
0175 
0176     Q_FOREACH (KoResourceLoadResult res, requiredResources) {
0177         switch (res.type())
0178         {
0179         case KoResourceLoadResult::ExistingResource:
0180             KIS_SAFE_ASSERT_RECOVER_NOOP(res.resource()->resourceId() >= 0);
0181             break;
0182         case KoResourceLoadResult::EmbeddedResource: {
0183             KoResourceSignature sig = res.embeddedResource().signature();
0184             QByteArray data = res.embeddedResource().data();
0185             QBuffer buffer(&data);
0186             buffer.open(QBuffer::ReadOnly);
0187 
0188             importResource(sig.type, sig.filename, &buffer, false, "memory");
0189             break;
0190         }
0191         case KoResourceLoadResult::FailedLink:
0192             qWarning() << "Failed to load linked resource:" << res.signature();
0193             break;
0194         }
0195     }
0196 }
0197 
0198 KisTagSP KisResourceLocator::tagForUrl(const QString &tagUrl, const QString resourceType)
0199 {
0200     if (d->tagCache.contains(QPair<QString, QString>(resourceType, tagUrl))) {
0201         return d->tagCache[QPair<QString, QString>(resourceType, tagUrl)];
0202     }
0203 
0204     KisTagSP tag = tagForUrlNoCache(tagUrl, resourceType);
0205 
0206     if (tag && tag->valid()) {
0207         d->tagCache[QPair<QString, QString>(resourceType, tagUrl)] = tag;
0208     }
0209 
0210     return tag;
0211 }
0212 
0213 KisTagSP KisResourceLocator::tagForUrlNoCache(const QString &tagUrl, const QString resourceType)
0214 {
0215     QSqlQuery query;
0216     bool r = query.prepare("SELECT tags.id\n"
0217                            ",      tags.url\n"
0218                            ",      tags.active\n"
0219                            ",      tags.name\n"
0220                            ",      tags.comment\n"
0221                            ",      tags.filename\n"
0222                            ",      resource_types.name as resource_type\n"
0223                            ",      resource_types.id\n"
0224                            "FROM   tags\n"
0225                            ",      resource_types\n"
0226                            "WHERE  tags.resource_type_id = resource_types.id\n"
0227                            "AND    resource_types.name = :resource_type\n"
0228                            "AND    tags.url = :tag_url\n");
0229 
0230     if (!r) {
0231         qWarning() << "Could not prepare KisResourceLocator::tagForUrl query" << query.lastError();
0232         return KisTagSP();
0233     }
0234 
0235     query.bindValue(":resource_type", resourceType);
0236     query.bindValue(":tag_url", tagUrl);
0237 
0238     r = query.exec();
0239     if (!r) {
0240         qWarning() << "Could not execute KisResourceLocator::tagForUrl query" << query.lastError() << query.boundValues();
0241         return KisTagSP();
0242     }
0243 
0244     r = query.first();
0245     if (!r) {
0246         return KisTagSP();
0247     }
0248 
0249     KisTagSP tag(new KisTag());
0250 
0251     int tagId = query.value("tags.id").toInt();
0252     int resourceTypeId = query.value("resource_types.id").toInt();
0253 
0254     tag->setUrl(query.value("url").toString());
0255     tag->setResourceType(resourceType);
0256     tag->setId(query.value("id").toInt());
0257     tag->setActive(query.value("active").toBool());
0258     tag->setName(query.value("name").toString());
0259     tag->setComment(query.value("comment").toString());
0260     tag->setFilename(query.value("filename").toString());
0261     tag->setValid(true);
0262 
0263 
0264     QMap<QString, QString> names;
0265     QMap<QString, QString> comments;
0266 
0267     r = query.prepare("SELECT language\n"
0268                       ",      name\n"
0269                       ",      comment\n"
0270                       "FROM   tag_translations\n"
0271                       "WHERE  tag_id = :id");
0272 
0273     if (!r) {
0274         qWarning() << "Could not prepare KisResourceLocator::tagForUrl translation query" << query.lastError();
0275     }
0276 
0277     query.bindValue(":id", tag->id());
0278 
0279     if (!query.exec()) {
0280         qWarning() << "Could not execute KisResourceLocator::tagForUrl translation query" << query.lastError();
0281     }
0282 
0283     while (query.next()) {
0284         names[query.value(0).toString()] = query.value(1).toString();
0285         comments[query.value(0).toString()] = query.value(2).toString();
0286     }
0287 
0288     tag->setNames(names);
0289     tag->setComments(comments);
0290 
0291     QSqlQuery defaultResourcesQuery;
0292 
0293     if (!defaultResourcesQuery.prepare("SELECT resources.filename\n"
0294                                        "FROM   resources\n"
0295                                        ",      resource_tags\n"
0296                                        "WHERE  resource_tags.tag_id = :tag_id\n"
0297                                        "AND    resources.resource_type_id = :type_id\n"
0298                                        "AND    resource_tags.resource_id = resources.id\n"
0299                                        "AND    resource_tags.active = 1\n")) {
0300         qWarning() << "Could not prepare resource/tag query" << defaultResourcesQuery.lastError();
0301     }
0302 
0303     defaultResourcesQuery.bindValue(":tag_id", tagId);
0304     defaultResourcesQuery.bindValue(":type_id", resourceTypeId);
0305 
0306     if (!defaultResourcesQuery.exec()) {
0307         qWarning() << "Could not execute resource/tag query" << defaultResourcesQuery.lastError();
0308     }
0309 
0310     QStringList resourceFileNames;
0311 
0312     while (defaultResourcesQuery.next()) {
0313         resourceFileNames << defaultResourcesQuery.value("resources.filename").toString();
0314     }
0315 
0316     tag->setDefaultResources(resourceFileNames);
0317 
0318     return tag;
0319 }
0320 
0321 
0322 KoResourceSP KisResourceLocator::resource(QString storageLocation, const QString &resourceType, const QString &filename)
0323 {
0324     storageLocation = makeStorageLocationAbsolute(storageLocation);
0325 
0326     QPair<QString, QString> key = QPair<QString, QString> (storageLocation, resourceType + "/" + filename);
0327 
0328     KoResourceSP resource;
0329     if (d->resourceCache.contains(key)) {
0330         resource = d->resourceCache[key];
0331     }
0332     else {
0333         KisResourceStorageSP storage = d->storages[storageLocation];
0334         if (!storage) {
0335             qWarning() << "Could not find storage" << storageLocation;
0336             return 0;
0337         }
0338 
0339         resource = storage->resource(resourceType + "/" + filename);
0340 
0341         if (resource) {
0342             d->resourceCache[key] = resource;
0343             // load all the embedded resources into temporary "memory" storage
0344             loadRequiredResources(resource);
0345             resource->updateLinkedResourcesMetaData(KisGlobalResourcesInterface::instance());
0346         }
0347     }
0348 
0349     if (!resource) {
0350         qWarning() << "KoResourceSP KisResourceLocator::resource" << storageLocation << resourceType << filename << "was not found";
0351         return 0;
0352     }
0353 
0354     resource->setStorageLocation(storageLocation);
0355     Q_ASSERT(!resource->storageLocation().isEmpty());
0356 
0357     if (resource->resourceId() < 0 || resource->version() < 0) {
0358         QSqlQuery q;
0359         if (!q.prepare("SELECT resources.id\n"
0360                        ",      versioned_resources.version as version\n"
0361                        ",      versioned_resources.md5sum as md5sum\n"
0362                        ",      resources.name\n"
0363                        ",      resources.status\n"
0364                        "FROM   resources\n"
0365                        ",      storages\n"
0366                        ",      resource_types\n"
0367                        ",      versioned_resources\n"
0368                        "WHERE  storages.id = resources.storage_id\n"
0369                        "AND    storages.location = :storage_location\n"
0370                        "AND    resource_types.id = resources.resource_type_id\n"
0371                        "AND    resource_types.name = :resource_type\n"
0372                        "AND    resources.filename  = :filename\n"
0373                        "AND    versioned_resources.resource_id = resources.id\n"
0374                        "AND    versioned_resources.version = (SELECT MAX(version) FROM versioned_resources WHERE versioned_resources.resource_id = resources.id)")) {
0375             qWarning() << "Could not prepare id/version query" << q.lastError();
0376 
0377         }
0378 
0379         q.bindValue(":storage_location", makeStorageLocationRelative(storageLocation));
0380         q.bindValue(":resource_type", resourceType);
0381         q.bindValue(":filename", filename);
0382 
0383         if (!q.exec()) {
0384             qWarning() << "Could not execute id/version query" << q.lastError() << q.boundValues();
0385         }
0386 
0387         if (!q.first()) {
0388             qWarning() << "Could not find the resource in the database" << storageLocation << resourceType << filename;
0389         }
0390 
0391         resource->setResourceId(q.value(0).toInt());
0392         Q_ASSERT(resource->resourceId() >= 0);
0393 
0394         resource->setVersion(q.value(1).toInt());
0395         Q_ASSERT(resource->version() >= 0);
0396 
0397         resource->setMD5Sum(q.value(2).toString());
0398         Q_ASSERT(!resource->md5Sum().isEmpty());
0399 
0400         resource->setActive(q.value(4).toBool());
0401 
0402         // To override resources that use the filename for the name, which is versioned, and we don't want the version number in the name
0403         resource->setName(q.value(3).toString());;
0404     }
0405 
0406     if (!resource) {
0407         qWarning() << "Could not find resource" << resourceType + "/" + filename;
0408         return 0;
0409     }
0410 
0411     return resource;
0412 }
0413 
0414 KoResourceSP KisResourceLocator::resourceForId(int resourceId)
0415 {
0416     ResourceStorage rs = getResourceStorage(resourceId);
0417     KoResourceSP r = resource(rs.storageLocation, rs.resourceType, rs.resourceFileName);
0418     return r;
0419 }
0420 
0421 bool KisResourceLocator::setResourceActive(int resourceId, bool active)
0422 {
0423     // First remove the resource from the cache
0424     ResourceStorage rs = getResourceStorage(resourceId);
0425     QPair<QString, QString> key = QPair<QString, QString> (rs.storageLocation, rs.resourceType + "/" + rs.resourceFileName);
0426 
0427     d->resourceCache.remove(key);
0428     if (!active && d->thumbnailCache.contains(key)) {
0429         d->thumbnailCache.remove(key);
0430     }
0431 
0432     bool result = KisResourceCacheDb::setResourceActive(resourceId, active);
0433 
0434     Q_EMIT resourceActiveStateChanged(rs.resourceType, resourceId);
0435 
0436     return result;
0437 }
0438 
0439 KoResourceSP KisResourceLocator::importResourceFromFile(const QString &resourceType, const QString &fileName, const bool allowOverwrite, const QString &storageLocation)
0440 {
0441     QFile f(fileName);
0442     if (!f.open(QFile::ReadOnly)) {
0443         qWarning() << "Could not open" << fileName << "for loading";
0444         return nullptr;
0445     }
0446 
0447     return importResource(resourceType, fileName, &f, allowOverwrite, storageLocation);
0448 }
0449 
0450 KoResourceSP KisResourceLocator::importResource(const QString &resourceType, const QString &fileName, QIODevice *device, const bool allowOverwrite, const QString &storageLocation)
0451 {
0452     KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)];
0453 
0454     QByteArray resourceData = device->readAll();
0455     KoResourceSP resource;
0456 
0457     {
0458         QBuffer buf(&resourceData);
0459         buf.open(QBuffer::ReadOnly);
0460 
0461         KisResourceLoaderBase *loader = KisResourceLoaderRegistry::instance()->loader(resourceType, KisMimeDatabase::mimeTypeForFile(fileName));
0462 
0463         if (!loader) {
0464             qWarning() << "Could not import" << fileName << ": resource doesn't load.";
0465             return nullptr;
0466         }
0467 
0468         resource = loader->load(QFileInfo(fileName).fileName(), buf, KisGlobalResourcesInterface::instance());
0469     }
0470 
0471     if (!resource || !resource->valid()) {
0472         qWarning() << "Could not import" << fileName << ": resource doesn't load.";
0473         return nullptr;
0474     }
0475 
0476     const QString md5 = KoMD5Generator::generateHash(resourceData);
0477     const QString resourceUrl = resourceType + "/" + resource->filename();
0478 
0479     const KoResourceSP existingResource = storage->resource(resourceUrl);
0480 
0481     if (existingResource) {
0482         const QString existingResourceMd5Sum = storage->resourceMd5(resourceUrl);
0483 
0484         if (!allowOverwrite) {
0485             return nullptr;
0486         }
0487 
0488         if (existingResourceMd5Sum == md5 &&
0489             existingResource->filename() == resource->filename()) {
0490 
0491             /**
0492              * Make sure that this resource is the latest version of the
0493              * resource. Also, we cannot just return existingResource, because
0494              * it has uninitialized fields. It should go through the initialization
0495              * by the locator's caching system.
0496              */
0497 
0498             int existingResourceId = -1;
0499             bool r = KisResourceCacheDb::getResourceIdFromFilename(existingResource->filename(), resourceType, storageLocation, existingResourceId);
0500 
0501             if (r && existingResourceId > 0) {
0502                 return resourceForId(existingResourceId);
0503             }
0504         }
0505 
0506         qWarning() << "A resource with the same filename but a different MD5 already exists in the storage" << resourceType << fileName << storageLocation;
0507         if (storageLocation == "") {
0508             qWarning() << "Proceeding with overwriting the existing resource...";
0509             // remove all versions of the resource from the resource folder
0510             QStringList versionsLocations;
0511 
0512             // this resource has id -1, we need correct id
0513             int existingResourceId = -1;
0514             bool r = KisResourceCacheDb::getResourceIdFromVersionedFilename(existingResource->filename(), resourceType, storageLocation, existingResourceId);
0515 
0516             if (r && existingResourceId >= 0) {
0517                 if (KisResourceCacheDb::getAllVersionsLocations(existingResourceId, versionsLocations)) {
0518 
0519                     for (int i = 0; i < versionsLocations.size(); i++) {
0520                         QFileInfo fi(this->resourceLocationBase() + "/" + resourceType + "/" + versionsLocations[i]);
0521                         if (fi.exists()) {
0522                             r = QFile::remove(fi.filePath());
0523                             if (!r) {
0524                                 qWarning() << "KisResourceLocator::importResourceFromFile: Removal of " << fi.filePath()
0525                                            << "was requested, but it wasn't possible, something went wrong.";
0526                             }
0527                         } else {
0528                             qWarning() << "KisResourceLocator::importResourceFromFile: Removal of " << fi.filePath()
0529                                        << "was requested, but it doesn't exist.";
0530                         }
0531                     }
0532                 } else {
0533                     qWarning() << "KisResourceLocator::importResourceFromFile: Finding all locations for " << existingResourceId << "was requested, but it failed.";
0534                     return nullptr;
0535                 }
0536             } else {
0537                 qWarning() << "KisResourceLocator::importResourceFromFile: there is no resource file found in the location of " << storageLocation << resource->filename() << resourceType;
0538                 return nullptr;
0539             }
0540 
0541             Q_EMIT beginExternalResourceRemove(resourceType, {existingResourceId});
0542 
0543             // remove everything related to this resource from the database (remember about tags and versions!!!)
0544             r = KisResourceCacheDb::removeResourceCompletely(existingResourceId);
0545 
0546             Q_EMIT endExternalResourceRemove(resourceType);
0547 
0548             if (!r) {
0549                 qWarning() << "KisResourceLocator::importResourceFromFile: Removing resource with id " << existingResourceId << "completely from the database failed.";
0550                 return nullptr;
0551             }
0552 
0553         } else {
0554             qWarning() << "KisResourceLocator::importResourceFromFile: Overwriting of the resource was denied, aborting import.";
0555             return nullptr;
0556         }
0557     }
0558 
0559     QBuffer buf(&resourceData);
0560     buf.open(QBuffer::ReadOnly);
0561 
0562     if (storage->importResource(resourceUrl, &buf)) {
0563         resource = storage->resource(resourceUrl);
0564 
0565         if (!resource) {
0566             qWarning() << "Could not retrieve imported resource from the storage" << resourceType << fileName << storageLocation;
0567             return nullptr;
0568         }
0569 
0570         resource->setStorageLocation(storageLocation);
0571         resource->setMD5Sum(storage->resourceMd5(resourceUrl));
0572         resource->setVersion(0);
0573         resource->setDirty(false);
0574         resource->updateLinkedResourcesMetaData(KisGlobalResourcesInterface::instance());
0575 
0576         Q_EMIT beginExternalResourceImport(resourceType, 1);
0577 
0578         // Insert into the database
0579         const bool result = KisResourceCacheDb::addResource(storage,
0580                                                 storage->timeStampForResource(resourceType, resource->filename()),
0581                                                 resource,
0582                                                 resourceType);
0583 
0584         Q_EMIT endExternalResourceImport(resourceType);
0585 
0586         if (!result) {
0587             return nullptr;
0588         }
0589 
0590         // resourceCaches use absolute locations
0591         const QString absoluteStorageLocation = makeStorageLocationAbsolute(resource->storageLocation());
0592         const QPair<QString, QString> key = {absoluteStorageLocation, resourceType + "/" + resource->filename()};
0593         // Add to the cache
0594         d->resourceCache[key] = resource;
0595         d->thumbnailCache[key] = resource->thumbnail();
0596 
0597         return resource;
0598     }
0599 
0600     return nullptr;
0601 }
0602 
0603 bool KisResourceLocator::importWillOverwriteResource(const QString &resourceType, const QString &fileName, const QString &storageLocation) const
0604 {
0605     KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)];
0606 
0607     const QString resourceUrl = resourceType + "/" + QFileInfo(fileName).fileName();
0608 
0609     const KoResourceSP existingResource = storage->resource(resourceUrl);
0610 
0611     return existingResource;
0612 }
0613 
0614 bool KisResourceLocator::exportResource(KoResourceSP resource, QIODevice *device)
0615 {
0616     if (!resource || !resource->valid() || resource->resourceId() < 0) return false;
0617 
0618     const QString resourceUrl = resource->resourceType().first + "/" + resource->filename();
0619     KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(resource->storageLocation())];
0620     return storage->exportResource(resourceUrl, device);
0621 }
0622 
0623 bool KisResourceLocator::addResource(const QString &resourceType, const KoResourceSP resource, const QString &storageLocation)
0624 {
0625     if (!resource || !resource->valid()) return false;
0626 
0627     KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)];
0628     Q_ASSERT(storage);
0629 
0630     //If we have gotten this far and the resource still doesn't have a filename to save to, we should generate one.
0631     if (resource->filename().isEmpty()) {
0632         resource->setFilename(resource->name().split(" ").join("_") + resource->defaultFileExtension());
0633     }
0634 
0635     if (resource->version() != 0) { // Can happen with cloned resources
0636         resource->setVersion(0);
0637     }
0638 
0639     // Save the resource to the storage storage
0640     if (!storage->addResource(resource)) {
0641         qWarning() << "Could not add resource" << resource->filename() << "to the storage" << storageLocation;
0642         return false;
0643     }
0644 
0645     resource->setStorageLocation(storageLocation);
0646     resource->setMD5Sum(storage->resourceMd5(resourceType + "/" + resource->filename()));
0647     resource->setDirty(false);
0648     resource->updateLinkedResourcesMetaData(KisGlobalResourcesInterface::instance());
0649 
0650     d->resourceCache[QPair<QString, QString>(storageLocation, resourceType + "/" + resource->filename())] = resource;
0651 
0652     /// And to the database.
0653     ///
0654     /// The metadata will be set by KisResourceCacheDb, which is
0655     /// not very consistent with KisResourceLocator::updateResource(),
0656     /// but works :)
0657     const bool result = KisResourceCacheDb::addResource(storage,
0658                                                         storage->timeStampForResource(resourceType, resource->filename()),
0659                                                         resource,
0660                                                         resourceType);
0661     return result;
0662 }
0663 
0664 bool KisResourceLocator::updateResource(const QString &resourceType, const KoResourceSP resource)
0665 {
0666     QString storageLocation = makeStorageLocationAbsolute(resource->storageLocation());
0667 
0668     Q_ASSERT(d->storages.contains(storageLocation));
0669 
0670     if (resource->resourceId() < 0) {
0671         return addResource(resourceType, resource);
0672     }
0673 
0674     KisResourceStorageSP storage = d->storages[storageLocation];
0675 
0676     if (!storage->supportsVersioning()) return false;
0677 
0678     // remove older version
0679     d->thumbnailCache.remove(QPair<QString, QString> (storageLocation, resourceType + "/" + resource->filename()));
0680 
0681     resource->updateThumbnail();
0682     resource->setVersion(resource->version() + 1);
0683     resource->setActive(true);
0684 
0685     if (!storage->saveAsNewVersion(resource)) {
0686         qWarning() << "Failed to save the new version of " << resource->name() << "to storage" << storageLocation;
0687         return false;
0688     }
0689 
0690     resource->setMD5Sum(storage->resourceMd5(resourceType + "/" + resource->filename()));
0691     resource->setDirty(false);
0692     resource->updateLinkedResourcesMetaData(KisGlobalResourcesInterface::instance());
0693 
0694     // The version needs already to have been incremented
0695     if (!KisResourceCacheDb::addResourceVersion(resource->resourceId(), QDateTime::currentDateTime(), storage, resource)) {
0696         qWarning() << "Failed to add a new version of the resource to the database" << resource->name();
0697         return false;
0698     }
0699 
0700     if (!setMetaDataForResource(resource->resourceId(), resource->metadata())) {
0701         qWarning() << "Failed to update resource metadata" << resource;
0702         return false;
0703     }
0704 
0705     // Update the resource in the cache
0706     QPair<QString, QString> key = QPair<QString, QString> (storageLocation, resourceType + "/" + resource->filename());
0707     d->resourceCache[key] = resource;
0708     d->thumbnailCache[key] = resource->thumbnail();
0709 
0710     return true;
0711 }
0712 
0713 bool KisResourceLocator::reloadResource(const QString &resourceType, const KoResourceSP resource)
0714 {
0715     // This resource isn't in the database yet, so we cannot reload it
0716     if (resource->resourceId() < 0) return false;
0717 
0718     QString storageLocation = makeStorageLocationAbsolute(resource->storageLocation());
0719     Q_ASSERT(d->storages.contains(storageLocation));
0720 
0721     KisResourceStorageSP storage = d->storages[storageLocation];
0722 
0723     if (!storage->loadVersionedResource(resource)) {
0724         qWarning() << "Failed to reload the resource" << resource->name() << "from storage" << storageLocation;
0725         return false;
0726     }
0727 
0728     resource->setMD5Sum(storage->resourceMd5(resourceType + "/" + resource->filename()));
0729     resource->setDirty(false);
0730     resource->updateLinkedResourcesMetaData(KisGlobalResourcesInterface::instance());
0731 
0732     // We haven't changed the version of the resource, so the cache must be still valid
0733     QPair<QString, QString> key = QPair<QString, QString> (storageLocation, resourceType + "/" + resource->filename());
0734     Q_ASSERT(d->resourceCache[key] == resource);
0735 
0736     return true;
0737 }
0738 
0739 QMap<QString, QVariant> KisResourceLocator::metaDataForResource(int id) const
0740 {
0741     return KisResourceCacheDb::metaDataForId(id, "resources");
0742 }
0743 
0744 bool KisResourceLocator::setMetaDataForResource(int id, QMap<QString, QVariant> map) const
0745 {
0746     return KisResourceCacheDb::updateMetaDataForId(map, id, "resources");
0747 }
0748 
0749 QMap<QString, QVariant> KisResourceLocator::metaDataForStorage(const QString &storageLocation) const
0750 {
0751     QMap<QString, QVariant> metadata;
0752     if (!d->storages.contains(makeStorageLocationAbsolute(storageLocation))) {
0753         qWarning() << storageLocation << "not in" << d->storages.keys();
0754         return metadata;
0755     }
0756 
0757     KisResourceStorageSP st = d->storages[makeStorageLocationAbsolute(storageLocation)];
0758 
0759     if (d->storages[makeStorageLocationAbsolute(storageLocation)].isNull()) {
0760         return metadata;
0761     }
0762 
0763     Q_FOREACH(const QString key, st->metaDataKeys()) {
0764         metadata[key] = st->metaData(key);
0765     }
0766     return metadata;
0767 }
0768 
0769 void KisResourceLocator::setMetaDataForStorage(const QString &storageLocation, QMap<QString, QVariant> map) const
0770 {
0771     Q_ASSERT(d->storages.contains(storageLocation));
0772     Q_FOREACH(const QString &key, map.keys()) {
0773         d->storages[storageLocation]->setMetaData(key, map[key]);
0774     }
0775 }
0776 
0777 void KisResourceLocator::purge(const QString &storageLocation)
0778 {
0779     Q_FOREACH(const auto key, d->resourceCache.keys()) {
0780         if (key.first == storageLocation) {
0781             d->resourceCache.take(key);
0782             d->thumbnailCache.take(key);
0783         }
0784     }
0785 }
0786 
0787 bool KisResourceLocator::addStorage(const QString &storageLocation, KisResourceStorageSP storage)
0788 {
0789     if (d->storages.contains(storageLocation)) {
0790         if (!removeStorage(storageLocation)) {
0791             qWarning() << "could not remove" << storageLocation;
0792             return false;
0793         }
0794     }
0795 
0796     QVector<std::pair<QString, int>> addedResources;
0797     Q_FOREACH(const QString &type, KisResourceLoaderRegistry::instance()->resourceTypes()) {
0798         int numAddedResources = 0;
0799 
0800         QSharedPointer<KisResourceStorage::ResourceIterator> it = storage->resources(type);
0801         while (it->hasNext()) {
0802             it->next();
0803             numAddedResources++;
0804         }
0805 
0806         if (numAddedResources > 0) {
0807             addedResources << std::make_pair(type, numAddedResources);
0808         }
0809     }
0810 
0811     Q_FOREACH (const auto &typedResources, addedResources) {
0812         Q_EMIT beginExternalResourceImport(typedResources.first, typedResources.second);
0813     }
0814 
0815     d->storages[storageLocation] = storage;
0816     if (!KisResourceCacheDb::addStorage(storage, false)) {
0817         d->errorMessages.append(i18n("Could not add %1 to the database", storage->location()));
0818         qWarning() << d->errorMessages;
0819         return false;
0820     }
0821 
0822     if (!KisResourceCacheDb::addStorageTags(storage)) {
0823         d->errorMessages.append(QString("Could not add tags for storage %1 to the cache database").arg(storage->location()));
0824         qWarning() << d->errorMessages;
0825         return false;
0826     }
0827 
0828     Q_FOREACH (const auto &typedResources, addedResources) {
0829         Q_EMIT endExternalResourceImport(typedResources.first);
0830     }
0831 
0832     Q_EMIT storageAdded(makeStorageLocationRelative(storage->location()));
0833     return true;
0834 }
0835 
0836 bool KisResourceLocator::removeStorage(const QString &storageLocation)
0837 {
0838     // Cloned documents have a document storage, but that isn't in the locator.
0839     if (!d->storages.contains(storageLocation)) {
0840         return true;
0841     }
0842 
0843     QVector<std::pair<QString, QVector<int>>> removedResources;
0844 
0845     Q_FOREACH(const QString &type, KisResourceLoaderRegistry::instance()->resourceTypes()) {
0846         const QVector<int> resources = KisResourceCacheDb::resourcesForStorage(type, storageLocation);
0847         if (!resources.isEmpty()) {
0848             removedResources << std::make_pair(type, resources);
0849         }
0850     }
0851 
0852     Q_FOREACH (const auto &typedResources, removedResources) {
0853         Q_EMIT beginExternalResourceRemove(typedResources.first, typedResources.second);
0854     }
0855 
0856     purge(storageLocation);
0857 
0858     KisResourceStorageSP storage = d->storages.take(storageLocation);
0859 
0860     if (!KisResourceCacheDb::deleteStorage(storage)) {
0861         d->errorMessages.append(i18n("Could not remove storage %1 from the database", storage->location()));
0862         qWarning() << d->errorMessages;
0863         return false;
0864     }
0865 
0866     Q_FOREACH (const auto &typedResources, removedResources) {
0867         Q_EMIT endExternalResourceRemove(typedResources.first);
0868     }
0869 
0870     Q_EMIT storageRemoved(makeStorageLocationRelative(storage->location()));
0871 
0872     return true;
0873 }
0874 
0875 bool KisResourceLocator::hasStorage(const QString &document)
0876 {
0877     return d->storages.contains(document);
0878 }
0879 
0880 void KisResourceLocator::saveTags()
0881 {
0882     QSqlQuery query;
0883 
0884     if (!query.prepare("SELECT tags.url \n"
0885                        ",      resource_types.name \n"
0886                        "FROM   tags\n"
0887                        ",      resource_types\n"
0888                        "WHERE  tags.resource_type_id = resource_types.id\n"))
0889     {
0890         qWarning() << "Could not prepare save tags query" << query.lastError();
0891         return;
0892     }
0893 
0894     if (!query.exec()) {
0895         qWarning() << "Could not execute save tags query" << query.lastError();
0896         return;
0897     }
0898 
0899     // this needs to use ResourcePaths because it is sometimes called during initialization
0900     // (when the database versions don't match up and tags need to be saved)
0901     QString resourceLocation = KoResourcePaths::getAppDataLocation() + "/";
0902 
0903     while (query.next()) {
0904         // Save tag...
0905         KisTagSP tag = tagForUrlNoCache(query.value("tags.url").toString(),
0906                                  query.value("resource_types.name").toString());
0907 
0908         if (!tag || !tag->valid()) {
0909             continue;
0910         }
0911 
0912 
0913         QString filename = tag->filename();
0914         if (filename.isEmpty() || QFileInfo(filename).suffix().isEmpty()) {
0915             filename = tag->url() + ".tag";
0916         }
0917 
0918 
0919         if (QFileInfo(filename).suffix() != "tag" && QFileInfo(filename).suffix() != "TAG") {
0920             // it's either .abr file, or maybe a .bundle
0921             // or something else, but not a tag file
0922             dbgResources << "Skipping saving tag " << tag->name(false) << filename << tag->resourceType();
0923             continue;
0924         }
0925 
0926         filename.remove(resourceLocation);
0927 
0928         QFile f(resourceLocation + "/" + tag->resourceType() + '/' + filename);
0929 
0930         if (!f.open(QFile::WriteOnly)) {
0931             qWarning () << "Couild not open tag file for writing" << f.fileName();
0932             continue;
0933         }
0934 
0935         QBuffer buf;
0936         buf.open(QIODevice::WriteOnly);;
0937 
0938         if (!tag->save(buf)) {
0939             qWarning() << "Could not save tag to" << f.fileName();
0940             buf.close();
0941             f.close();
0942             continue;
0943         }
0944 
0945         f.write(buf.data());
0946         f.flush();
0947 
0948         f.close();
0949     }
0950 }
0951 
0952 void KisResourceLocator::purgeTag(const QString tagUrl, const QString resourceType)
0953 {
0954     d->tagCache.remove(QPair<QString, QString>(resourceType, tagUrl));
0955 }
0956 
0957 QString KisResourceLocator::filePathForResource(KoResourceSP resource)
0958 {
0959     const QString storageLocation = makeStorageLocationAbsolute(resource->storageLocation());
0960     KisResourceStorageSP storage = d->storages[storageLocation];
0961     if (!storage) {
0962         qWarning() << "Could not find storage" << storageLocation;
0963         return QString();
0964     }
0965 
0966     const QString resourceUrl = resource->resourceType().first + "/" + resource->filename();
0967 
0968     return storage->resourceFilePath(resourceUrl);
0969 }
0970 
0971 KisResourceLocator::LocatorError KisResourceLocator::firstTimeInstallation(InitializationStatus initializationStatus, const QString &installationResourcesLocation)
0972 {
0973     emit progressMessage(i18n("Krita is running for the first time. Initialization will take some time."));
0974     Q_UNUSED(initializationStatus);
0975 
0976     Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) {
0977         QDir dir(d->resourceLocation + '/' + folder + '/');
0978         if (!dir.exists()) {
0979             if (!QDir().mkpath(d->resourceLocation + '/' + folder + '/')) {
0980                 d->errorMessages << i18n("3. Could not create the resource location at %1.", dir.path());
0981                 return LocatorError::CannotCreateLocation;
0982             }
0983         }
0984     }
0985 
0986     Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) {
0987         QDir dir(installationResourcesLocation + '/' + folder + '/');
0988         if (dir.exists()) {
0989             Q_FOREACH(const QString &entry, dir.entryList(QDir::Files | QDir::Readable)) {
0990                 QFile f(dir.canonicalPath() + '/'+ entry);
0991                 if (!QFileInfo(d->resourceLocation + '/' + folder + '/' + entry).exists()) {
0992                     if (!f.copy(d->resourceLocation + '/' + folder + '/' + entry)) {
0993                         d->errorMessages << i18n("Could not copy resource %1 to %2", f.fileName(), d->resourceLocation + '/' + folder + '/' + entry);
0994                     }
0995                 }
0996             }
0997         }
0998     }
0999 
1000     // And add bundles and adobe libraries
1001     QStringList filters = QStringList() << "*.bundle" << "*.abr" << "*.asl";
1002     QDirIterator iter(installationResourcesLocation, filters, QDir::Files, QDirIterator::Subdirectories);
1003     while (iter.hasNext()) {
1004         iter.next();
1005         emit progressMessage(i18n("Installing the resources from bundle %1.", iter.filePath()));
1006         QFile f(iter.filePath());
1007         Q_ASSERT(f.exists());
1008         if (!f.copy(d->resourceLocation + '/' + iter.fileName())) {
1009             d->errorMessages << i18n("Could not copy resource %1 to %2", f.fileName(), d->resourceLocation);
1010         }
1011     }
1012 
1013     QFile f(d->resourceLocation + '/' + "KRITA_RESOURCE_VERSION");
1014     f.open(QFile::WriteOnly);
1015     f.write(KritaVersionWrapper::versionString().toUtf8());
1016     f.close();
1017 
1018     if (!initializeDb()) {
1019         return LocatorError::CannotInitializeDb;
1020     }
1021 
1022     return LocatorError::Ok;
1023 }
1024 
1025 bool KisResourceLocator::initializeDb()
1026 {
1027     emit progressMessage(i18n("Initializing the resources."));
1028     d->errorMessages.clear();
1029     findStorages();
1030 
1031     Q_FOREACH(auto loader, KisResourceLoaderRegistry::instance()->values()) {
1032         KisResourceCacheDb::registerResourceType(loader->resourceType());
1033     }
1034 
1035     Q_FOREACH(KisResourceStorageSP storage, d->storages) {
1036         if (!KisResourceCacheDb::addStorage(storage, (storage->type() == KisResourceStorage::StorageType::Folder ? false : true))) {
1037             d->errorMessages.append(QString("Could not add storage %1 to the cache database").arg(storage->location()));
1038         }
1039     }
1040 
1041     Q_FOREACH(KisResourceStorageSP storage, d->storages) {
1042         if (!KisResourceCacheDb::addStorageTags(storage)) {
1043             d->errorMessages.append(QString("Could not add tags for storage %1 to the cache database").arg(storage->location()));
1044         }
1045     }
1046     return (d->errorMessages.isEmpty());
1047 }
1048 
1049 void KisResourceLocator::findStorages()
1050 {
1051     d->storages.clear();
1052     d->resourceCache.clear();
1053 
1054     // Add the folder
1055     KisResourceStorageSP storage = QSharedPointer<KisResourceStorage>::create(d->resourceLocation);
1056     Q_ASSERT(storage->location() == d->resourceLocation);
1057     d->storages[d->resourceLocation] = storage;
1058 
1059     // Add the memory storage
1060     d->storages["memory"] = QSharedPointer<KisResourceStorage>::create("memory");
1061     d->storages["memory"]->setMetaData(KisResourceStorage::s_meta_name, i18n("Temporary Resources"));
1062 
1063     // And add bundles and adobe libraries
1064     QStringList filters = QStringList() << "*.bundle" << "*.abr" << "*.asl";
1065     QDirIterator iter(d->resourceLocation, filters, QDir::Files, QDirIterator::Subdirectories);
1066     while (iter.hasNext()) {
1067         iter.next();
1068         KisResourceStorageSP storage = QSharedPointer<KisResourceStorage>::create(iter.filePath());
1069         if (!storage->valid()) {
1070             // we still add the storage to the list and try to read whatever possible
1071             qWarning() << "KisResourceLocator::findStorages: the storage is invalid" << storage->location();
1072         }
1073         d->storages[storage->location()] = storage;
1074     }
1075 }
1076 
1077 QList<KisResourceStorageSP> KisResourceLocator::storages() const
1078 {
1079     return d->storages.values();
1080 }
1081 
1082 KisResourceStorageSP KisResourceLocator::storageByLocation(const QString &location) const
1083 {
1084     if (!d->storages.contains(location)) {
1085         qWarning() << "No" << location << "storage defined:" << d->storages.keys();
1086         return 0;
1087     }
1088     KisResourceStorageSP storage = d->storages[location];
1089     if (!storage || !storage->valid()) {
1090         qWarning() << "Could not retrieve the" << location << "storage object or the object is not valid";
1091         return 0;
1092     }
1093 
1094     return storage;
1095 }
1096 
1097 KisResourceStorageSP KisResourceLocator::folderStorage() const
1098 {
1099     return storageByLocation(d->resourceLocation);
1100 }
1101 
1102 KisResourceStorageSP KisResourceLocator::memoryStorage() const
1103 {
1104     return storageByLocation("memory");
1105 }
1106 
1107 KisResourceLocator::ResourceStorage KisResourceLocator::getResourceStorage(int resourceId) const
1108 {
1109     ResourceStorage rs;
1110 
1111     QSqlQuery q;
1112     bool r = q.prepare("SELECT storages.location\n"
1113                        ",      resource_types.name as resource_type\n"
1114                        ",      resources.filename\n"
1115                        "FROM   resources\n"
1116                        ",      storages\n"
1117                        ",      resource_types\n"
1118                        "WHERE  resources.id = :resource_id\n"
1119                        "AND    resources.storage_id = storages.id\n"
1120                        "AND    resource_types.id = resources.resource_type_id");
1121     if (!r) {
1122         qWarning() << "KisResourceLocator::removeResource: could not prepare query." << q.lastError();
1123         return rs;
1124     }
1125 
1126 
1127     q.bindValue(":resource_id", resourceId);
1128 
1129     r = q.exec();
1130     if (!r) {
1131         qWarning() << "KisResourceLocator::removeResource: could not execute query." << q.lastError();
1132         return rs;
1133     }
1134 
1135     q.first();
1136 
1137     QString storageLocation = q.value("location").toString();
1138     QString resourceType= q.value("resource_type").toString();
1139     QString resourceFilename = q.value("filename").toString();
1140 
1141     rs.storageLocation = makeStorageLocationAbsolute(storageLocation);
1142     rs.resourceType = resourceType;
1143     rs.resourceFileName = resourceFilename;
1144 
1145     return rs;
1146 }
1147 
1148 QString KisResourceLocator::makeStorageLocationAbsolute(QString storageLocation) const
1149 {
1150 //    debugResource << "makeStorageLocationAbsolute" << storageLocation;
1151 
1152     if (storageLocation.isEmpty()) {
1153         return resourceLocationBase();
1154     }
1155 
1156     if (QFileInfo(storageLocation).isRelative() && (storageLocation.endsWith(".bundle", Qt::CaseInsensitive)
1157                                              || storageLocation.endsWith(".asl", Qt::CaseInsensitive)
1158                                              || storageLocation.endsWith(".abr", Qt::CaseInsensitive))) {
1159         if (resourceLocationBase().endsWith('/') || resourceLocationBase().endsWith("\\")) {
1160             storageLocation = resourceLocationBase() + storageLocation;
1161         }
1162         else {
1163             storageLocation = resourceLocationBase() + '/' + storageLocation;
1164         }
1165     }
1166 
1167 //    debugResource  << "\t" << storageLocation;
1168     return storageLocation;
1169 }
1170 
1171 bool KisResourceLocator::synchronizeDb()
1172 {
1173     d->errorMessages.clear();
1174 
1175     // Add resource types that have been added since first-time installation.
1176     Q_FOREACH(auto loader, KisResourceLoaderRegistry::instance()->values()) {
1177         KisResourceCacheDb::registerResourceType(loader->resourceType());
1178     }
1179 
1180 
1181     findStorages();
1182     Q_FOREACH(const KisResourceStorageSP storage, d->storages) {
1183         if (!KisResourceCacheDb::synchronizeStorage(storage)) {
1184             d->errorMessages.append(i18n("Could not synchronize %1 with the database", storage->location()));
1185         }
1186     }
1187 
1188     Q_FOREACH(const KisResourceStorageSP storage, d->storages) {
1189         if (!KisResourceCacheDb::addStorageTags(storage)) {
1190             d->errorMessages.append(i18n("Could not synchronize %1 with the database", storage->location()));
1191         }
1192     }
1193 
1194     // now remove the storages that no longer exists
1195     KisStorageModel model;
1196 
1197     QList<QString> storagesToRemove;
1198     for (int i = 0; i < model.rowCount(); i++) {
1199         QModelIndex idx = model.index(i, 0);
1200         QString location = model.data(idx, Qt::UserRole + KisStorageModel::Location).toString();
1201         storagesToRemove << location;
1202     }
1203 
1204     for (int i = 0; i < storagesToRemove.size(); i++) {
1205         QString location = storagesToRemove[i];
1206         if (!d->storages.contains(this->makeStorageLocationAbsolute(location))) {
1207             if (!KisResourceCacheDb::deleteStorage(location)) {
1208                 d->errorMessages.append(i18n("Could not remove storage %1 from the database", this->makeStorageLocationAbsolute(location)));
1209                 qWarning() << d->errorMessages;
1210                 return false;
1211             }
1212             emit storageRemoved(this->makeStorageLocationAbsolute(location));
1213         }
1214     }
1215 
1216 
1217     d->errorMessages <<
1218         KisResourceLoaderRegistry::instance()->executeAllFixups();
1219 
1220     d->resourceCache.clear();
1221     return d->errorMessages.isEmpty();
1222 }
1223 
1224 
1225 QString KisResourceLocator::makeStorageLocationRelative(QString location) const
1226 {
1227 //    debugResource << "makeStorageLocationRelative" << location << "locationbase" << resourceLocationBase();
1228     return location.remove(resourceLocationBase());
1229 }