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