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 }