diff --git a/libs/resources/KisResourceLocator.cpp b/libs/resources/KisResourceLocator.cpp index 3f8067ad20..6c476684c4 100644 --- a/libs/resources/KisResourceLocator.cpp +++ b/libs/resources/KisResourceLocator.cpp @@ -1,686 +1,689 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisResourceLocator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourcePaths.h" #include "KisResourceStorage.h" #include "KisResourceCacheDb.h" #include "KisResourceLoaderRegistry.h" #include "KisMemoryStorage.h" #include "KisResourceModelProvider.h" #include +#include const QString KisResourceLocator::resourceLocationKey {"ResourceDirectory"}; class KisResourceLocator::Private { public: QString resourceLocation; QMap storages; QHash, KoResourceSP> resourceCache; QStringList errorMessages; }; KisResourceLocator::KisResourceLocator(QObject *parent) : QObject(parent) , d(new Private()) { } KisResourceLocator *KisResourceLocator::instance() { // Not a regular Q_GLOBAL_STATIC, because we want this deleted as // part of the app destructor. KisResourceLocator *locator = qApp->findChild(QString()); if (!locator) { locator = new KisResourceLocator(qApp); } return locator; } KisResourceLocator::~KisResourceLocator() { } KisResourceLocator::LocatorError KisResourceLocator::initialize(const QString &installationResourcesLocation) { InitializationStatus initializationStatus = InitializationStatus::Unknown; KConfigGroup cfg(KSharedConfig::openConfig(), ""); d->resourceLocation = cfg.readEntry(resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); if (!d->resourceLocation.endsWith('/')) d->resourceLocation += '/'; QFileInfo fi(d->resourceLocation); if (!fi.exists()) { if (!QDir().mkpath(d->resourceLocation)) { d->errorMessages << i18n("1. Could not create the resource location at %1.", d->resourceLocation); return LocatorError::CannotCreateLocation; } initializationStatus = InitializationStatus::FirstRun; } if (!fi.isWritable()) { d->errorMessages << i18n("2. The resource location at %1 is not writable.", d->resourceLocation); return LocatorError::LocationReadOnly; } // Check whether we're updating from an older version if (initializationStatus != InitializationStatus::FirstRun) { QFile fi(d->resourceLocation + '/' + "KRITA_RESOURCE_VERSION"); if (!fi.exists()) { initializationStatus = InitializationStatus::FirstUpdate; } else { fi.open(QFile::ReadOnly); QVersionNumber resource_version = QVersionNumber::fromString(QString::fromUtf8(fi.readAll())); QVersionNumber krita_version = QVersionNumber::fromString(KritaVersionWrapper::versionString()); if (krita_version > resource_version) { initializationStatus = InitializationStatus::Updating; } else { initializationStatus = InitializationStatus::Initialized; } } } if (initializationStatus != InitializationStatus::Initialized) { KisResourceLocator::LocatorError res = firstTimeInstallation(initializationStatus, installationResourcesLocation); if (res != LocatorError::Ok) { return res; } initializationStatus = InitializationStatus::Initialized; } else { if (!synchronizeDb()) { return LocatorError::CannotSynchronizeDb; } } return LocatorError::Ok; } QStringList KisResourceLocator::errorMessages() const { return d->errorMessages; } QString KisResourceLocator::resourceLocationBase() const { return d->resourceLocation; } bool KisResourceLocator::resourceCached(QString storageLocation, const QString &resourceType, const QString &filename) const { storageLocation = makeStorageLocationAbsolute(storageLocation); QPair key = QPair (storageLocation, resourceType + "/" + filename); return d->resourceCache.contains(key); } void KisResourceLocator::loadRequiredResources(KoResourceSP resource) { QList requiredResources = resource->requiredResources(KisGlobalResourcesInterface::instance()); Q_FOREACH (KoResourceSP res, requiredResources) { if (res->resourceId() < 0) { // we put all the embedded resources into the global shared "memory" storage this->addResource(res->resourceType().first, res, "memory"); } } } KoResourceSP KisResourceLocator::resource(QString storageLocation, const QString &resourceType, const QString &filename) { storageLocation = makeStorageLocationAbsolute(storageLocation); QPair key = QPair (storageLocation, resourceType + "/" + filename); KoResourceSP resource; if (d->resourceCache.contains(key)) { resource = d->resourceCache[key]; } else { KisResourceStorageSP storage = d->storages[storageLocation]; if (!storage) { qWarning() << "Could not find storage" << storageLocation; return 0; } resource = storage->resource(resourceType + "/" + filename); // Try to locate bundle in bundle modificated resources location. if (QFileInfo(storage->location() + "_modified" + "/" + resourceType + "/" + filename).exists()) { QFileInfo bundleLoc(storage->location()); storage = d->storages[bundleLoc.path() + "/"]; QString bundleFolderLocation(bundleLoc.fileName() + "_modified" + "/" + resourceType + "/" + filename); resource = storage->resource(bundleFolderLocation); key = QPair (storageLocation, bundleFolderLocation); } else { resource = storage->resource(resourceType + "/" + filename); } if (resource) { KIS_SAFE_ASSERT_RECOVER(!resource->filename().startsWith(resourceType)) {}; d->resourceCache[key] = resource; // load all the embedded resources into temporary "memory" storage loadRequiredResources(resource); } } if (!resource) { qDebug() << "KoResourceSP KisResourceLocator::resource" << storageLocation << resourceType << filename; } Q_ASSERT(resource); resource->setStorageLocation(storageLocation); Q_ASSERT(!resource->storageLocation().isEmpty()); if (resource->resourceId() < 0 || resource->version() < 0) { QSqlQuery q; if (!q.prepare("SELECT resources.id\n" ", resources.version\n" "FROM resources\n" ", storages\n" ", resource_types\n" "WHERE storages.id = resources.storage_id\n" "AND storages.location = :storage_location\n" "AND resource_types.id = resources.resource_type_id\n" "AND resource_types.name = :resource_type\n" "AND resources.filename = :filename")) { qWarning() << "Could not prepare id/version query" << q.lastError(); } q.bindValue(":storage_location", makeStorageLocationRelative(storageLocation)); q.bindValue(":resource_type", resourceType); q.bindValue(":filename", filename); if (!q.exec()) { qWarning() << "Could not execute id/version quert" << q.lastError() << q.boundValues(); } if (!q.first()) { qWarning() << "Could not find the resource in the database" << storageLocation << resourceType << filename; } resource->setResourceId(q.value(0).toInt()); Q_ASSERT(resource->resourceId() >= 0); resource->setVersion(q.value(1).toInt()); Q_ASSERT(resource->version() >= 0); } if (!resource) { qWarning() << "Could not find resource" << resourceType + "/" + filename; return 0; } return resource; } KoResourceSP KisResourceLocator::resourceForId(int resourceId) { ResourceStorage rs = getResourceStorage(resourceId); KoResourceSP r = resource(rs.storageLocation, rs.resourceType, rs.resourceFileName); return r; } bool KisResourceLocator::removeResource(int resourceId, const QString &/*storageLocation*/) { // First remove the resource from the cache ResourceStorage rs = getResourceStorage(resourceId); QPair key = QPair (rs.storageLocation, rs.resourceType + "/" + rs.resourceFileName); d->resourceCache.remove(key); return KisResourceCacheDb::removeResource(resourceId); } bool KisResourceLocator::importResourceFromFile(const QString &resourceType, const QString &fileName, const QString &storageLocation) { KisResourceLoaderBase *loader = KisResourceLoaderRegistry::instance()->loader(resourceType, KisMimeDatabase::mimeTypeForFile(fileName)); QFile f(fileName); if (!f.open(QFile::ReadOnly)) { qWarning() << "Could not open" << fileName << "for loading"; return false; } KoResourceSP resource = loader->load(QFileInfo(fileName).fileName(), f, KisGlobalResourcesInterface::instance()); if (!resource) { qWarning() << "Could not import" << fileName << ": resource doesn't load."; return false; } KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)]; Q_ASSERT(storage); if (!storage->addResource(resource)) { qWarning() << "Could not add resource" << resource->filename() << "to the folder storage"; return false; } return KisResourceCacheDb::addResource(folderStorage(), QFileInfo(resource->filename()).lastModified(), resource, resourceType); } bool KisResourceLocator::addResource(const QString &resourceType, const KoResourceSP resource, const QString &storageLocation) { if (!resource || !resource->valid()) return false; KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)]; Q_ASSERT(storage); //If we have gotten this far and the resource still doesn't have a filename to save to, we should generate one. if (resource->filename().isEmpty()) { if (storageLocation == "memory") { resource->setFilename("memory/" + resourceType + "/" + resource->name()); } else { resource->setFilename(resource->name().split(" ").join("_") + resource->defaultFileExtension()); } } // Save the resource to the storage storage if (!storage->addResource(resource)) { qWarning() << "Could not add resource" << resource->filename() << "to the folder storage"; return false; } // And the database return KisResourceCacheDb::addResource(storage, storage->timeStampForResource(resourceType, resource->filename()), resource, resourceType); } bool KisResourceLocator::updateResource(const QString &resourceType, const KoResourceSP resource) { QString storageLocation = makeStorageLocationAbsolute(resource->storageLocation()); qDebug() << ">>>>>>>>>>>>>>>> storageLocation"<< storageLocation << "resource storage location" << resource->storageLocation(); Q_ASSERT(d->storages.contains(storageLocation)); Q_ASSERT(resource->resourceId() > -1); KisResourceStorageSP storage = d->storages[storageLocation]; resource->updateThumbnail(); int version = resource->version(); // This increments the version in the resource if (!storage->addResource(resource)) { qWarning() << "Failed to save the new version of " << resource->name() << "to storage" << storageLocation; return false; } // Memory storages don't store versioned resources if (storage->type() == KisResourceStorage::StorageType::Memory) { return true; } // It's the storages that keep track of the version Q_ASSERT(resource->version() == version + 1); // The version needs already to have been incremented if (!KisResourceCacheDb::addResourceVersion(resource->resourceId(), QDateTime::currentDateTime(), storage, resource)) { qWarning() << "Failed to add a new version of the resource to the database" << resource->name(); return false; } // Update the resource in the cache QPair key = QPair (storageLocation, resourceType + "/" + QFileInfo(resource->filename()).fileName()); d->resourceCache[key] = resource; return true; } QMap KisResourceLocator::metaDataForResource(int id) const { return KisResourceCacheDb::metaDataForId(id, "resources"); } bool KisResourceLocator::setMetaDataForResource(int id, QMap map) const { return KisResourceCacheDb::updateMetaDataForId(map, id, "resources"); } QMap KisResourceLocator::metaDataForStorage(const QString &storageLocation) const { QMap metadata; if (!d->storages.contains(makeStorageLocationAbsolute(storageLocation))) { qWarning() << storageLocation << "not in" << d->storages.keys(); return metadata; } KisResourceStorageSP st = d->storages[makeStorageLocationAbsolute(storageLocation)]; if (d->storages[makeStorageLocationAbsolute(storageLocation)].isNull()) { return metadata; } Q_FOREACH(const QString key, st->metaDataKeys()) { metadata[key] = st->metaData(key); } return metadata; } void KisResourceLocator::setMetaDataForStorage(const QString &storageLocation, QMap map) const { Q_ASSERT(d->storages.contains(storageLocation)); Q_FOREACH(const QString &key, map.keys()) { d->storages[storageLocation]->setMetaData(key, map[key]); } } bool KisResourceLocator::storageContainsResourceByFile(const QString &storageLocation, const QString &resourceType, const QString &filename) const { QSqlQuery q; if (!q.prepare("SELECT *\n" "FROM storages\n" ", resources\n" ", resource_types\n" "WHERE resources.filename = :filename\n" "AND resources.storage_id = storages.id\n" "AND storages.location = :storage_location\n" "AND resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type")) { qWarning() << "Could not prepare storageCOntainsResourceByFile query" << q.lastError(); return false; } q.bindValue(":filename", filename); q.bindValue(":storage_location", storageLocation); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not execute storageCOntainsResourceByFile query" << q.lastError() << q.boundValues(); return false; } return q.first(); } void KisResourceLocator::purge() { d->resourceCache.clear(); } bool KisResourceLocator::addStorage(const QString &storageLocation, KisResourceStorageSP storage) { Q_ASSERT(!d->storages.contains(storageLocation)); d->storages[storageLocation] = storage; if (!KisResourceCacheDb::addStorage(storage, false)) { d->errorMessages.append(i18n("Could not add %1 to the database", storage->location())); return false; } - KisResourceModelProvider::resetAllModels(); - emit storageAdded(); + + emit storageAdded(storage->location()); return true; } bool KisResourceLocator::removeStorage(const QString &document) { // Cloned documents have a document storage, but that isn't in the locator. if (!d->storages.contains(document)) return true; purge(); + KisResourceStorageSP storage = d->storages. take(document); if (!KisResourceCacheDb::deleteStorage(storage)) { d->errorMessages.append(i18n("Could not remove storage %1 from the database", storage->location())); return false; } KisResourceModelProvider::resetAllModels(); - emit storageRemoved(); + emit storageRemoved(storage->location()); + return true; } bool KisResourceLocator::hasStorage(const QString &document) { return d->storages.contains(document); } KisResourceLocator::LocatorError KisResourceLocator::firstTimeInstallation(InitializationStatus initializationStatus, const QString &installationResourcesLocation) { emit progressMessage(i18n("Krita is running for the first time. Initialization will take some time.")); Q_UNUSED(initializationStatus); Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dir(d->resourceLocation + '/' + folder + '/'); if (!dir.exists()) { if (!QDir().mkpath(d->resourceLocation + '/' + folder + '/')) { d->errorMessages << i18n("3. Could not create the resource location at %1.", dir.path()); return LocatorError::CannotCreateLocation; } } } Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dir(installationResourcesLocation + '/' + folder + '/'); if (dir.exists()) { Q_FOREACH(const QString &entry, dir.entryList(QDir::Files | QDir::Readable)) { QFile f(dir.canonicalPath() + '/'+ entry); if (!QFileInfo(d->resourceLocation + '/' + folder + '/' + entry).exists()) { if (!f.copy(d->resourceLocation + '/' + folder + '/' + entry)) { d->errorMessages << i18n("Could not copy resource %1 to %2", f.fileName(), d->resourceLocation + '/' + folder + '/' + entry); } } } } } // And add bundles and adobe libraries QStringList filters = QStringList() << "*.bundle" << "*.abr" << "*.asl"; QDirIterator iter(installationResourcesLocation, filters, QDir::Files, QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); emit progressMessage(i18n("Installing the resources from bundle %1.", iter.filePath())); QFile f(iter.filePath()); Q_ASSERT(f.exists()); if (!f.copy(d->resourceLocation + '/' + iter.fileName())) { d->errorMessages << i18n("Could not copy resource %1 to %2", f.fileName(), d->resourceLocation); } } QFile f(d->resourceLocation + '/' + "KRITA_RESOURCE_VERSION"); f.open(QFile::WriteOnly); f.write(KritaVersionWrapper::versionString().toUtf8()); f.close(); if (!initializeDb()) { return LocatorError::CannotInitializeDb; } return LocatorError::Ok; } bool KisResourceLocator::initializeDb() { emit progressMessage(i18n("Initializing the resources.")); d->errorMessages.clear(); findStorages(); Q_FOREACH(KisResourceStorageSP storage, d->storages) { QElapsedTimer t; t.start(); if (!KisResourceCacheDb::addStorage(storage, (storage->type() == KisResourceStorage::StorageType::Folder ? false : true))) { d->errorMessages.append(i18n("Could not add storage %1 to the cache database", storage->location())); } qDebug() << "Adding storage" << storage->location() << "to the database took" << t.elapsed() << "ms"; } return (d->errorMessages.isEmpty()); } void KisResourceLocator::findStorages() { d->storages.clear(); // Add the folder KisResourceStorageSP storage = QSharedPointer::create(d->resourceLocation); Q_ASSERT(storage->location() == d->resourceLocation); d->storages[d->resourceLocation] = storage; // Add the memory storage d->storages["memory"] = QSharedPointer::create("memory"); d->storages["memory"]->setMetaData(KisResourceStorage::s_meta_name, i18n("Temporary Resources")); // And add bundles and adobe libraries QStringList filters = QStringList() << "*.bundle" << "*.abr" << "*.asl"; QDirIterator iter(d->resourceLocation, filters, QDir::Files, QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); KisResourceStorageSP storage = QSharedPointer::create(iter.filePath()); d->storages[storage->location()] = storage; } } QList KisResourceLocator::storages() const { return d->storages.values(); } KisResourceStorageSP KisResourceLocator::storageByLocation(const QString &location) const { if (!d->storages.contains(location)) { qWarning() << "No" << location << "storage defined:" << d->storages.keys(); return 0; } KisResourceStorageSP storage = d->storages[location]; if (!storage || !storage->valid()) { qWarning() << "Could not retrieve the" << location << "storage object or the object is not valid"; return 0; } return storage; } KisResourceStorageSP KisResourceLocator::folderStorage() const { return storageByLocation(d->resourceLocation); } KisResourceStorageSP KisResourceLocator::memoryStorage() const { return storageByLocation("memory"); } KisResourceLocator::ResourceStorage KisResourceLocator::getResourceStorage(int resourceId) const { ResourceStorage rs; QSqlQuery q; bool r = q.prepare("SELECT storages.location\n" ", resource_types.name as resource_type\n" ", resources.filename\n" "FROM resources\n" ", storages\n" ", resource_types\n" "WHERE resources.id = :resource_id\n" "AND resources.storage_id = storages.id\n" "AND resource_types.id = resources.resource_type_id"); if (!r) { qWarning() << "KisResourceLocator::removeResource: could not prepare query." << q.lastError(); return rs; } q.bindValue(":resource_id", resourceId); r = q.exec(); if (!r) { qWarning() << "KisResourceLocator::removeResource: could not execute query." << q.lastError(); return rs; } q.first(); QString storageLocation = q.value("location").toString(); QString resourceType= q.value("resource_type").toString(); QString resourceFilename = q.value("filename").toString(); rs.storageLocation = makeStorageLocationAbsolute(storageLocation); rs.resourceType = resourceType; rs.resourceFileName = resourceFilename; return rs; } QString KisResourceLocator::makeStorageLocationAbsolute(QString storageLocation) const { if (storageLocation.isEmpty()) { return resourceLocationBase(); } if (!storageLocation.startsWith('/') && (storageLocation.endsWith("bundle") || storageLocation.endsWith("asl") || storageLocation.endsWith("abr"))) { if (resourceLocationBase().endsWith('/')) { storageLocation = resourceLocationBase() + storageLocation; } else { storageLocation = resourceLocationBase() + '/' + storageLocation; } } return storageLocation; } bool KisResourceLocator::synchronizeDb() { d->errorMessages.clear(); findStorages(); Q_FOREACH(const KisResourceStorageSP storage, d->storages) { if (!KisResourceCacheDb::synchronizeStorage(storage)) { d->errorMessages.append(i18n("Could not synchronize %1 with the database", storage->location())); } } return d->errorMessages.isEmpty(); } QString KisResourceLocator::makeStorageLocationRelative(QString location) const { return location.remove(resourceLocationBase()); } diff --git a/libs/resources/KisResourceLocator.h b/libs/resources/KisResourceLocator.h index 8ffb71e184..6e5cc35a0a 100644 --- a/libs/resources/KisResourceLocator.h +++ b/libs/resources/KisResourceLocator.h @@ -1,284 +1,284 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISRESOURCELOCATOR_H #define KISRESOURCELOCATOR_H #include #include #include #include #include "kritaresources_export.h" #include /** * The KisResourceLocator class locates all resource storages (folders, * bundles, various adobe resource libraries) in the resource location. * * The resource location is always a writable folder. * * There is one resource locator which is owned by the QApplication * object. * * The resource location is configurable, but there is only one location * where Krita will look for resources. */ class KRITARESOURCES_EXPORT KisResourceLocator : public QObject { Q_OBJECT public: // The configuration key that holds the resource location // for this installation of Krita. The location is // QStandardPaths::AppDataLocation by default, but that // can be changed. static const QString resourceLocationKey; static KisResourceLocator *instance(); ~KisResourceLocator(); enum class LocatorError { Ok, LocationReadOnly, CannotCreateLocation, CannotInitializeDb, CannotSynchronizeDb }; /** * @brief initialize Setup the resource locator for use. * * @param installationResourcesLocation the place where the resources * that come packaged with Krita reside. */ LocatorError initialize(const QString &installationResourcesLocation); /** * @brief errorMessages * @return */ QStringList errorMessages() const; /** * @brief resourceLocationBase is the place where all resource storages (folder, * bundles etc. are located. This is a writable place. * @return the base location for all storages. */ QString resourceLocationBase() const; /** * @brief purge purges the local resource cache */ void purge(); /** * @brief addStorage Adds a new resource storage to the database. The storage is * will be marked as not pre-installed. * @param storageLocation a unique name for the given storage * @param storage a storage object * @return true if the storage has been added successfully */ bool addStorage(const QString &storageLocation, KisResourceStorageSP storage); /** * @brief removeStorage removes the temporary storage from the database * @param document the unique name of the document * @return true is successful. */ bool removeStorage(const QString &storageLocation); /** * @brief hasStorage can be used to check whether the given storage already exists * @param storageLocation the name of the storage * @return true if the storage is known */ bool hasStorage(const QString &storageLocation); Q_SIGNALS: void progressMessage(const QString&); /// Emitted whenever a storage is added - void storageAdded(); + void storageAdded(const QString &location); /// Emitted whenever a storage is removed - void storageRemoved(); + void storageRemoved(const QString &location); private: friend class KisResourceModel; friend class KisTagModel; friend class KisStorageModel; friend class TestResourceLocator; friend class TestResourceModel; friend class Resource; friend class KisResourceCacheDb; friend class KisStorageFilterProxyModel; /// @return true if the resource is present in the cache, false if it hasn't been loaded bool resourceCached(QString storageLocation, const QString &resourceType, const QString &filename) const; /** * @brief resource finds a physical resource in one of the storages * @param storageLocation the storage containing the resource. If empty, * this is the folder storage. * * Note that the resource does not have the version or id field set, so this cannot be used directly, * but only through KisResourceModel. * * @param resourceType the type of the resource * @param filename the filename of the resource including extension, but withou * any paths * @return A resource if found, or 0 */ KoResourceSP resource(QString storageLocation, const QString &resourceType, const QString &filename); /** * @brief resourceForId returns the resource with the given id, or 0 if no such resource exists. * The resource object will have its id set but not its version. * @param resourceId the id */ KoResourceSP resourceForId(int resourceId); /** * @brief removeResource * @param resourceId * @param optional: the storage that contains the given resource * @return */ bool removeResource(int resourceId, const QString &storageLocation = QString()); /** * @brief importResourceFromFile * @param resourceType * @param fileName * @param storageLocation: optional, the storage where the resource will be stored. Empty means in the default Folder storage. * @return */ bool importResourceFromFile(const QString &resourceType, const QString &fileName, const QString &storageLocation = QString()); /** * @brief addResource adds the given resource to the database and potentially a storage * @param resourceType the type of the resource * @param resource the actual resource object * @param storageLocation the storage where the resource will be saved. By default this is the the default folder storage. * @return true if successful */ bool addResource(const QString &resourceType, const KoResourceSP resource, const QString &storageLocation = QString()); /** * @brief updateResource * @param resourceType * @param resource * @return */ bool updateResource(const QString &resourceType, const KoResourceSP resource); /** * @brief metaDataForResource * @param id * @return */ QMap metaDataForResource(int id) const; /** * @brief setMetaDataForResource * @param id * @param map * @return */ bool setMetaDataForResource(int id, QMap map) const; /** * @brief metaDataForStorage * @param storage * @return */ QMap metaDataForStorage(const QString &storageLocation) const; /** * @brief setMetaDataForStorage * @param storage * @param map */ void setMetaDataForStorage(const QString &storageLocation, QMap map) const; /** * @brief storageContainsResourceByFile * @param storageLocation * @param filename * @return */ bool storageContainsResourceByFile(const QString &storageLocation, const QString &resourceType, const QString &filename) const; /** * Loads all the resources required by \p resource into the cache * * loadRequiredResources() also loads embedded resources and adds them * into the database. */ void loadRequiredResources(KoResourceSP resource); KisResourceLocator(QObject *parent); KisResourceLocator(const KisResourceLocator&); KisResourceLocator operator=(const KisResourceLocator&); enum class InitializationStatus { Unknown, // We don't know whether Krita has run on this system for this resource location yet Initialized, // Everything is ready to start synchronizing the database FirstRun, // Krita hasn't run for this resource location yet FirstUpdate, // Krita was installed, but it's a version from before the resource locator existed, only user-defined resources are present Updating // Krita is updating from an older version with resource locator }; LocatorError firstTimeInstallation(InitializationStatus initializationStatus, const QString &installationResourcesLocation); // First time installation bool initializeDb(); // Synchronize on restarting Krita to see whether the user has added any storages or resources to the resources location bool synchronizeDb(); void findStorages(); QList storages() const; KisResourceStorageSP storageByLocation(const QString &location) const; KisResourceStorageSP folderStorage() const; KisResourceStorageSP memoryStorage() const; struct ResourceStorage { QString storageLocation; QString resourceType; QString resourceFileName; }; ResourceStorage getResourceStorage(int resourceId) const; QString makeStorageLocationAbsolute(QString storageLocation) const; QString makeStorageLocationRelative(QString location) const; class Private; QScopedPointer d; }; #endif // KISRESOURCELOCATOR_H diff --git a/libs/resources/KisResourceModel.h b/libs/resources/KisResourceModel.h index dc67de708b..24f59e1a14 100644 --- a/libs/resources/KisResourceModel.h +++ b/libs/resources/KisResourceModel.h @@ -1,216 +1,217 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISRESOURCEMODEL_H #define KISRESOURCEMODEL_H #include #include #include #include /** * KisAbstractResourceModel defines the interface for accessing resources * that is used in KisResourceModel and the various filter/proxy models */ class KRITARESOURCES_EXPORT KisAbstractResourceModel { public: virtual ~KisAbstractResourceModel(){} /** * @brief resourceForIndex * @param index * @return */ virtual KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const = 0; /** * @brief indexFromResource * @param resource * @return */ virtual QModelIndex indexFromResource(KoResourceSP resource) const = 0; /** * @brief removeResource * @param index * @return */ virtual bool removeResource(const QModelIndex &index) = 0; /** * @brief importResourceFile * @param filename * @return */ virtual bool importResourceFile(const QString &filename) = 0; /** * @brief addResource adds the given resource to the database and storage * @param resource the resource itself * @param storageId the id of the storage (could be "memory" for temporary * resources, the document's storage id for document storages or empty to save * to the default resources folder * @return true if adding the resoruce succeeded. */ virtual bool addResource(KoResourceSP resource, const QString &storageId = QString()) = 0; /** * @brief updateResource * @param resource * @return */ virtual bool updateResource(KoResourceSP resource) = 0; /** * @brief renameResource name the given resource. The resource will have its * name field reset, will be saved to the storage and there will be a new * version created in the database. * @param resource The resource to rename * @param name The new name * @return true if the operation succeeded. */ virtual bool renameResource(KoResourceSP resource, const QString &name) = 0; /** * @brief removeResource * @param resource * @return */ virtual bool removeResource(KoResourceSP resource) = 0; /** * @brief setResourceMetaData * @param metadata * @return */ virtual bool setResourceMetaData(KoResourceSP resource, QMap metadata) = 0; }; /** * @brief The KisResourceModel class provides access to the cache database * for a particular resource type. Instances should be retrieved using - * KisResourceModelProvider. + * KisResourceModelProvider. All resources are part of this model, active and + * inactive. */ class KRITARESOURCES_EXPORT KisResourceModel : public QAbstractTableModel, public KisAbstractResourceModel { Q_OBJECT public: /** * @brief The Columns enum indexes the columns in the model. To get * the thumbnail for a particular resource, create the index with * QModelIndex(row, Thumbnail). */ enum Columns { Id = 0, StorageId, Name, Filename, Tooltip, Thumbnail, Status, Location, ResourceType, Tags, /// A larger thumbnail for displaying in a tooltip. 200x200 or so. LargeThumbnail, /// A dirty resource is one that has been modified locally but not saved Dirty, /// MetaData is a map of key, value pairs that is associated with this resource MetaData, KoResourceRole }; private: friend class KisResourceModelProvider; friend class TestResourceModel; KisResourceModel(const QString &resourceType, QObject *parent = 0); public: ~KisResourceModel() override; // QAbstractItemModel API int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; // Resources API /** * @brief resourceForIndex returns a properly versioned and id's resource object */ KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const override; KoResourceSP resourceForId(int id) const; /** * resourceForFilename returns the first resource with the given filename that * is active and is in an active store. Note that the filename does not include * a path to the storage, and if there are resources with the same filename * in several active storages, only one resource is returned. * * @return a resource if one is found, or 0 if none are found */ KoResourceSP resourceForFilename(QString fileName) const; /** * resourceForName returns the first resource with the given name that * is active and is in an active store. Note that if there are resources * with the same name in several active storages, only one resource * is returned. * * @return a resource if one is found, or 0 if none are found */ KoResourceSP resourceForName(QString name) const; KoResourceSP resourceForMD5(const QByteArray md5sum) const; QModelIndex indexFromResource(KoResourceSP resource) const override; bool removeResource(const QModelIndex &index) override; bool removeResource(KoResourceSP resource) override; bool importResourceFile(const QString &filename) override; bool addResource(KoResourceSP resource, const QString &storageId = QString()) override; bool updateResource(KoResourceSP resource) override; bool renameResource(KoResourceSP resource, const QString &name) override; bool setResourceMetaData(KoResourceSP resource, QMap metadata) override; QVector tagsForResource(int resourceId) const; Q_SIGNALS: // XXX: emit these signals void beforeResourcesLayoutReset(QModelIndex activateAfterReformat); void afterResourcesLayoutReset(); private: bool resetQuery(); struct Private; Private *const d; }; #endif // KISRESOURCEMODEL_H diff --git a/libs/resources/KisResourceStorage.cpp b/libs/resources/KisResourceStorage.cpp index 262ea9e12b..3944605411 100644 --- a/libs/resources/KisResourceStorage.cpp +++ b/libs/resources/KisResourceStorage.cpp @@ -1,269 +1,280 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisResourceStorage.h" #include #include #include #include "KisFolderStorage.h" #include "KisBundleStorage.h" #include "KisMemoryStorage.h" const QString KisResourceStorage::s_meta_generator("meta:generator"); const QString KisResourceStorage::s_meta_author("dc:author"); const QString KisResourceStorage::s_meta_title("dc:title"); const QString KisResourceStorage::s_meta_description("dc:description"); const QString KisResourceStorage::s_meta_initial_creator("meta:initial-creator"); const QString KisResourceStorage::s_meta_creator("cd:creator"); const QString KisResourceStorage::s_meta_creation_date("meta:creation-data"); const QString KisResourceStorage::s_meta_dc_date("meta:dc-date"); const QString KisResourceStorage::s_meta_user_defined("meta:meta-userdefined"); const QString KisResourceStorage::s_meta_name("meta:name"); const QString KisResourceStorage::s_meta_value("meta:value"); const QString KisResourceStorage::s_meta_version("meta:bundle-version"); Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance); KisStoragePluginRegistry::KisStoragePluginRegistry() { m_storageFactoryMap[KisResourceStorage::StorageType::Folder] = new KisStoragePluginFactory(); m_storageFactoryMap[KisResourceStorage::StorageType::Memory] = new KisStoragePluginFactory(); m_storageFactoryMap[KisResourceStorage::StorageType::Bundle] = new KisStoragePluginFactory(); } void KisStoragePluginRegistry::addStoragePluginFactory(KisResourceStorage::StorageType storageType, KisStoragePluginFactoryBase *factory) { m_storageFactoryMap[storageType] = factory; } KisStoragePluginRegistry *KisStoragePluginRegistry::instance() { return s_instance; } class KisResourceStorage::Private { public: QString name; QString location; bool valid {false}; KisResourceStorage::StorageType storageType {KisResourceStorage::StorageType::Unknown}; QSharedPointer storagePlugin; + int storageId {-1}; }; KisResourceStorage::KisResourceStorage(const QString &location) : d(new Private()) { d->location = location; d->name = QFileInfo(d->location).fileName(); QFileInfo fi(d->location); if (fi.isDir()) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Folder]->create(location)); d->storageType = StorageType::Folder; d->valid = fi.isWritable(); } else if (d->location.endsWith(".bundle")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Bundle]->create(location)); d->storageType = StorageType::Bundle; // XXX: should we also check whether there's a valid metadata entry? Or is this enough? d->valid = (fi.isReadable() && QuaZip(d->location).open(QuaZip::mdUnzip)); } else if (d->location.endsWith(".abr")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::AdobeBrushLibrary]->create(location)); d->storageType = StorageType::AdobeBrushLibrary; d->valid = fi.isReadable(); } else if (d->location.endsWith(".asl")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::AdobeStyleLibrary]->create(location)); d->storageType = StorageType::AdobeStyleLibrary; d->valid = fi.isReadable(); } else if (!d->location.isEmpty()) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Memory]->create(location)); d->name = location; d->storageType = StorageType::Memory; d->valid = true; } else { d->valid = false; } } KisResourceStorage::~KisResourceStorage() { } KisResourceStorage::KisResourceStorage(const KisResourceStorage &rhs) : d(new Private) { *this = rhs; } KisResourceStorage &KisResourceStorage::operator=(const KisResourceStorage &rhs) { if (this != &rhs) { d->name = rhs.d->name; d->location = rhs.d->location; d->storageType = rhs.d->storageType; if (d->storageType == StorageType::Memory) { d->storagePlugin = QSharedPointer(new KisMemoryStorage(*dynamic_cast(rhs.d->storagePlugin.data()))); } else { d->storagePlugin = rhs.d->storagePlugin; } d->valid = false; } return *this; } KisResourceStorageSP KisResourceStorage::clone() const { return KisResourceStorageSP(new KisResourceStorage(*this)); } QString KisResourceStorage::name() const { return d->name; } QString KisResourceStorage::location() const { return d->location; } KisResourceStorage::StorageType KisResourceStorage::type() const { return d->storageType; } QImage KisResourceStorage::thumbnail() const { return d->storagePlugin->thumbnail(); } QDateTime KisResourceStorage::timestamp() const { return d->storagePlugin->timestamp(); } QDateTime KisResourceStorage::timeStampForResource(const QString &resourceType, const QString &filename) const { QFileInfo li(d->location); if (li.suffix().toLower() == "bundle") { QFileInfo bf(d->location + "_modified/" + resourceType + "/" + filename); if (bf.exists()) { return bf.lastModified(); } } else if (QFileInfo(d->location + "/" + resourceType + "/" + filename).exists()) { return QFileInfo(d->location + "/" + resourceType + "/" + filename).lastModified(); } return this->timestamp(); } KisResourceStorage::ResourceItem KisResourceStorage::resourceItem(const QString &url) { return d->storagePlugin->resourceItem(url); } KoResourceSP KisResourceStorage::resource(const QString &url) { return d->storagePlugin->resource(url); } QSharedPointer KisResourceStorage::resources(const QString &resourceType) const { return d->storagePlugin->resources(resourceType); } QSharedPointer KisResourceStorage::tags(const QString &resourceType) const { return d->storagePlugin->tags(resourceType); } bool KisResourceStorage::addTag(const QString &resourceType, KisTagSP tag) { return d->storagePlugin->addTag(resourceType, tag); } bool KisResourceStorage::addResource(KoResourceSP resource) { return d->storagePlugin->addResource(resource->resourceType().first, resource); } void KisResourceStorage::setMetaData(const QString &key, const QVariant &value) { d->storagePlugin->setMetaData(key, value); } bool KisResourceStorage::valid() const { return d->valid; } QStringList KisResourceStorage::metaDataKeys() const { return d->storagePlugin->metaDataKeys(); } QVariant KisResourceStorage::metaData(const QString &key) const { return d->storagePlugin->metaData(key); } +void KisResourceStorage::setStorageId(int storageId) +{ + d->storageId = storageId; +} + +int KisResourceStorage::storageId() +{ + return d->storageId; +} + bool KisStorageVersioningHelper::addVersionedResource(const QString &filename, const QString &saveLocation, KoResourceSP resource) { // Find a new filename for the resource if it already exists: we do not rename old resources, but rename updated resources if (!QFileInfo(filename).exists()) { // Simply save it QFile f(filename); if (!f.open(QFile::WriteOnly)) { qWarning() << "Could not open resource file for writing" << filename; return false; } if (!resource->saveToDevice(&f)) { qWarning() << "Could not save resource file" << filename; return false; } f.close(); } else { QFileInfo fi(filename); // Save the new resource QString newFilename = fi.baseName() + "." + QString("%1").arg(resource->version() + 1, 4, 10, QChar('0')) + "." + fi.suffix(); QFile f(saveLocation + "/" + newFilename); if (!f.open(QFile::WriteOnly)) { qWarning() << "Could not open resource file for writing" << newFilename; return false; } if (!resource->saveToDevice(&f)) { qWarning() << "Could not save resource file" << newFilename; return false; } resource->setFilename(newFilename); f.close(); } return true; }; diff --git a/libs/resources/KisResourceStorage.h b/libs/resources/KisResourceStorage.h index 321023d4b1..c19c98f04c 100644 --- a/libs/resources/KisResourceStorage.h +++ b/libs/resources/KisResourceStorage.h @@ -1,271 +1,278 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISRESOURCESTORAGE_H #define KISRESOURCESTORAGE_H #include #include #include #include #include #include #include #include #include class KisStoragePlugin; class KisStoragePluginFactoryBase { public: virtual ~KisStoragePluginFactoryBase(){} virtual KisStoragePlugin *create(const QString &/*location*/) { return 0; } }; template class KisStoragePluginFactory : public KisStoragePluginFactoryBase { public: KisStoragePlugin *create(const QString &location) override { return new T(location); } }; class KisResourceStorage; typedef QSharedPointer KisResourceStorageSP; /** * The KisResourceStorage class is the base class for * places where resources can be stored. Examples are * folders, bundles or Adobe resource libraries like * ABR files. */ class KRITARESOURCES_EXPORT KisResourceStorage { public: /// A resource item is simply an entry in the storage, struct ResourceItem { virtual ~ResourceItem() {} QString url; QString folder; QString resourceType; QDateTime lastModified; }; class TagIterator { public: virtual ~TagIterator() {} virtual bool hasNext() const = 0; /// The iterator is only valid if next() has been called at least once. virtual void next() = 0; /// The untranslated name of the tag, to be used for making connections to resources virtual QString url() const = 0; /// The translated name of the tag, to be shown in the GUI virtual QString name() const = 0; /// An extra, optional comment for the tag virtual QString comment() const = 0; /// A tag object on which we can set properties and which we can save virtual KisTagSP tag() const = 0; }; class ResourceIterator { public: virtual ~ResourceIterator() {} virtual bool hasNext() const = 0; /// The iterator is only valid if next() has been called at least once. virtual void next() = 0; virtual QString url() const = 0; virtual QString type() const = 0; virtual QDateTime lastModified() const = 0; /// This only loads the resource when called virtual KoResourceSP resource() const = 0; }; enum class StorageType : int { Unknown = 1, Folder = 2, Bundle = 3, AdobeBrushLibrary = 4, AdobeStyleLibrary = 5, Memory = 6 }; static QString storageTypeToString(StorageType storageType) { switch (storageType) { case StorageType::Unknown: return i18n("Unknown"); case StorageType::Folder: return i18n("Folder"); case StorageType::Bundle: return i18n("Bundle"); case StorageType::AdobeBrushLibrary: return i18n("Adobe Brush Library"); case StorageType::AdobeStyleLibrary: return i18n("Adobe Style Library"); case StorageType::Memory: return i18n("Memory"); default: return i18n("Invalid"); } } static QString storageTypeToUntranslatedString(StorageType storageType) { switch (storageType) { case StorageType::Unknown: return ("Unknown"); case StorageType::Folder: return ("Folder"); case StorageType::Bundle: return ("Bundle"); case StorageType::AdobeBrushLibrary: return ("Adobe Brush Library"); case StorageType::AdobeStyleLibrary: return ("Adobe Style Library"); case StorageType::Memory: return ("Memory"); default: return ("Invalid"); } } KisResourceStorage(const QString &location); ~KisResourceStorage(); KisResourceStorage(const KisResourceStorage &rhs); KisResourceStorage &operator=(const KisResourceStorage &rhs); KisResourceStorageSP clone() const; /// The filename of the storage if it's a bundle or Adobe Library. This can /// also be empty (for the folder storage) or "memory" for the storage for /// temporary resources, a UUID for storages associated with documents. QString name() const; /// The absolute location of the storage QString location() const; /// true if the storage exists and can be used bool valid() const; /// The type of the storage StorageType type() const; /// The icond for the storage QImage thumbnail() const; /// The time and date when the storage was last modified, or created /// for memory storages. QDateTime timestamp() const; /// The time and date when the resource was last modified /// For filestorage QDateTime timeStampForResource(const QString &resourceType, const QString &filename) const; /// And entry in the storage; this is not the loaded resource ResourceItem resourceItem(const QString &url); /// The loaded resource for an entry in the storage KoResourceSP resource(const QString &url); /// An iterator over all the resources in the storage QSharedPointer resources(const QString &resourceType) const; /// An iterator over all the tags in the resource QSharedPointer tags(const QString &resourceType) const; /// Adds a tag to the storage, however, it does not store the links between /// tags and resources. bool addTag(const QString &resourceType, KisTagSP tag); /// Adds the given resource to the storage. bool addResource(KoResourceSP resource); static const QString s_meta_generator; static const QString s_meta_author; static const QString s_meta_title; static const QString s_meta_description; static const QString s_meta_initial_creator; static const QString s_meta_creator; static const QString s_meta_creation_date; static const QString s_meta_dc_date; static const QString s_meta_user_defined; static const QString s_meta_name; static const QString s_meta_value; static const QString s_meta_version; void setMetaData(const QString &key, const QVariant &value); QStringList metaDataKeys() const; QVariant metaData(const QString &key) const; private: + friend class KisStorageModel; + friend class KisResourceLocator; + friend class KisResourceCacheDb; + + void setStorageId(int storageId); + int storageId(); + class Private; QScopedPointer d; }; inline QDebug operator<<(QDebug dbg, const KisResourceStorageSP storage) { if (storage.isNull()) { dbg.nospace() << "[RESOURCESTORAGE] NULL"; } else { dbg.nospace() << "[RESOURCESTORAGE] Name: " << storage->name() << " Version: " << storage->location() << " Valid: " << storage->valid() << " Storage: " << KisResourceStorage::storageTypeToString(storage->type()) << " Timestamp: " << storage->timestamp(); } return dbg.space(); } class KRITARESOURCES_EXPORT KisStoragePluginRegistry { public: KisStoragePluginRegistry(); void addStoragePluginFactory(KisResourceStorage::StorageType storageType, KisStoragePluginFactoryBase *factory); static KisStoragePluginRegistry *instance(); private: friend class KisResourceStorage; QMap m_storageFactoryMap; }; class KisStorageVersioningHelper { public: static bool addVersionedResource(const QString &filename, const QString &saveLocation, KoResourceSP resource); }; #endif // KISRESOURCESTORAGE_H diff --git a/libs/resources/KisStorageModel.cpp b/libs/resources/KisStorageModel.cpp index 1187675a47..69354b1541 100644 --- a/libs/resources/KisStorageModel.cpp +++ b/libs/resources/KisStorageModel.cpp @@ -1,291 +1,317 @@ /* * Copyright (c) 2019 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisStorageModel.h" #include #include #include #include Q_GLOBAL_STATIC(KisStorageModel, s_instance) struct KisStorageModel::Private { int cachedRowCount {-1}; - QSqlQuery query; + QList storages; }; KisStorageModel::KisStorageModel(QObject *parent) : QAbstractTableModel(parent) , d(new Private()) { - prepareQuery(); - connect(KisResourceLocator::instance(), SIGNAL(storageAdded()), this, SLOT(resetQuery())); - connect(KisResourceLocator::instance(), SIGNAL(storageRemoved()), this, SLOT(resetQuery())); + connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString&)), this, SLOT(addStorage(const QString&))); + connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString&)), this, SLOT(removeStorage(const QString&))); + + QSqlQuery query; + + bool r = query.prepare("SELECT location\n" + "FROM storages\n"); + if (!r) { + qWarning() << "Could not prepare KisStorageModel query" << query.lastError(); + } + + r = query.exec(); + + if (!r) { + qWarning() << "Could not execute KisStorageModel query" << query.lastError(); + } + + while (query.next()) { + d->storages << query.value(0).toString(); + } } KisStorageModel *KisStorageModel::instance() { return s_instance; } KisStorageModel::~KisStorageModel() { } int KisStorageModel::rowCount(const QModelIndex & /*parent*/) const { - if (d->cachedRowCount < 0) { - QSqlQuery q; - q.prepare("SELECT count(*)\n" - "FROM storages\n"); - q.exec(); - q.first(); - - const_cast(this)->d->cachedRowCount = q.value(0).toInt(); - } - return d->cachedRowCount; + return d->storages.size(); } int KisStorageModel::columnCount(const QModelIndex &/*parent*/) const { return 8; } QVariant KisStorageModel::data(const QModelIndex &index, int role) const { QVariant v; if (!index.isValid()) return v; if (index.row() > rowCount()) return v; if (index.column() > (int)MetaData) return v; - bool pos = d->query.seek(index.row()); + QString location = d->storages.at(index.row()); + + QSqlQuery query; + + bool r = query.prepare("SELECT storages.id as id\n" + ", storage_types.name as storage_type\n" + ", location\n" + ", timestamp\n" + ", pre_installed\n" + ", active\n" + ", thumbnail\n" + "FROM storages\n" + ", storage_types\n" + "WHERE storages.storage_type_id = storage_types.id\n" + "AND location = :location"); + + if (!r) { + qWarning() << "Could not prepare KisStorageModel data query" << query.lastError(); + return v; + } + + query.bindValue(":location", location); + + r = query.exec(); + + if (!r) { + qWarning() << "Could not execute KisStorageModel data query" << query.lastError() << query.boundValues(); + return v; + } + + if (!query.first()) { + qWarning() << "KisStorageModel data query did not return anything"; + return v; + } - if (pos) { - switch(role) { - case Qt::DisplayRole: + switch(role) { + case Qt::DisplayRole: + { + switch(index.column()) { + case Id: + return query.value("id"); + case StorageType: + return query.value("storage_type"); + case Location: + return query.value("location"); + case TimeStamp: + return query.value("timestamp"); + case PreInstalled: + return query.value("pre_installed"); + case Active: + return query.value("active"); + case Thumbnail: { - switch(index.column()) { - case Id: - return d->query.value("id"); - case StorageType: - return d->query.value("storage_type"); - case Location: - return d->query.value("location"); - case TimeStamp: - return d->query.value("timestamp"); - case PreInstalled: - return d->query.value("pre_installed"); - case Active: - return d->query.value("active"); - case Thumbnail: - { - QByteArray ba = d->query.value("thumbnail").toByteArray(); - QBuffer buf(&ba); - buf.open(QBuffer::ReadOnly); - QImage img; - img.load(&buf, "PNG"); - return QVariant::fromValue(img); - } - case DisplayName: - { - QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); - QVariant name = d->query.value("location"); - if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].isNull()) { - name = r[KisResourceStorage::s_meta_name]; - } - else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].isNull()) { - name = r[KisResourceStorage::s_meta_title]; - } - return name; - } - case Qt::UserRole + MetaData: - { - QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); - return r; - } - default: - return v; - } + QByteArray ba = query.value("thumbnail").toByteArray(); + QBuffer buf(&ba); + buf.open(QBuffer::ReadOnly); + QImage img; + img.load(&buf, "PNG"); + return QVariant::fromValue(img); } - case Qt::UserRole + Id: - return d->query.value("id"); - case Qt::UserRole + DisplayName: + case DisplayName: { - QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); - QVariant name = d->query.value("location"); + QMap r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString()); + QVariant name = query.value("location"); if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].isNull()) { name = r[KisResourceStorage::s_meta_name]; } else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].isNull()) { name = r[KisResourceStorage::s_meta_title]; } return name; } - case Qt::UserRole + StorageType: - return d->query.value("storage_type"); - case Qt::UserRole + Location: - return d->query.value("location"); - case Qt::UserRole + TimeStamp: - return d->query.value("timestamp"); - case Qt::UserRole + PreInstalled: - return d->query.value("pre_installed"); - case Qt::UserRole + Active: - return d->query.value("active"); - case Qt::UserRole + Thumbnail: - { - QByteArray ba = d->query.value("thumbnail").toByteArray(); - QBuffer buf(&ba); - buf.open(QBuffer::ReadOnly); - QImage img; - img.load(&buf, "PNG"); - return QVariant::fromValue(img); - } case Qt::UserRole + MetaData: { - QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); + QMap r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString()); return r; } - default: - ; + return v; } } - return v; + case Qt::UserRole + Id: + return query.value("id"); + case Qt::UserRole + DisplayName: + { + QMap r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString()); + QVariant name = query.value("location"); + if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].isNull()) { + name = r[KisResourceStorage::s_meta_name]; + } + else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].isNull()) { + name = r[KisResourceStorage::s_meta_title]; + } + return name; + } + case Qt::UserRole + StorageType: + return query.value("storage_type"); + case Qt::UserRole + Location: + return query.value("location"); + case Qt::UserRole + TimeStamp: + return query.value("timestamp"); + case Qt::UserRole + PreInstalled: + return query.value("pre_installed"); + case Qt::UserRole + Active: + return query.value("active"); + case Qt::UserRole + Thumbnail: + { + QByteArray ba = query.value("thumbnail").toByteArray(); + QBuffer buf(&ba); + buf.open(QBuffer::ReadOnly); + QImage img; + img.load(&buf, "PNG"); + return QVariant::fromValue(img); + } + case Qt::UserRole + MetaData: + { + QMap r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString()); + return r; + } + + default: + ; + } + return v; } bool KisStorageModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid()) { if (role == Qt::CheckStateRole) { - QSqlQuery q; - bool r = q.prepare("UPDATE storages\n" - "SET active = :active\n" - "WHERE id = :id\n"); - q.bindValue(":active", value); - q.bindValue(":id", index.data(Qt::UserRole + Id)); + QSqlQuery query; + bool r = query.prepare("UPDATE storages\n" + "SET active = :active\n" + "WHERE id = :id\n"); + query.bindValue(":active", value); + query.bindValue(":id", index.data(Qt::UserRole + Id)); + if (!r) { - qWarning() << "Could not prepare KisStorageModel update query" << d->query.lastError(); + qWarning() << "Could not prepare KisStorageModel update query" << query.lastError(); return false; } - r = q.exec(); + + r = query.exec(); + if (!r) { - qWarning() << "Could not execute KisStorageModel update query" << d->query.lastError(); + qWarning() << "Could not execute KisStorageModel update query" << query.lastError(); return false; } } } - QAbstractTableModel::setData(index, value, role); - KisResourceModelProvider::resetAllModels(); - return prepareQuery(); + + return true; } Qt::ItemFlags KisStorageModel::flags(const QModelIndex &index) const { return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } KisResourceStorageSP KisStorageModel::storageForIndex(const QModelIndex &index) const { - return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(index.data(Qt::UserRole + Location).toString())); + + if (!index.isValid()) return 0; + if (index.row() > rowCount()) return 0; + if (index.column() > (int)MetaData) return 0; + + QString location = d->storages.at(index.row()); + + return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(location)); } QVariant KisStorageModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant v = QVariant(); if (role != Qt::DisplayRole) { return v; } if (orientation == Qt::Horizontal) { switch(section) { case 0: v = i18n("Id"); break; case 1: v = i18n("Type"); break; case 2: v = i18n("Location"); break; case 3: v = i18n("Creation Date"); break; case 4: v = i18n("Preinstalled"); break; case 5: v = i18n("Active"); break; case 6: v = i18n("Thumbnail"); break; case 7: v = i18n("Name"); break; default: v = QString::number(section); } return v; } return QAbstractTableModel::headerData(section, orientation, role); } -bool KisStorageModel::resetQuery() +void KisStorageModel::addStorage(const QString &location) { - QElapsedTimer t; - t.start(); + qDebug() << "before" << d->storages << rowCount(); - beginResetModel(); - bool r = d->query.exec(); - if (!r) { - qWarning() << "Could not select storages" << d->query.lastError() << d->query.boundValues(); - } - d->cachedRowCount = -1; - - endResetModel(); - qDebug() << "KisStorageModel::resetQuery took" << t.elapsed() << "ms"; + beginInsertRows(QModelIndex(), rowCount(), 1); + d->storages.append(location); + endInsertRows(); - return r; + qDebug() << "after" << d->storages << rowCount(); } -bool KisStorageModel::prepareQuery() +void KisStorageModel::removeStorage(const QString &location) { - beginResetModel(); - bool r = d->query.prepare("SELECT storages.id as id\n" - ", storage_types.name as storage_type\n" - ", location\n" - ", timestamp\n" - ", pre_installed\n" - ", active\n" - ", thumbnail\n" - "FROM storages\n" - ", storage_types\n" - "WHERE storages.storage_type_id = storage_types.id\n"); - if (!r) { - qWarning() << "Could not prepare KisStorageModel query" << d->query.lastError(); - } - r = d->query.exec(); - if (!r) { - qWarning() << "Could not execute KisStorageModel query" << d->query.lastError(); - } - d->cachedRowCount = -1; - endResetModel(); - return r; + int index = d->storages.indexOf(location); + beginRemoveRows(QModelIndex(), index, index); + d->storages.removeAt(index); + endRemoveRows(); } + + diff --git a/libs/resources/KisStorageModel.h b/libs/resources/KisStorageModel.h index 3d13c4f92f..a1b7e339ae 100644 --- a/libs/resources/KisStorageModel.h +++ b/libs/resources/KisStorageModel.h @@ -1,82 +1,83 @@ /* * Copyright (c) 2019 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISSTORAGEMODEL_H #define KISSTORAGEMODEL_H #include #include #include #include "KisResourceStorage.h" #include "kritaresources_export.h" /** * KisStorageModel provides a model of all registered storages, like * the folder storages, the bundle storages or the memory storages. Note * that inactive storages are also part of this model. */ class KRITARESOURCES_EXPORT KisStorageModel : public QAbstractTableModel { Q_OBJECT public: enum Columns { Id = 0, StorageType, Location, TimeStamp, PreInstalled, Active, Thumbnail, DisplayName, MetaData }; KisStorageModel(QObject *parent = 0); ~KisStorageModel() override; - static KisStorageModel * instance(); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; Qt::ItemFlags flags(const QModelIndex &index) const override; KisResourceStorageSP storageForIndex(const QModelIndex &index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; -public Q_SLOTS: +private Q_SLOTS: - bool resetQuery(); + friend class KisResourceLocator; + + void addStorage(const QString &location); + void removeStorage(const QString &location); private: KisStorageModel(const KisStorageModel&); KisStorageModel operator=(const KisStorageModel&); - bool prepareQuery(); - struct Private; QScopedPointer d; }; #endif // KISSTORAGEMODEL_H diff --git a/libs/resources/tests/TestResourceLocator.cpp b/libs/resources/tests/TestResourceLocator.cpp index 86b636ae0f..7cb22b6212 100644 --- a/libs/resources/tests/TestResourceLocator.cpp +++ b/libs/resources/tests/TestResourceLocator.cpp @@ -1,186 +1,188 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestResourceLocator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestResourceLocator::initTestCase() { ResourceTestHelper::initTestDb(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); ResourceTestHelper::createDummyLoaderRegistry(); } void TestResourceLocator::testLocatorInitialization() { KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dstDir(m_dstLocation + '/' + folder + '/'); QDir srcDir(m_srcLocation + '/' + folder + '/'); QVERIFY(dstDir.exists()); QVERIFY(dstDir.entryList(QDir::Files | QDir::NoDotAndDotDot) == srcDir.entryList(QDir::Files | QDir::NoDotAndDotDot)); } QFile f(m_dstLocation + '/' + "KRITA_RESOURCE_VERSION"); QVERIFY(f.exists()); f.open(QFile::ReadOnly); QVersionNumber version = QVersionNumber::fromString(QString::fromUtf8(f.readAll())); QVERIFY(version == QVersionNumber::fromString(KritaVersionWrapper::versionString())); } void TestResourceLocator::testStorageInitialization() { Q_FOREACH(KisResourceStorageSP storage, m_locator->storages()) { QVERIFY(KisResourceCacheDb::addStorage(storage, true)); } QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM storages"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), m_locator->storages().count()); } void TestResourceLocator::testLocatorSynchronization() { QVERIFY(m_locator->synchronizeDb()); { QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM resources"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), 7); } { QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM tags"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), 1); } } void TestResourceLocator::testResourceLocationBase() { QCOMPARE(m_locator->resourceLocationBase(), QString(FILES_DEST_DIR)); } void TestResourceLocator::testResource() { KoResourceSP res = m_locator->resource("", ResourceType::PaintOpPresets, "test0.kpp"); QVERIFY(res); } void TestResourceLocator::testResourceForId() { KoResourceSP res = m_locator->resource("", ResourceType::PaintOpPresets, "test0.kpp"); int resourceId = KisResourceCacheDb::resourceIdForResource("test0.kpp", ResourceType::PaintOpPresets, ""); QVERIFY(resourceId > -1); KoResourceSP res2 = m_locator->resourceForId(resourceId); QCOMPARE(res, res2); } void TestResourceLocator::testStorageContainsResourceByFile() { QVERIFY(m_locator->storageContainsResourceByFile("", "paintoppresets", "test0.kpp") > 0); QVERIFY(m_locator->storageContainsResourceByFile("", "paintoppresets", "XSLKDJSADLKSAJDA") == 0); } void TestResourceLocator::testDocumentStorage() { const QString &documentName("document"); KisResourceModel *model = KisResourceModelProvider::resourceModel(ResourceType::PaintOpPresets); int rowcount = model->rowCount(); KisResourceStorageSP documentStorage = QSharedPointer::create(documentName); KoResourceSP resource(new DummyResource("test")); documentStorage->addResource(resource); m_locator->addStorage(documentName, documentStorage); QVERIFY(model->rowCount() > rowcount); QVERIFY(m_locator->hasStorage(documentName)); + m_locator->removeStorage(documentName); QVERIFY(!m_locator->hasStorage(documentName)); + QVERIFY(model->rowCount() == rowcount); } void TestResourceLocator::cleanupTestCase() { //ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } QTEST_MAIN(TestResourceLocator) diff --git a/libs/resources/tests/TestStorageModel.cpp b/libs/resources/tests/TestStorageModel.cpp index da12267ea3..5de19c159f 100644 --- a/libs/resources/tests/TestStorageModel.cpp +++ b/libs/resources/tests/TestStorageModel.cpp @@ -1,155 +1,158 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestStorageModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestStorageModel::initTestCase() { ResourceTestHelper::initTestDb(); ResourceTestHelper::createDummyLoaderRegistry(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { qDebug() << "Could not initialize KisResourceCacheDb on" << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } QVERIFY(KisResourceCacheDb::isValid()); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); } void TestStorageModel::testRowCount() { QSqlQuery q; QVERIFY(q.prepare("SELECT count(*)\n" "FROM storages")); QVERIFY(q.exec()); q.first(); int rowCount = q.value(0).toInt(); KisStorageModel storageModel; QCOMPARE(storageModel.rowCount(), rowCount); } void TestStorageModel::testSetActive() { KisStorageModel storageModel; + for (int i = 0; i < storageModel.rowCount(); ++i) { + QModelIndex idx = storageModel.index(i, 0); storageModel.setData(idx, QVariant(true), Qt::CheckStateRole); + + idx = storageModel.index(i, 0); QVERIFY(idx.data(Qt::UserRole + KisStorageModel::Active).toBool() == true); storageModel.setData(idx, QVariant(false), Qt::CheckStateRole); idx = storageModel.index(i, 0); QVERIFY(idx.data(Qt::UserRole + KisStorageModel::Active).toBool() == false); } } void TestStorageModel::cleanupTestCase() { ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } void TestStorageModel::testMetaData() { KisStorageModel storageModel; int rowCount = storageModel.rowCount(); KisResourceStorageSP storage {new KisResourceStorage("My Named Memory Storage")}; KisResourceLocator::instance()->addStorage("My Named Memory Storage", storage); storage->setMetaData(KisResourceStorage::s_meta_name, "My Named Memory Storage"); - storageModel.resetQuery(); QVERIFY(storageModel.rowCount() > rowCount); QModelIndex idx; for (int row = 0; row < storageModel.rowCount(); ++row) { idx = storageModel.index(row, 7); KisResourceStorageSP st = storageModel.storageForIndex(idx); QVERIFY(st); if (st == storage) { break; } } QVERIFY(idx.isValid()); QString displayName = storageModel.data(idx, Qt::DisplayRole).toString(); QCOMPARE("My Named Memory Storage", displayName); idx = storageModel.index(idx.row(), 0); QMap metadata = storageModel.data(idx, Qt::UserRole + KisStorageModel::MetaData).toMap(); QVERIFY(metadata.contains(KisResourceStorage::s_meta_name)); QVERIFY(metadata[KisResourceStorage::s_meta_name] == "My Named Memory Storage"); } QTEST_MAIN(TestStorageModel)