diff --git a/libs/resources/KisResourceCacheDb.cpp b/libs/resources/KisResourceCacheDb.cpp index 437acfc8cc..03d3d9593e 100644 --- a/libs/resources/KisResourceCacheDb.cpp +++ b/libs/resources/KisResourceCacheDb.cpp @@ -1,1286 +1,1286 @@ /* * 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 "KisResourceCacheDb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceLocator.h" #include "KisResourceLoaderRegistry.h" const QString dbDriver = "QSQLITE"; const QString KisResourceCacheDb::dbLocationKey { "ResourceCacheDbDirectory" }; const QString KisResourceCacheDb::resourceCacheDbFilename { "resourcecache.sqlite" }; const QString KisResourceCacheDb::databaseVersion { "0.0.2" }; QStringList KisResourceCacheDb::storageTypes { QStringList() }; QStringList KisResourceCacheDb::disabledBundles { QStringList() << "Krita_3_Default_Resources.bundle" }; bool KisResourceCacheDb::s_valid {false}; QString KisResourceCacheDb::s_lastError {QString()}; bool KisResourceCacheDb::isValid() { return s_valid; } QString KisResourceCacheDb::lastError() { return s_lastError; } QSqlError createDatabase(const QString &location) { // NOTE: if the id's of Unknown and Memory in the database // will change, and that will break the queries that // remove Unknown and Memory storages on start-up. KisResourceCacheDb::storageTypes << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(1)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(2)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(3)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(4)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(5)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(6)) ; if (!QSqlDatabase::connectionNames().isEmpty()) { infoResources << "Already connected to resource cache database"; return QSqlError(); } QDir dbLocation(location); if (!dbLocation.exists()) { dbLocation.mkpath(dbLocation.path()); } QSqlDatabase db = QSqlDatabase::addDatabase(dbDriver); db.setDatabaseName(location + "/" + KisResourceCacheDb::resourceCacheDbFilename); //qDebug() << "QuerySize supported" << db.driver()->hasFeature(QSqlDriver::QuerySize); if (!db.open()) { qWarning() << "Could not connect to resource cache database"; return db.lastError(); } QStringList tables = QStringList() << "version_information" << "storage_types" << "resource_types" << "storages" << "tags" << "resources" << "versioned_resources" << "resource_tags" << "metadata" << "tags_storages"; QStringList dbTables; // Verify whether we should recreate the database { bool allTablesPresent = true; dbTables = db.tables(); Q_FOREACH(const QString &table, tables) { if (!dbTables.contains(table)) { allTablesPresent = false; } } bool schemaIsOutDated = false; QString schemaVersion = "Unknown"; QString kritaVersion = "Unknown"; int creationDate = 0; if (dbTables.contains("version_information")) { // Verify the version number QFile f(":/get_version_information.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q(f.readAll()); if (q.size() > 0) { q.first(); schemaVersion = q.value(0).toString(); kritaVersion = q.value(1).toString(); creationDate = q.value(2).toInt(); if (schemaVersion != KisResourceCacheDb::databaseVersion) { // XXX: Implement migration schemaIsOutDated = true; qFatal("Database schema is outdated, migration is needed. Database migration has NOT been implemented yet."); } } } else { return QSqlError("Error executing SQL", "Could not open get_version_information.sql", QSqlError::StatementError); } } if (allTablesPresent && !schemaIsOutDated) { KisUsageLogger::log(QString("Database is up to date. Version: %1, created by Krita %2, at %3") .arg(schemaVersion) .arg(kritaVersion) .arg(QDateTime::fromSecsSinceEpoch(creationDate).toString())); return QSqlError(); } } // Create tables Q_FOREACH(const QString &table, tables) { QFile f(":/create_" + table + ".sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.exec(f.readAll())) { qWarning() << "Could not create table" << table << q.lastError(); return db.lastError(); } infoResources << "Created table" << table; } else { return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(table), QSqlError::StatementError); } } // Create indexes QStringList indexes = QStringList() << "storages" << "versioned_resources"; Q_FOREACH(const QString &index, indexes) { QFile f(":/create_index_" + index + ".sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.exec(f.readAll())) { qWarning() << "Could not create index" << index; return db.lastError(); } infoResources << "Created table" << index; } else { return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(index), QSqlError::StatementError); } } // Fill lookup tables { if (dbTables.contains("storage_types")) { QSqlQuery q; if (!q.exec("DELETE * FROM storage_types;")) { qWarning() << "Could not clear table storage_types" << db.lastError(); } } QFile f(":/fill_storage_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); Q_FOREACH(const QString &originType, KisResourceCacheDb::storageTypes) { QSqlQuery q(sql); q.addBindValue(originType); if (!q.exec()) { qWarning() << "Could not insert" << originType << db.lastError() << q.executedQuery(); return db.lastError(); } } infoResources << "Filled lookup table storage_types"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_storage_types.sql."), QSqlError::StatementError); } } { if (dbTables.contains("resource_types")) { QSqlQuery q; if (!q.exec("DELETE * FROM resource_types;")) { qWarning() << "Could not clear table resource_types" << db.lastError(); } } QFile f(":/fill_resource_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { QSqlQuery q(sql); q.addBindValue(resourceType); if (!q.exec()) { qWarning() << "Could not insert" << resourceType << db.lastError() << q.executedQuery(); return db.lastError(); } } infoResources << "Filled lookup table resource_types"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_resource_types.sql."), QSqlError::StatementError); } } { QFile f(":/fill_version_information.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); QSqlQuery q; q.prepare(sql); q.addBindValue(KisResourceCacheDb::databaseVersion); q.addBindValue(KritaVersionWrapper::versionString()); q.addBindValue(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); if (!q.exec()) { qWarning() << "Could not insert the current version" << db.lastError() << q.executedQuery() << q.boundValues(); return db.lastError(); } infoResources << "Filled version table"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_version_information.sql."), QSqlError::StatementError); } } return QSqlError(); } bool KisResourceCacheDb::initialize(const QString &location) { QSqlError err = createDatabase(location); s_valid = !err.isValid(); switch (err.type()) { case QSqlError::NoError: s_lastError = QString(); break; case QSqlError::ConnectionError: s_lastError = QString("Could not initialize the resource cache database. Connection error: %1").arg(err.text()); break; case QSqlError::StatementError: s_lastError = QString("Could not initialize the resource cache database. Statement error: %1").arg(err.text()); break; case QSqlError::TransactionError: s_lastError = QString("Could not initialize the resource cache database. Transaction error: %1").arg(err.text()); break; case QSqlError::UnknownError: s_lastError = QString("Could not initialize the resource cache database. Unknown error: %1").arg(err.text()); break; } // Delete all storages that are no longer known to the resource locator (including the memory storages) deleteTemporaryResources(); return s_valid; } int KisResourceCacheDb::resourceIdForResource(const QString &resourceName, const QString &resourceType, const QString &storageLocation) { QFile f(":/select_resource_id.sql"); f.open(QFile::ReadOnly); QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare resourceIdForResource" << q.lastError(); return -1; } q.bindValue(":name", resourceName); q.bindValue(":resource_type", resourceType); q.bindValue(":storage_location", storageLocation); if (!q.exec()) { qWarning() << "Could not query resourceIdForResource" << q.boundValues() << q.lastError(); return -1; } if (!q.first()) { return -1; } return q.value(0).toInt(); } bool KisResourceCacheDb::resourceNeedsUpdating(int resourceId, QDateTime timestamp) { QSqlQuery q; if (!q.prepare("SELECT timestamp\n" "FROM versioned_resources\n" "WHERE resource_id = :resource_id\n" "AND version = (SELECT MAX(version)\n" " FROM versioned_resources\n" " WHERE resource_id = :resource_id);")) { qWarning() << "Could not prepare resourceNeedsUpdating statement" << q.lastError(); return false; } q.bindValue(":resource_id", resourceId); if (!q.exec()) { qWarning() << "Could not query for the most recent timestamp" << q.boundValues() << q.lastError(); return false; } if (!q.first()) { qWarning() << "Inconsistent database: could not find a version for resource with Id" << resourceId; return false; } QVariant resourceTimeStamp = q.value(0); if (!resourceTimeStamp.isValid()) { qWarning() << "Could not retrieve timestamp from versioned_resources" << resourceId; return false; } return (timestamp.toSecsSinceEpoch() > resourceTimeStamp.toInt()); } bool KisResourceCacheDb::addResourceVersion(int resourceId, QDateTime timestamp, KisResourceStorageSP storage, KoResourceSP resource) { bool r = false; // Create the new version. The resource is expected to have an updated version number, or // this will fail on the unique index on resource_id, storage_id and version. { QSqlQuery q; r = q.prepare("INSERT INTO versioned_resources \n" "(resource_id, storage_id, version, location, timestamp, md5sum)\n" "VALUES\n" "( :resource_id\n" ", (SELECT id \n" " FROM storages \n" " WHERE location = :storage_location)\n" ", :version\n" ", :location\n" ", :timestamp\n" ", :md5sum\n" ");"); if (!r) { qWarning() << "Could not prepare addResourceVersion statement" << q.lastError(); return r; } q.bindValue(":resource_id", resourceId); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":version", resource->version()); q.bindValue(":location", QFileInfo(resource->filename()).fileName()); q.bindValue(":timestamp", timestamp.toSecsSinceEpoch()); Q_ASSERT(!resource->md5().isEmpty()); q.bindValue(":md5sum", resource->md5().toHex()); r = q.exec(); if (!r) { qWarning() << "Could not execute addResourceVersion statement" << q.boundValues() << q.lastError(); return r; } } // Update the resource itself. The resource gets a new filename when it's updated { QSqlQuery q; r = q.prepare("UPDATE resources\n" "SET name = :name\n" ", filename = :filename\n" ", tooltip = :tooltip\n" ", thumbnail = :thumbnail\n" ", version = :version\n" "WHERE id = :id"); if (!r) { qWarning() << "Could not prepare updateResource statement" << q.lastError(); return r; } qDebug() << resource->name() << resource->filename() << resource->version(); q.bindValue(":name", resource->name()); q.bindValue(":filename", QFileInfo(resource->filename()).fileName()); q.bindValue(":tooltip", i18n(resource->name().toUtf8())); q.bindValue(":version", resource->version()); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); resource->thumbnail().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); q.bindValue(":id", resourceId); r = q.exec(); if (!r) { qWarning() << "Could not update resource" << q.boundValues() << q.lastError(); } } return r; } bool KisResourceCacheDb::addResource(KisResourceStorageSP storage, QDateTime timestamp, KoResourceSP resource, const QString &resourceType) { bool r = false; if (!s_valid) { qWarning() << "KisResourceCacheDb::addResource: The database is not valid"; return false; } if (!resource || !resource->valid()) { qWarning() << "KisResourceCacheDb::addResource: The resource is not valid"; // We don't care about invalid resources and will just ignore them. return true; } bool temporary = (storage->type() == KisResourceStorage::StorageType::Memory); // Check whether it already exists int resourceId = resourceIdForResource(resource->name(), resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (resourceId > -1) { if (resourceNeedsUpdating(resourceId, timestamp)) { r = addResourceVersion(resourceId, timestamp, storage, resource); } return true; } QSqlQuery q; r = q.prepare("INSERT INTO resources \n" "(storage_id, resource_type_id, name, filename, tooltip, thumbnail, status, temporary) \n" "VALUES \n" "((SELECT id " " FROM storages " " WHERE location = :storage_location)\n" ", (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" ", :name\n" ", :filename\n" ", :tooltip\n" ", :thumbnail\n" ", :status\n" ", :temporary);"); if (!r) { qWarning() << "Could not prepare addResource statement" << q.lastError(); return r; } q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":resource_type", resourceType); q.bindValue(":name", resource->name()); q.bindValue(":filename", QFileInfo(resource->filename()).fileName()); q.bindValue(":tooltip", i18n(resource->name().toUtf8())); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); resource->image().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); q.bindValue(":status", 1); q.bindValue(":temporary", (temporary ? 1 : 0)); r = q.exec(); if (!r) { qWarning() << "Could not execute addResource statement" << q.boundValues() << q.lastError(); return r; } resourceId = resourceIdForResource(resource->name(), resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); // Then add a new version r = q.prepare("INSERT INTO versioned_resources\n" "(resource_id, storage_id, version, location, timestamp, md5sum)\n" "VALUES\n" "(:resource_id\n" ", (SELECT id FROM storages\n" " WHERE location = :storage_location)\n" ", :version\n" ", :location\n" ", :timestamp\n" ", :md5sum\n" ");"); if (!r) { qWarning() << "Could not prepare intitial addResourceVersion statement" << q.lastError(); return r; } q.bindValue(":resource_id", resourceId); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":version", resource->version()); q.bindValue(":location", QFileInfo(resource->filename()).fileName()); q.bindValue(":timestamp", timestamp.toSecsSinceEpoch()); //Q_ASSERT(!resource->md5().isEmpty()); if (resource->md5().isEmpty()) { qWarning() << "No md5 for resource" << resource->name() << resourceType << storage->location(); } q.bindValue(":md5sum", resource->md5().toHex()); r = q.exec(); if (!r) { qWarning() << "Could not execute initial addResourceVersion statement" << q.boundValues() << q.lastError(); } return r; } bool KisResourceCacheDb::addResources(KisResourceStorageSP storage, QString resourceType) { QSqlDatabase::database().transaction(); QSharedPointer iter = storage->resources(resourceType); while (iter->hasNext()) { iter->next(); KoResourceSP resource = iter->resource(); if (resource && resource->valid()) { if (!addResource(storage, iter->lastModified(), resource, iter->type())) { qWarning() << "Could not add resource" << QFileInfo(resource->filename()).fileName() << "to the database"; } } } QSqlDatabase::database().commit(); return true; } bool KisResourceCacheDb::setResourceInActive(int resourceId) { if (resourceId < 0) { qWarning() << "Invalid resource id; cannot remove resource"; return false; } QSqlQuery q; bool r = q.prepare("UPDATE resources\n" "SET status = 0\n" "WHERE id = :resource_id"); if (!r) { qWarning() << "Could not prepare removeResource query" << q.lastError(); } q.bindValue(":resource_id", resourceId); if (!q.exec()) { qWarning() << "Could not update resource" << resourceId << "to inactive" << q.lastError(); return false; } return true; } bool KisResourceCacheDb::tagResource(KisResourceStorageSP storage, const QString resourceName, KisTagSP tag, const QString &resourceType) { // Get resource id int resourceId = resourceIdForResource(resourceName, resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (resourceId < 0) { qWarning() << "Could not find resource to tag" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()) << resourceName << resourceType; return false; } // Get tag id int tagId {-1}; { QFile f(":/select_tag.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare select_tag.sql" << q.lastError(); return false; } q.bindValue(":url", tag->url()); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not query tags" << q.boundValues() << q.lastError(); return false; } if (!q.first()) { qWarning() << "Could not find tag" << q.boundValues() << q.lastError(); return false; } tagId = q.value(0).toInt(); } } QSqlQuery q; if (!q.prepare("INSERT INTO resource_tags\n" "(resource_id, tag_id)\n" "VALUES\n" "(:resource_id, :tag_id);")) { qWarning() << "Could not prepare tagResource statement" << q.lastError(); return false; } q.bindValue(":resource_id", resourceId); q.bindValue(":tag_id", tagId); if (!q.exec()) { qWarning() << "Could not execute tagResource stagement" << q.boundValues() << q.lastError(); return false; } return true; } bool KisResourceCacheDb::hasTag(const QString &url, const QString &resourceType) { QFile f(":/select_tag.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare select_tag.sql" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not query tags" << q.boundValues() << q.lastError(); } return q.first(); } qWarning() << "Could not open select_tag.sql"; return false; } bool KisResourceCacheDb::linkTagToStorage(const QString &url, const QString &resourceType, const QString &storageLocation) { QSqlQuery q; if (!q.prepare("INSERT INTO tags_storages\n" "(tag_id, storage_id)\n" "VALUES\n" "(\n" " ( SELECT id\n" " FROM tags\n" " WHERE url = :url\n" " AND resource_type_id = (SELECT id \n" " FROM resource_types\n" " WHERE name = :resource_type)" " )\n" ",( SELECT id\n" " FROM storages\n" " WHERE location = :storage_location\n" " )\n" ");")) { qWarning() << "Could not prepare add tag/storage statement" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storageLocation)); if (!q.exec()) { qWarning() << "Could not insert tag/storage link" << q.boundValues() << q.lastError(); return false; } return true; } bool KisResourceCacheDb::addTag(const QString &resourceType, const QString storageLocation, const QString url, const QString name, const QString comment) { if (hasTag(url, resourceType)) { // Check whether this storage is already registered for this tag QSqlQuery q; if (!q.prepare("SELECT storages.location\n" "FROM tags_storages\n" ", tags\n" ", storages\n" "WHERE tags.id = tags_storages.tag_id\n" "AND storages.id = tags_storages.storage_id\n" "AND tags.resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" "AND tags.url = :url")) { qWarning() << "Could not prepare select tags from tags_storages query" << q.lastError(); } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not execute tags_storages query" << q.boundValues() << q.lastError(); } // If this tag is not yet linked to the storage, link it if (!q.first()) { return linkTagToStorage(url, resourceType, storageLocation); } return true; } // Insert the tag { QSqlQuery q; if (!q.prepare("INSERT INTO tags\n" "(url, name, comment, resource_type_id, active)\n" "VALUES\n" "( :url\n" ", :name\n" ", :comment\n" ", (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" ", 1" ");")) { qWarning() << "Could not prepare insert tag statement" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":name", name); q.bindValue(":comment", comment); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not insert tag" << q.boundValues() << q.lastError(); } } linkTagToStorage(url, resourceType, storageLocation); return true; } bool KisResourceCacheDb::addTags(KisResourceStorageSP storage, QString resourceType) { QSqlDatabase::database().transaction(); QSharedPointer iter = storage->tags(resourceType); while(iter->hasNext()) { iter->next(); if (!addTag(resourceType, storage->location(), iter->url(), iter->name(), iter->comment())) { qWarning() << "Could not add tag" << iter->url() << "to the database"; } if (!iter->tag()->defaultResources().isEmpty()) { Q_FOREACH(const QString &resourceName, iter->tag()->defaultResources()) { if (!tagResource(storage, resourceName, iter->tag(), resourceType)) { qWarning() << "Could not tag resource" << resourceName << "with tag" << iter->url(); } } } } QSqlDatabase::database().commit(); return true; } bool KisResourceCacheDb::addStorage(KisResourceStorageSP storage, bool preinstalled) { bool r = true; if (!s_valid) { qWarning() << "The database is not valid"; return false; } { QSqlQuery q; r = q.prepare("SELECT * FROM storages WHERE location = :location"); q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); r = q.exec(); if (!r) { qWarning() << "Could not select from storages"; return r; } if (q.first()) { qDebug() << "Storage already exists" << storage; return true; } } // Insert the storage; { QSqlQuery q; r = q.prepare("INSERT INTO storages\n " "(storage_type_id, location, timestamp, pre_installed, active, thumbnail)\n" "VALUES\n" "(:storage_type_id, :location, :timestamp, :pre_installed, :active, :thumbnail);"); if (!r) { qWarning() << "Could not prepare query" << q.lastError(); return r; } q.bindValue(":storage_type_id", static_cast(storage->type())); q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":timestamp", storage->timestamp().toSecsSinceEpoch()); q.bindValue(":pre_installed", preinstalled ? 1 : 0); q.bindValue(":active", !disabledBundles.contains(storage->name())); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); storage->thumbnail().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); r = q.exec(); if (!r) qWarning() << "Could not execute query" << q.lastError(); } // Insert the metadata { QStringList keys = storage->metaDataKeys(); if (keys.size() > 0) { QSqlQuery q; if (!q.prepare("SELECT MAX(id)\n" "FROM storages\n")) { qWarning() << "Could not create select storages query for metadata" << q.lastError(); } if (!q.exec()) { qWarning() << "Could not execute select storages query for metadata" << q.lastError(); } q.first(); int id = q.value(0).toInt(); QMap metadata; Q_FOREACH(const QString &key, storage->metaDataKeys()) { metadata[key] = storage->metaData(key); } addMetaDataForId(metadata, id, "storages"); } } Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { if (!KisResourceCacheDb::addResources(storage, resourceType)) { qWarning() << "Failed to add all resources for storage" << storage; r = false; } if (!KisResourceCacheDb::addTags(storage, resourceType)) { qWarning() << "Failed to add all tags for storage" << storage; } } return r; } bool KisResourceCacheDb::deleteStorage(KisResourceStorageSP storage) { { QSqlQuery q; if (!q.prepare("DELETE FROM resources\n" "WHERE id IN (SELECT versioned_resources.resource_id\n" " FROM versioned_resources\n" " WHERE versioned_resources.storage_id = (SELECT storages.id\n" " FROM storages\n" " WHERE storages.location = :location)\n" " );")) { qWarning() << "Could not prepare delete resources query in deleteStorage" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete resources query in deleteStorage" << q.lastError(); return false; } } { QSqlQuery q; if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE storage_id = (SELECT storages.id\n" " FROM storages\n" " WHERE storages.location = :location);")) { qWarning() << "Could not prepare delete versioned_resources query" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete versioned_resources query" << q.lastError(); return false; } } { QSqlQuery q; if (!q.prepare("DELETE FROM storages\n" "WHERE location = :location;")) { qWarning() << "Could not prepare delete storages query" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete storages query" << q.lastError(); return false; } } return true; } bool KisResourceCacheDb::synchronizeStorage(KisResourceStorageSP storage) { qDebug() << "Going to synchronize" << storage->location(); QElapsedTimer t; t.start(); QSqlDatabase::database().transaction(); if (!s_valid) { qWarning() << "KisResourceCacheDb::addResource: The database is not valid"; return false; } bool success = true; // Find the storage in the database QSqlQuery q; if (!q.prepare("SELECT id\n" ", timestamp\n" ", pre_installed\n" "FROM storages\n" "WHERE location = :location\n")) { qWarning() << "Could not prepare storage timestamp statement" << q.lastError(); } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute storage timestamp statement" << q.boundValues() << q.lastError(); } if (!q.first()) { // This is a new storage, the user must have dropped it in the path before restarting Krita, so add it. qDebug() << "Adding storage to the database:" << storage; if (!addStorage(storage, false)) { qWarning() << "Could not add new storage" << storage->name() << "to the database"; success = false; } return true; } // Only check the time stamp for container storages, not the contents if (storage->type() != KisResourceStorage::StorageType::Folder) { qDebug() << storage->location() << "is not a folder, going to check timestamps. Database:" << q.value(1).toInt() << ", File:" << storage->timestamp().toSecsSinceEpoch(); if (!q.value(0).isValid()) { qWarning() << "Could not retrieve timestamp for storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } if (storage->timestamp().toSecsSinceEpoch() > q.value(1).toInt()) { qDebug() << "Deleting" << storage->location() << "because the one on disk is newer."; if (!deleteStorage(storage)) { qWarning() << "Could not delete storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } qDebug() << "Inserting" << storage->location(); if (!addStorage(storage, q.value(2).toBool())) { qWarning() << "Could not add storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } } } else { // This is a folder, we need to check what's on disk and what's in the database // Check whether everything in the storage is in the database QList resourcesToBeDeleted; Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { QStringList resourcesOnDisk; // Check the folder QSharedPointer iter = storage->resources(resourceType); while (iter->hasNext()) { iter->next(); - qDebug() << "\tadding resources" << iter->url(); + // qDebug() << "\tadding resources" << iter->url(); KoResourceSP resource = iter->resource(); resourcesOnDisk << QFileInfo(iter->url()).fileName(); if (resource) { if (!addResource(storage, iter->lastModified(), resource, iter->type())) { qWarning() << "Could not add/update resource" << QFileInfo(resource->filename()).fileName() << "to the database"; success = false; } } } - qDebug() << "Checking for" << resourceType << ":" << resourcesOnDisk; + // qDebug() << "Checking for" << resourceType << ":" << resourcesOnDisk; QSqlQuery q; q.setForwardOnly(true); if (!q.prepare("SELECT resources.id, resources.filename\n" "FROM resources\n" ", resource_types\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare resource by type query" << q.lastError(); success = false; continue; } q.bindValue(":resource_type", resourceType); q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Folder); if (!q.exec()) { qWarning() << "Could not exec resource by type query" << q.boundValues() << q.lastError(); success = false; continue; } while (q.next()) { if (!resourcesOnDisk.contains(q.value(1).toString())) { resourcesToBeDeleted << q.value(0).toInt(); } } } QSqlQuery deleteResources; if (!deleteResources.prepare("DELETE FROM resources WHERE id = :id")) { success = false; qWarning() << "Could not prepare delete Resources query"; } QSqlQuery deleteResourceVersions; if (!deleteResourceVersions.prepare("DELETE FROM versioned_resources WHERE resource_id = :id")) { success = false; qWarning() << "Could not prepare delete Resources query"; } Q_FOREACH(int id, resourcesToBeDeleted) { deleteResourceVersions.bindValue(":id", id); if (!deleteResourceVersions.exec()) { success = false; qWarning() << "Could not delete resource version" << deleteResourceVersions.boundValues() << deleteResourceVersions.lastError(); } deleteResources.bindValue(":id", id); if (!deleteResources.exec()) { success = false; qWarning() << "Could not delete resource" << deleteResources.boundValues() << deleteResources.lastError(); } } } QSqlDatabase::database().commit(); qDebug() << "Synchronizing the storages took" << t.elapsed() << "milliseconds for" << storage->location(); return success; } void KisResourceCacheDb::deleteTemporaryResources() { QSqlDatabase::database().transaction(); QSqlQuery q; if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare delete versioned resources from Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete versioned resources from Unknown or Memory storages query." << q.lastError(); } if (!q.prepare("DELETE FROM resources\n" "WHERE storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare delete resources from Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete resources from Unknown or Memory storages query." << q.lastError(); } if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE resource_id IN (SELECT id FROM resources\n" " WHERE temporary = 1)")) { qWarning() << "Could not prepare delete temporary versioned resources query." << q.lastError(); } if (!q.exec()) { qWarning() << "Could not execute delete temporary versioned resources query." << q.lastError(); } if (!q.prepare("DELETE FROM resources\n" "WHERE temporary = 1")) { qWarning() << "Could not prepare delete temporary resources query." << q.lastError(); return; } if (!q.exec()) { qWarning() << "Could not execute delete temporary resources query." << q.lastError(); } if (!q.prepare("DELETE FROM storages\n" "WHERE storage_type_id == :storage_type\n")) { qWarning() << "Could not prepare delete Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete Unknown or Memory storages query." << q.lastError(); } QSqlDatabase::database().commit(); } bool KisResourceCacheDb::registerResourceType(const QString &resourceType) { // Check whether the type already exists { QSqlQuery q; if (!q.prepare("SELECT count(*)\n" "FROM resource_types\n" "WHERE name = :resource_type\n")) { qWarning() << "Could not prepare select from resource_types query" << q.lastError(); return false; } q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not execute select from resource_types query" << q.lastError(); return false; } q.first(); int rowCount = q.value(0).toInt(); if (rowCount > 0) { return true; } } // if not, add it QFile f(":/fill_resource_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); QSqlQuery q(sql); q.addBindValue(resourceType); if (!q.exec()) { qWarning() << "Could not insert" << resourceType << q.lastError(); return false; } return true; } qWarning() << "Could not open fill_resource_types.sql"; return false; } QMap KisResourceCacheDb::metaDataForId(int id, const QString &tableName) { QMap map; QSqlQuery q; q.setForwardOnly(true); if (!q.prepare("SELECT key\n" ", value\n" "FROM metadata\n" "WHERE foreign_id = :id\n" "AND table_name = :table")) { qWarning() << "Could not prepare metadata query" << q.lastError(); return map; } q.bindValue(":id", id); q.bindValue(":table", tableName); if (!q.exec()) { qWarning() << "Could not execute metadata query" << q.lastError(); return map; } while (q.next()) { QString key = q.value(0).toString(); QByteArray ba = q.value(1).toByteArray(); QDataStream ds(QByteArray::fromBase64(ba)); QVariant value; ds >> value; map[key] = value; } return map; } bool KisResourceCacheDb::updateMetaDataForId(const QMap map, int id, const QString &tableName) { QSqlDatabase::database().transaction(); { QSqlQuery q; if (!q.prepare("DELETE FROM metadata\n" "WHERE foreign_id = :id\n" "AND table_name = :table\n")) { qWarning() << "Could not prepare delete metadata query" << q.lastError(); return false; } q.bindValue(":id", id); q.bindValue(":table", tableName); if (!q.exec()) { QSqlDatabase::database().rollback(); qWarning() << "Could not execute delete metadata query" << q.lastError(); return false; } } if (addMetaDataForId(map, id, tableName)) { QSqlDatabase::database().commit(); } else { QSqlDatabase::database().rollback(); } return true; } bool KisResourceCacheDb::addMetaDataForId(const QMap map, int id, const QString &tableName) { QSqlQuery q; if (!q.prepare("INSERT INTO metadata\n" "(foreign_id, table_name, key, value)\n" "VALUES\n" "(:id, :table, :key, :value)")) { QSqlDatabase::database().rollback(); qWarning() << "Could not create insert metadata query" << q.lastError(); return false; } QMap::const_iterator iter = map.cbegin(); while (iter != map.cend()) { q.bindValue(":id", id); q.bindValue(":table", tableName); q.bindValue(":key", iter.key()); QVariant v = iter.value(); QByteArray ba; QDataStream ds(&ba, QIODevice::WriteOnly); ds << v; ba = ba.toBase64(); q.bindValue(":value", QString::fromLatin1(ba)); if (!q.exec()) { qWarning() << "Could not insert metadata" << q.lastError(); return false; } ++iter; } return true; } diff --git a/libs/resources/KisResourceModel.cpp b/libs/resources/KisResourceModel.cpp index 5413c55793..292df93cd9 100644 --- a/libs/resources/KisResourceModel.cpp +++ b/libs/resources/KisResourceModel.cpp @@ -1,777 +1,777 @@ /* * 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 "KisResourceModel.h" #include #include #include #include #include #include #include #include struct KisAllResourcesModel::Private { QSqlQuery resourcesQuery; QSqlQuery tagQuery; QString resourceType; int columnCount {10}; int cachedRowCount {-1}; }; //static int s_i = 0; KisAllResourcesModel::KisAllResourcesModel(const QString &resourceType, QObject *parent) : QAbstractTableModel(parent) , d(new Private) { //qDebug() << "ResourceModel" << s_i << resourceType; s_i++; connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString&)), this, SLOT(addStorage(const QString&))); connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString&)), this, SLOT(removeStorage(const QString&))); d->resourceType = resourceType; bool r = d->resourcesQuery.prepare("SELECT resources.id\n" ", resources.storage_id\n" ", resources.name\n" ", resources.filename\n" ", resources.tooltip\n" ", resources.thumbnail\n" ", resources.status\n" ", storages.location\n" ", resources.version\n" ", resource_types.name as resource_type\n" ", resources.status as resource_active\n" ", storages.active as storage_active\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resource_types.name = :resource_type\n" "ORDER BY resources.id"); if (!r) { qWarning() << "Could not prepare KisAllResourcesModel query" << d->resourcesQuery.lastError(); } d->resourcesQuery.bindValue(":resource_type", d->resourceType); resetQuery(); r = d->tagQuery.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" "FROM tags\n" ", resource_tags\n" "WHERE tags.active > 0\n" // make sure the tag is active "AND tags.id = resource_tags.tag_id\n" // join tags + resource_tags by tag_id "AND resource_tags.resource_id = :resource_id\n" "ORDER BY tags.id"); // make sure we're looking for tags for a specific resource if (!r) { qWarning() << "Could not prepare TagsForResource query" << d->tagQuery.lastError(); } } KisAllResourcesModel::~KisAllResourcesModel() { delete d; } int KisAllResourcesModel::columnCount(const QModelIndex &/*parent*/) const { return d->columnCount; } QVariant KisAllResourcesModel::data(const QModelIndex &index, int role) const { QVariant v; if (!index.isValid()) return v; if (index.row() > rowCount()) return v; if (index.column() > d->columnCount) return v; bool pos = const_cast(this)->d->resourcesQuery.seek(index.row()); if (pos) { switch(role) { case Qt::DisplayRole: { switch(index.column()) { case Id: return d->resourcesQuery.value("id"); case StorageId: return d->resourcesQuery.value("storage_id"); case Name: return d->resourcesQuery.value("name"); case Filename: return d->resourcesQuery.value("filename"); case Tooltip: return d->resourcesQuery.value("tooltip"); case Thumbnail: { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } case Status: return d->resourcesQuery.value("status"); case Location: return d->resourcesQuery.value("location"); case ResourceType: return d->resourcesQuery.value("resource_type"); case ResourceActive: return d->resourcesQuery.value("resource_active"); case StorageActive: return d->resourcesQuery.value("storage_active"); default: ; }; Q_FALLTHROUGH(); } case Qt::DecorationRole: { if (index.column() == Thumbnail) { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } return QVariant(); } case Qt::ToolTipRole: Q_FALLTHROUGH(); case Qt::StatusTipRole: Q_FALLTHROUGH(); case Qt::WhatsThisRole: return d->resourcesQuery.value("tooltip"); case Qt::UserRole + Id: return d->resourcesQuery.value("id"); case Qt::UserRole + StorageId: return d->resourcesQuery.value("storage_id"); case Qt::UserRole + Name: return d->resourcesQuery.value("name"); case Qt::UserRole + Filename: return d->resourcesQuery.value("filename"); case Qt::UserRole + Tooltip: return d->resourcesQuery.value("tooltip"); case Qt::UserRole + Thumbnail: { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } case Qt::UserRole + Status: return d->resourcesQuery.value("status"); case Qt::UserRole + Location: return d->resourcesQuery.value("location"); case Qt::UserRole + ResourceType: return d->resourcesQuery.value("resource_type"); case Qt::UserRole + Tags: { QVector tags = tagsForResource(d->resourcesQuery.value("id").toInt()); QStringList tagNames; Q_FOREACH(const KisTagSP tag, tags) { tagNames << tag->name(); } return tagNames; } case Qt::UserRole + Dirty: { QString storageLocation = d->resourcesQuery.value("location").toString(); QString filename = d->resourcesQuery.value("filename").toString(); // An uncached resource has not been loaded, so it cannot be dirty if (!KisResourceLocator::instance()->resourceCached(storageLocation, d->resourceType, filename)) { return false; } else { // Now we have to check the resource, but that's cheap since it's been loaded in any case KoResourceSP resource = resourceForIndex(index); return resource->isDirty(); } } case Qt::UserRole + MetaData: { QMap r = KisResourceLocator::instance()->metaDataForResource(d->resourcesQuery.value("id").toInt()); return r; } case Qt::UserRole + KoResourceRole: { KoResourceSP tag = resourceForIndex(index); QVariant response; response.setValue(tag); return response; } case Qt::UserRole + ResourceActive: { return d->resourcesQuery.value("resource_active"); } case Qt::UserRole + StorageActive: { return d->resourcesQuery.value("storage_active"); } default: ; } } return v; } QVariant KisAllResourcesModel::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 Id: v = i18n("Id"); break; case StorageId: v = i18n("Storage ID"); break; case Name: v = i18n("Name"); break; case Filename: v = i18n("File Name"); break; case Tooltip: v = i18n("Tooltip"); break; case Thumbnail: v = i18n("Image"); break; case Status: v = i18n("Status"); break; case Location: v = i18n("Location"); break; case ResourceType: v = i18n("Resource Type"); break; case ResourceActive: v = i18n("Active"); case StorageActive: v = i18n("Storage Active"); default: v = QString::number(section); } return v; } return QAbstractItemModel::headerData(section, orientation, role); } //static int s_i2 {0}; KoResourceSP KisAllResourcesModel::resourceForIndex(QModelIndex index) const { KoResourceSP resource = 0; if (!index.isValid()) return resource; if (index.row() > rowCount()) return resource; if (index.column() > d->columnCount) return resource; //qDebug() << "KisAllResourcesModel::resourceForIndex" << s_i2 << d->resourceType; s_i2++; bool pos = const_cast(this)->d->resourcesQuery.seek(index.row()); if (pos) { int id = d->resourcesQuery.value("id").toInt(); resource = resourceForId(id); } return resource; } KoResourceSP KisAllResourcesModel::resourceForId(int id) const { return KisResourceLocator::instance()->resourceForId(id); } KoResourceSP KisAllResourcesModel::resourceForFilename(QString filename) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resources.id AS id\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resources.filename = :resource_filename\n" "AND resource_types.name = :resource_type\n" "AND resources.status = 1\n" "AND storages.active = 1"); if (!r) { qWarning() << "Could not prepare KisAllResourcesModel query for resource name" << q.lastError(); } q.bindValue(":resource_filename", filename); q.bindValue(":resource_type", d->resourceType); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by filename" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } KoResourceSP KisAllResourcesModel::resourceForName(QString name) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resources.id AS id\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resources.name = :resource_name\n" "AND resource_types.name = :resource_type\n" "AND resources.status = 1\n" "AND storages.active = 1"); if (!r) { qWarning() << "Could not prepare KisAllResourcesModel query for resource name" << q.lastError(); } q.bindValue(":resource_type", d->resourceType); q.bindValue(":resource_name", name); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by name" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } KoResourceSP KisAllResourcesModel::resourceForMD5(const QByteArray md5sum) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resource_id AS id\n" "FROM versioned_resources\n" "WHERE md5sum = :md5sum"); if (!r) { qWarning() << "Could not prepare KisAllResourcesModel query for resource md5" << q.lastError(); } q.bindValue(":md5sum", md5sum.toHex()); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by md5" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } //static int s_i3 {0}; QModelIndex KisAllResourcesModel::indexFromResource(KoResourceSP resource) const { if (!resource || !resource->valid()) return QModelIndex(); //qDebug() << "KisAllResourcesModel::indexFromResource" << s_i3 << d->resourceType; s_i3++; // For now a linear seek to find the first resource with the right id d->resourcesQuery.first(); do { if (d->resourcesQuery.value("id").toInt() == resource->resourceId()) { return createIndex(d->resourcesQuery.at(), 0); } } while (d->resourcesQuery.next()); return QModelIndex(); } //static int s_i4 {0}; bool KisAllResourcesModel::setResourceInactive(const QModelIndex &index) { if (index.row() > rowCount()) return false; if (index.column() > d->columnCount) return false; //qDebug() << "KisAllResourcesModel::setResourceInactive" << s_i4 << d->resourceType; s_i4++; bool pos = d->resourcesQuery.seek(index.row()); if (!pos) return false; int resourceId = d->resourcesQuery.value("id").toInt(); if (!KisResourceLocator::instance()->setResourceInactive(resourceId)) { qWarning() << "Failed to remove resource" << resourceId; return false; } return resetQuery(); } //static int s_i5 {0}; bool KisAllResourcesModel::setResourceInactive(KoResourceSP resource) { if (!resource || !resource->valid()) return false; //qDebug() << "KisAllResourcesModel::remvoeResource 2" << s_i5 << d->resourceType; s_i5++; if (!KisResourceLocator::instance()->setResourceInactive(resource->resourceId())) { qWarning() << "Failed to remove resource" << resource->resourceId(); return false; } return resetQuery(); } //static int s_i6 {0}; bool KisAllResourcesModel::importResourceFile(const QString &filename) { //qDebug() << "KisAllResourcesModel::importResource" << s_i6 << d->resourceType; s_i6++; if (!KisResourceLocator::instance()->importResourceFromFile(d->resourceType, filename)) { qWarning() << "Failed to import resource" << filename; return false; } return resetQuery(); } //static int s_i7 {0}; bool KisAllResourcesModel::addResource(KoResourceSP resource, const QString &storageId) { if (!resource || !resource->valid()) { qWarning() << "Cannot add resource. Resource is null or not valid"; return false; } //qDebug() << "KisAllResourcesModel::addResource" << s_i7 << d->resourceType; s_i7++; if (!KisResourceLocator::instance()->addResource(d->resourceType, resource, storageId)) { qWarning() << "Failed to add resource" << resource->name(); return false; } return resetQuery(); } //static int s_i8 {0}; bool KisAllResourcesModel::updateResource(KoResourceSP resource) { if (!resource || !resource->valid()) { qWarning() << "Cannot update resource. Resource is null or not valid"; return false; } //qDebug() << "KisAllResourcesModel::updateResource" << s_i8 << d->resourceType; s_i8++; if (!KisResourceLocator::instance()->updateResource(d->resourceType, resource)) { qWarning() << "Failed to update resource" << resource; return false; } return resetQuery(); } bool KisAllResourcesModel::renameResource(KoResourceSP resource, const QString &name) { if (!resource || !resource->valid() || name.isEmpty()) { qWarning() << "Cannot rename resources. Resource is NULL or not valid or name is empty"; return false; } resource->setName(name); if (!KisResourceLocator::instance()->updateResource(d->resourceType, resource)) { qWarning() << "Failed to rename resource" << resource << name; return false; } return resetQuery(); } //static int s_i9 {0}; bool KisAllResourcesModel::setResourceMetaData(KoResourceSP resource, QMap metadata) { //qDebug() << "KisAllResourcesModel::setResourceMetaData" << s_i9 << d->resourceType; s_i9++; Q_ASSERT(resource->resourceId() > -1); return KisResourceLocator::instance()->setMetaDataForResource(resource->resourceId(), metadata); } bool KisAllResourcesModel::resetQuery() { - QElapsedTimer t; - t.start(); +// QElapsedTimer t; +// t.start(); beginResetModel(); bool r = d->resourcesQuery.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources" << d->resourcesQuery.lastError() << d->resourcesQuery.boundValues(); } d->cachedRowCount = -1; endResetModel(); - qDebug() << "KisAllResourcesModel::resetQuery for" << d->resourceType << "took" << t.elapsed() << "ms"; +// qDebug() << "KisAllResourcesModel::resetQuery for" << d->resourceType << "took" << t.elapsed() << "ms"; return r; } QVector KisAllResourcesModel::tagsForResource(int resourceId) const { return KisTagModelProvider::tagModel(d->resourceType)->tagsForResource(resourceId); } int KisAllResourcesModel::rowCount(const QModelIndex &) const { if (d->cachedRowCount < 0) { QSqlQuery q; q.prepare("SELECT count(*)\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND resources.storage_id = storages.id\n"); q.bindValue(":resource_type", d->resourceType); q.exec(); q.first(); const_cast(this)->d->cachedRowCount = q.value(0).toInt(); } return d->cachedRowCount; } void KisAllResourcesModel::addStorage(const QString &location) { } void KisAllResourcesModel::removeStorage(const QString &location) { } struct KisResourceModel::Private { ResourceFilter resourceFilter {ShowActiveResources}; StorageFilter storageFilter {ShowActiveStorages}; }; KisResourceModel::KisResourceModel(const QString &type, QObject *parent) : QSortFilterProxyModel(parent) , d(new Private) { setSourceModel(new KisAllResourcesModel(type)); } KisResourceModel::~KisResourceModel() { delete d; } void KisResourceModel::setResourceFilter(ResourceFilter filter) { d->resourceFilter = filter; } void KisResourceModel::setStorageFilter(StorageFilter filter) { d->storageFilter = filter; } KoResourceSP KisResourceModel::resourceForIndex(QModelIndex index) const { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->resourceForIndex(mapToSource(index)); } return 0; } QModelIndex KisResourceModel::indexFromResource(KoResourceSP resource) const { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return mapFromSource(source->indexFromResource(resource)); } return QModelIndex(); } bool KisResourceModel::setResourceInactive(const QModelIndex &index) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->setResourceInactive(mapToSource(index)); } return false; } bool KisResourceModel::importResourceFile(const QString &filename) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->importResourceFile(filename); } return false; } bool KisResourceModel::addResource(KoResourceSP resource, const QString &storageId) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->addResource(resource, storageId); } return false; } bool KisResourceModel::updateResource(KoResourceSP resource) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->updateResource(resource); } return false; } bool KisResourceModel::renameResource(KoResourceSP resource, const QString &name) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->renameResource(resource, name); } return false; } bool KisResourceModel::setResourceInactive(KoResourceSP resource) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->setResourceInactive(resource); } return false; } bool KisResourceModel::setResourceMetaData(KoResourceSP resource, QMap metadata) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->setResourceMetaData(resource, metadata); } return false; } bool KisResourceModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const { return true; } bool KisResourceModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (d->resourceFilter == ShowAllResources && d->storageFilter == ShowAllStorages) { return true; } QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); ResourceFilter resourceActive = (ResourceFilter)sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::ResourceActive).toInt(); StorageFilter storageActive = (StorageFilter)sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::StorageActive).toInt(); if (d->resourceFilter == ShowAllResources) { return (storageActive == d->storageFilter); } if (d->storageFilter == ShowAllStorages) { return (resourceActive == d->resourceFilter); } return ((storageActive == d->storageFilter) && (resourceActive == d->resourceFilter)); } bool KisResourceModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAbstractResourceModel::Name).toString(); QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAbstractResourceModel::Name).toString(); return nameLeft < nameRight; } KoResourceSP KisResourceModel::resourceForId(int id) const { return static_cast(sourceModel())->resourceForId(id); } KoResourceSP KisResourceModel::resourceForFilename(QString fileName) const { return static_cast(sourceModel())->resourceForFilename(fileName); } KoResourceSP KisResourceModel::resourceForName(QString name) const { return static_cast(sourceModel())->resourceForName(name); } KoResourceSP KisResourceModel::resourceForMD5(const QByteArray md5sum) const { return static_cast(sourceModel())->resourceForMD5(md5sum); } QVector KisResourceModel::tagsForResource(int resourceId) const { return static_cast(sourceModel())->tagsForResource(resourceId); } bool KisResourceModel::resetQuery() { return static_cast(sourceModel())->resetQuery(); } diff --git a/libs/resources/KisTagModel.cpp b/libs/resources/KisTagModel.cpp index a1337dcca3..c416a16ece 100644 --- a/libs/resources/KisTagModel.cpp +++ b/libs/resources/KisTagModel.cpp @@ -1,611 +1,611 @@ /* * Copyright (c) 2018 boud * Copyright (c) 2020 Agata Cacko * * 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 "KisTagModel.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QSharedPointer) struct KisTagModel::Private { QSqlQuery query; QSqlQuery tagsForResourceQuery; QSqlQuery resourcesForTagQuery; QString resourceType; int columnCount {5}; int cachedRowCount {-1}; int fakeRowsCount {2}; }; KisTagModel::KisTagModel(const QString &resourceType, QObject *parent) : QAbstractTableModel(parent) , d(new Private()) { d->resourceType = resourceType; if (!d->resourceType.isEmpty()) { prepareQuery(); } } KisTagModel::~KisTagModel() { delete d; } int KisTagModel::rowCount(const QModelIndex &/*parent*/) const { if (d->cachedRowCount < 0) { QSqlQuery q; q.prepare("SELECT count(*)\n" "FROM tags\n" ", resource_types\n" "WHERE active = 1\n" "AND tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n"); q.bindValue(":resource_type", d->resourceType); q.exec(); q.first(); const_cast(this)->d->cachedRowCount = q.value(0).toInt() + d->fakeRowsCount; } return d->cachedRowCount; } int KisTagModel::columnCount(const QModelIndex &/*parent*/) const { return d->columnCount; } QVariant KisTagModel::data(const QModelIndex &index, int role) const { QVariant v; if (!index.isValid()) return v; if (index.row() > rowCount()) return v; if (index.column() > d->columnCount) return v; // The first row is All // XXX: Should we also add an "All Untagged"? if (index.row() < d->fakeRowsCount) { if (index.row() == KisTagModel::All + d->fakeRowsCount) { switch(role) { case Qt::DisplayRole: // fallthrough case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: case Qt::UserRole + Name: return i18n("All"); case Qt::UserRole + Id: return QString::number(KisTagModel::All); case Qt::UserRole + Url: { return "All"; } case Qt::UserRole + ResourceType: return d->resourceType; case Qt::UserRole + Active: return true; case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } else if (index.row() == KisTagModel::AllUntagged + d->fakeRowsCount) { switch(role) { case Qt::DisplayRole: // fallthrough case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: case Qt::UserRole + Name: return i18n("All untagged"); case Qt::UserRole + Id: return QString::number(KisTagModel::AllUntagged); case Qt::UserRole + Url: { return "All untagged"; } case Qt::UserRole + ResourceType: return d->resourceType; case Qt::UserRole + Active: return true; case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } } else { bool pos = const_cast(this)->d->query.seek(index.row() - d->fakeRowsCount); if (pos) { switch(role) { case Qt::DisplayRole: return d->query.value("name"); case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: return d->query.value("comment"); case Qt::UserRole + Id: return d->query.value("id"); case Qt::UserRole + Name: return d->query.value("name"); case Qt::UserRole + Url: return d->query.value("url"); case Qt::UserRole + ResourceType: return d->query.value("resource_type"); case Qt::UserRole + Active: return d->query.value("active"); case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } } return v; } void KisTagModel::setResourceType(const QString &resourceType) { d->resourceType = resourceType; prepareQuery(); } KisTagSP KisTagModel::tagForIndex(QModelIndex index) const { KisTagSP tag = 0; if (!index.isValid()) return tag; if (index.row() > rowCount()) return tag; if (index.column() > columnCount()) return tag; if (index.row() < d->fakeRowsCount) { if (index.row() == KisTagModel::All + d->fakeRowsCount) { tag.reset(new KisTag()); tag->setName(i18n("All")); tag->setUrl("All"); tag->setComment(i18n("All")); tag->setId(KisTagModel::All); tag->setActive(true); tag->setValid(true); } else if (index.row() == KisTagModel::AllUntagged + d->fakeRowsCount) { tag.reset(new KisTag()); tag->setName(i18n("All untagged")); tag->setUrl("All untagged"); tag->setComment(i18n("All untagged")); tag->setId(KisTagModel::AllUntagged); tag->setActive(true); tag->setValid(true); } } else { bool pos = const_cast(this)->d->query.seek(index.row() - d->fakeRowsCount); if (pos) { tag.reset(new KisTag()); tag->setUrl(d->query.value("url").toString()); tag->setName(d->query.value("name").toString()); tag->setComment(d->query.value("comment").toString()); tag->setId(d->query.value("id").toInt()); tag->setActive(d->query.value("active").toBool()); tag->setValid(true); } } return tag; } bool KisTagModel::addEmptyTag(const QString& tagName, QVector taggedResouces) { qDebug() << "bool KisTagModel::addEmptyTag(const QString& tagName, QVector taggedResouces) ### " << tagName; KisTagSP tag = KisTagSP(new KisTag()); tag->setName(tagName); tag->setUrl(tagName); return addEmptyTag(tag, taggedResouces); } bool KisTagModel::addEmptyTag(const KisTagSP tag, QVector taggedResouces) { qDebug() << "bool KisTagModel::addEmptyTag(const KisTagSP tag, QVector taggedResouces) ### " << tag; tag->setValid(true); tag->setActive(true); return addTag(tag, taggedResouces); } bool KisTagModel::addTag(const KisTagSP tag, QVector taggedResouces) { qDebug() << "######################"; qDebug() << "bool KisTagModel::addTag(const KisTagSP tag, QVector taggedResouces) " << tag; if (tag.isNull()) return false; if (!tag) return false; if (!tag->valid()) return false; // A new tag doesn't have an ID yet, that comes from the database if (tag->id() >= 0) return false; if (!KisResourceCacheDb::hasTag(tag->url(), d->resourceType)) { qDebug() << "it doesn't have the tag!" << tag->url() << tag->name() << tag->comment(); if (!KisResourceCacheDb::addTag(d->resourceType, "", tag->url(), tag->name(), tag->comment())) { qWarning() << "Could not add tag" << tag; return false; } } else { QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = 1\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } } qDebug() << "tag = " << tag; if (!taggedResouces.isEmpty()) { qDebug() << "********************"; qDebug() << "tag = " << tag; qDebug() << "tag url = " << tag->url(); KisTagSP tagFromDb = tagByUrl(tag->url()); qDebug() << "tag from db: " << tagFromDb << tag->id(); Q_FOREACH(const KoResourceSP resource, taggedResouces) { if (!resource) continue; if (!resource->valid()) continue; if (resource->resourceId() < 0) continue; tagResource(tagFromDb, resource); } } qDebug() << "^^^^^^^^^^^^^^^^^^^^^^^^^"; return prepareQuery(); } bool KisTagModel::removeTag(const KisTagSP tag) { if (!tag) return false; if (!tag->valid()) return false; if (tag->id() < 0) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = 0\n" "WHERE id = :id")) { qWarning() << "Could not prepare remove tag query" << q.lastError(); return false; } q.bindValue(":id", tag->id()); if (!q.exec()) { qWarning() << "Could not execute remove tag query" << q.lastError(); return false; } return prepareQuery(); } bool KisTagModel::tagResource(const KisTagSP tag, const KoResourceSP resource) { if (!tag) return false; if (!tag->valid()) return false; if (tag->id() < 0) return false; qDebug() << tag << " tag id " << tag->id(); if (!resource) return false; if (!resource->valid()) return false; if (resource->resourceId() < 0) return false; QSqlQuery q; bool r = q.prepare("INSERT INTO resource_tags\n" "(resource_id, tag_id)\n" "VALUES\n" "( (SELECT id\n" " FROM resources\n" " WHERE id = :resource_id)\n" ", (SELECT id\n" " FROM tags\n" " WHERE id = :tag_id\n" " AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type" " \n)" " )\n" ")\n"); if (!r) { qWarning() << "Could not prepare insert into resource tags statement" << q.lastError(); return false; } q.bindValue(":resource_id", resource->resourceId()); q.bindValue(":tag_id", tag->id()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Could not execute insert into resource tags statement" << q.boundValues() << q.lastError(); return false; } KisResourceModelProvider::resetModel(d->resourceType); return true; } bool KisTagModel::untagResource(const KisTagSP tag, const KoResourceSP resource) { if (!tag) return false; if (!tag->valid()) return false; if (!tag->id()) return false; if (!resource) return false; if (!resource->valid()) return false; if (resource->resourceId() < 0) return false; // we need to delete an entry in resource_tags QSqlQuery query; bool r = query.prepare("DELETE FROM resource_tags\n" "WHERE resource_id = :resource_id\n" "AND tag_id = :tag_id"); if (!r) { qWarning() << "Could not prepare KisTagModel query untagResource " << query.lastError(); } query.bindValue(":resource_id", resource->resourceId()); query.bindValue(":tag_id", tag->id()); r = query.exec(); if (!r) { qWarning() << "Could not select tags" << query.lastError(); } KisResourceModelProvider::resetModel(d->resourceType); return true; } bool KisTagModel::renameTag(const KisTagSP tag, const QString &name) { if (!tag) return false; if (!tag->valid()) return false; if (name.isEmpty()) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET name = :name\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":name", name); q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } return prepareQuery(); } bool KisTagModel::changeTagActive(const KisTagSP tag, bool active) { if (!tag) return false; if (!tag->valid()) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = :active\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":active", active); q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } return prepareQuery(); } QVector KisTagModel::tagsForResource(int resourceId) const { bool r = d->tagsForResourceQuery.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" "FROM tags\n" ", resource_tags\n" "WHERE tags.active > 0\n" // make sure the tag is active "AND tags.id = resource_tags.tag_id\n" // join tags + resource_tags by tag_id "AND resource_tags.resource_id = :resource_id\n"); // make sure we're looking for tags for a specific resource if (!r) { qWarning() << "Could not prepare TagsForResource query" << d->tagsForResourceQuery.lastError(); } d->tagsForResourceQuery.bindValue(":resource_id", resourceId); r = d->tagsForResourceQuery.exec(); if (!r) { qWarning() << "Could not select tags for" << resourceId << d->tagsForResourceQuery.lastError() << d->tagsForResourceQuery.boundValues(); } QVector tags; while (d->tagsForResourceQuery.next()) { //qDebug() << d->tagQuery.value(0).toString() << d->tagQuery.value(1).toString() << d->tagQuery.value(2).toString(); KisTagSP tag(new KisTag()); tag->setId(d->tagsForResourceQuery.value("id").toInt()); tag->setUrl(d->tagsForResourceQuery.value("url").toString()); tag->setName(d->tagsForResourceQuery.value("name").toString()); tag->setComment(d->tagsForResourceQuery.value("comment").toString()); tag->setValid(true); tag->setActive(true); tags << tag; } return tags; } KisTagSP KisTagModel::tagByUrl(const QString& tagUrl) const { if (tagUrl.isEmpty()) { return KisTagSP(); } QSqlQuery query; bool r = query.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" ", tags.active\n" ", resource_types.name as resource_type\n" "FROM tags\n" ", resource_types\n" "WHERE tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND tags.active = 1\n" "AND tags.url = :tag_url\n" "AND tags.storage_id in (SELECT id\n" " FROM storages\n" " WHERE storages.active == 1)"); if (!r) { qWarning() << "Could not prepare KisTagModel::tagByUrl query" << query.lastError(); } query.bindValue(":resource_type", d->resourceType); QString tagUrlForSql = tagUrl; query.bindValue(":tag_url", tagUrlForSql); r = query.exec(); if (!r) { qWarning() << "Could not execute KisTagModel::tagByUrl query" << query.lastError(); } KisTagSP tag(new KisTag()); query.next(); tag->setUrl(query.value("url").toString()); tag->setName(query.value("name").toString()); tag->setComment(query.value("comment").toString()); tag->setId(query.value("id").toInt()); tag->setActive(query.value("active").toBool()); tag->setValid(true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(tagUrl == tag->url(), KisTagSP()); return tag; } bool KisTagModel::prepareQuery() { - QElapsedTimer t; - t.start(); +// QElapsedTimer t; +// t.start(); beginResetModel(); bool r = d->query.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" ", tags.active\n" ", resource_types.name as resource_type\n" "FROM tags\n" ", resource_types\n" "WHERE tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND tags.active = 1\n"); if (!r) { qWarning() << "Could not prepare KisTagModel query" << d->query.lastError(); } d->query.bindValue(":resource_type", d->resourceType); r = d->query.exec(); if (!r) { qWarning() << "Could not select tags" << d->query.lastError(); } d->cachedRowCount = -1; endResetModel(); - qDebug() << "bool KisTagModel::prepareQuery() ### RESET TAG MODEL ### for " << d->resourceType << " took " << t.elapsed() << " ms."; + // qDebug() << "bool KisTagModel::prepareQuery() ### RESET TAG MODEL ### for " << d->resourceType << " took " << t.elapsed() << " ms."; return r; }