File indexing completed on 2024-05-19 04:27:37
0001 /* 0002 * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 #include "KisResourceCacheDb.h" 0007 0008 #include <QtSql> 0009 #include <QStandardPaths> 0010 #include <QDir> 0011 #include <QDirIterator> 0012 #include <QStringList> 0013 #include <QElapsedTimer> 0014 #include <QDataStream> 0015 #include <QByteArray> 0016 #include <QMessageBox> 0017 0018 #include <KritaVersionWrapper.h> 0019 0020 #include <klocalizedstring.h> 0021 #include <KisBackup.h> 0022 0023 #include <kis_debug.h> 0024 #include <KisUsageLogger.h> 0025 0026 #include "KisResourceLocator.h" 0027 #include "KisResourceLoaderRegistry.h" 0028 0029 #include "ResourceDebug.h" 0030 #include <kis_assert.h> 0031 0032 #include <KisCppQuirks.h> 0033 0034 const QString dbDriver = "QSQLITE"; 0035 0036 const QString KisResourceCacheDb::resourceCacheDbFilename { "resourcecache.sqlite" }; 0037 const QString KisResourceCacheDb::databaseVersion { "0.0.17" }; 0038 QStringList KisResourceCacheDb::storageTypes { QStringList() }; 0039 QStringList KisResourceCacheDb::disabledBundles { QStringList() << "Krita_3_Default_Resources.bundle" }; 0040 0041 bool KisResourceCacheDb::s_valid {false}; 0042 QString KisResourceCacheDb::s_lastError {QString()}; 0043 0044 bool KisResourceCacheDb::isValid() 0045 { 0046 return s_valid; 0047 } 0048 0049 QString KisResourceCacheDb::lastError() 0050 { 0051 return s_lastError; 0052 } 0053 0054 // use in WHERE QSqlQuery clauses 0055 // because if the string is null, the query will also have null there 0056 // and every comparison with null is false, so the query won't find anything 0057 // (especially important for storage location where empty string is common) 0058 QString changeToEmptyIfNull(QString s) 0059 { 0060 return s.isNull() ? QString("") : s; 0061 } 0062 0063 bool updateSchemaVersion() 0064 { 0065 QFile f(":/fill_version_information.sql"); 0066 if (f.open(QFile::ReadOnly)) { 0067 QString sql = f.readAll(); 0068 QSqlQuery q; 0069 if (!q.prepare(sql)) { 0070 qWarning() << "Could not prepare the schema information query" << q.lastError() << q.boundValues(); 0071 return false; 0072 } 0073 q.addBindValue(KisResourceCacheDb::databaseVersion); 0074 q.addBindValue(KritaVersionWrapper::versionString()); 0075 q.addBindValue(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); 0076 if (!q.exec()) { 0077 qWarning() << "Could not insert the current version" << q.lastError() << q.boundValues(); 0078 return false; 0079 } 0080 infoResources << "Filled version table"; 0081 } 0082 return true; 0083 } 0084 0085 0086 0087 QSqlError createDatabase(const QString &location) 0088 { 0089 // NOTE: if the id's of Unknown and Memory in the database 0090 // will change, and that will break the queries that 0091 // remove Unknown and Memory storages on start-up. 0092 KisResourceCacheDb::storageTypes << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType(1)) 0093 << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType(2)) 0094 << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType(3)) 0095 << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType(4)) 0096 << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType(5)) 0097 << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType(6)) 0098 ; 0099 0100 if (!QSqlDatabase::connectionNames().isEmpty()) { 0101 return QSqlError(); 0102 } 0103 0104 QDir dbLocation(location); 0105 if (!dbLocation.exists()) { 0106 dbLocation.mkpath(dbLocation.path()); 0107 } 0108 0109 QSqlDatabase db = QSqlDatabase::addDatabase(dbDriver); 0110 db.setDatabaseName(location + "/" + KisResourceCacheDb::resourceCacheDbFilename); 0111 0112 if (!db.open()) { 0113 qWarning() << "Could not connect to resource cache database"; 0114 return db.lastError(); 0115 } 0116 0117 // will be filled correctly later 0118 QVersionNumber oldSchemaVersionNumber; 0119 QVersionNumber newSchemaVersionNumber = QVersionNumber::fromString(KisResourceCacheDb::databaseVersion); 0120 0121 0122 QStringList tables = QStringList() << "version_information" 0123 << "storage_types" 0124 << "resource_types" 0125 << "storages" 0126 << "tags" 0127 << "resources" 0128 << "versioned_resources" 0129 << "resource_tags" 0130 << "metadata" 0131 << "tags_storages" 0132 << "tag_translations"; 0133 0134 QStringList dbTables; 0135 // Verify whether we should recreate the database 0136 { 0137 bool allTablesPresent = true; 0138 dbTables = db.tables(); 0139 Q_FOREACH(const QString &table, tables) { 0140 if (!dbTables.contains(table)) { 0141 allTablesPresent = false; 0142 break; 0143 } 0144 } 0145 0146 bool schemaIsOutDated = false; 0147 QString schemaVersion = "0.0.0"; 0148 QString kritaVersion = "Unknown"; 0149 int creationDate = 0; 0150 0151 if (dbTables.contains("version_information")) { 0152 // Verify the version number 0153 0154 QSqlQuery q("SELECT database_version\n" 0155 ", krita_version\n" 0156 ", creation_date\n" 0157 "FROM version_information\n" 0158 "ORDER BY id\n" 0159 "DESC\n" 0160 "LIMIT 1;\n"); 0161 0162 if (!q.exec()) { 0163 qWarning() << "Could not retrieve version information from the database." << q.lastError(); 0164 abort(); 0165 } 0166 q.first(); 0167 schemaVersion = q.value(0).toString(); 0168 kritaVersion = q.value(1).toString(); 0169 creationDate = q.value(2).toInt(); 0170 0171 oldSchemaVersionNumber = QVersionNumber::fromString(schemaVersion); 0172 newSchemaVersionNumber = QVersionNumber::fromString(KisResourceCacheDb::databaseVersion); 0173 0174 if (QVersionNumber::compare(oldSchemaVersionNumber, newSchemaVersionNumber) != 0) { 0175 0176 qWarning() << "Old schema:" << schemaVersion << "New schema:" << newSchemaVersionNumber; 0177 0178 schemaIsOutDated = true; 0179 KisBackup::numberedBackupFile(location + "/" + KisResourceCacheDb::resourceCacheDbFilename); 0180 0181 if (newSchemaVersionNumber == QVersionNumber::fromString("0.0.17") 0182 && QVersionNumber::compare(oldSchemaVersionNumber, QVersionNumber::fromString("0.0.14")) > 0 0183 && QVersionNumber::compare(oldSchemaVersionNumber, QVersionNumber::fromString("0.0.17")) < 0) { 0184 bool from14to15 = oldSchemaVersionNumber == QVersionNumber::fromString("0.0.14"); 0185 bool from15to16 = oldSchemaVersionNumber == QVersionNumber::fromString("0.0.14") 0186 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.15"); 0187 bool from16to17 = oldSchemaVersionNumber == QVersionNumber::fromString("0.0.14") 0188 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.15") 0189 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.16"); 0190 0191 bool success = true; 0192 if (from14to15) { 0193 qWarning() << "Going to update resource_tags table"; 0194 0195 QSqlQuery q; 0196 q.prepare("ALTER TABLE resource_tags\n" 0197 "ADD COLUMN active INTEGER NOT NULL DEFAULT 1"); 0198 if (!q.exec()) { 0199 qWarning() << "Could not update the resource_tags table." << q.lastError(); 0200 success = false; 0201 } 0202 else { 0203 qWarning() << "Updated table resource_tags: success."; 0204 } 0205 } 0206 if (from15to16) { 0207 qWarning() << "Going to update indices"; 0208 0209 QStringList indexes = QStringList() << "tags" << "resources" << "tag_translations" << "resource_tags"; 0210 0211 Q_FOREACH(const QString &index, indexes) { 0212 QFile f(":/create_index_" + index + ".sql"); 0213 if (f.open(QFile::ReadOnly)) { 0214 QSqlQuery q; 0215 if (!q.exec(f.readAll())) { 0216 qWarning() << "Could not create index" << index << q.lastError(); 0217 return db.lastError(); 0218 } 0219 infoResources << "Created index" << index; 0220 } 0221 else { 0222 return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(index), QSqlError::StatementError); 0223 } 0224 } 0225 } 0226 0227 if (from16to17) { 0228 qWarning() << "Going to update resource signature index"; 0229 0230 QFile f(":/create_index_resources_signature.sql"); 0231 if (f.open(QFile::ReadOnly)) { 0232 QSqlQuery q; 0233 if (!q.exec(f.readAll())) { 0234 qWarning() << "Could not create index for resources signature" << q.lastError(); 0235 return db.lastError(); 0236 } 0237 infoResources << "Created resources signature index"; 0238 } 0239 else { 0240 return QSqlError("Error executing SQL", QString("Could not find SQL file for resources signature index"), QSqlError::StatementError); 0241 } 0242 } 0243 0244 if (success) { 0245 if (!updateSchemaVersion()) { 0246 return QSqlError("Error executing SQL", QString("Could not update schema version."), QSqlError::StatementError); 0247 } 0248 } 0249 0250 schemaIsOutDated = !success; 0251 0252 } 0253 0254 if (schemaIsOutDated) { 0255 QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The resource database scheme has changed. Krita will backup your database and create a new database.")); 0256 if (QVersionNumber::compare(oldSchemaVersionNumber, QVersionNumber::fromString("0.0.14")) > 0) { 0257 KisResourceLocator::instance()->saveTags(); 0258 } 0259 db.close(); 0260 QFile::remove(location + "/" + KisResourceCacheDb::resourceCacheDbFilename); 0261 db.open(); 0262 } 0263 } 0264 0265 } 0266 0267 if (allTablesPresent && !schemaIsOutDated) { 0268 KisUsageLogger::log(QString("Database is up to date. Version: %1, created by Krita %2, at %3") 0269 .arg(schemaVersion) 0270 .arg(kritaVersion) 0271 .arg(QDateTime::fromSecsSinceEpoch(creationDate).toString())); 0272 return QSqlError(); 0273 } 0274 } 0275 0276 KisUsageLogger::log(QString("Creating database from scratch (%1, %2).") 0277 .arg(oldSchemaVersionNumber.toString().isEmpty() ? QString("database didn't exist") : ("old schema version: " + oldSchemaVersionNumber.toString())) 0278 .arg("new schema version: " + newSchemaVersionNumber.toString())); 0279 0280 // Create tables 0281 Q_FOREACH(const QString &table, tables) { 0282 QFile f(":/create_" + table + ".sql"); 0283 if (f.open(QFile::ReadOnly)) { 0284 QSqlQuery q; 0285 if (!q.exec(f.readAll())) { 0286 qWarning() << "Could not create table" << table << q.lastError(); 0287 return db.lastError(); 0288 } 0289 infoResources << "Created table" << table; 0290 } 0291 else { 0292 return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(table), QSqlError::StatementError); 0293 } 0294 } 0295 0296 // Create indexes 0297 QStringList indexes; 0298 0299 // these indexes came in version 0.0.16 0300 indexes << "storages" << "versioned_resources" << "tags" << "resources" << "tag_translations" << "resource_tags"; 0301 0302 // this indexes came in version 0.0.17 0303 indexes << "resources_signature"; 0304 0305 Q_FOREACH(const QString &index, indexes) { 0306 QFile f(":/create_index_" + index + ".sql"); 0307 if (f.open(QFile::ReadOnly)) { 0308 QSqlQuery q; 0309 if (!q.exec(f.readAll())) { 0310 qWarning() << "Could not create index" << index; 0311 return db.lastError(); 0312 } 0313 infoResources << "Created table" << index; 0314 } 0315 else { 0316 return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(index), QSqlError::StatementError); 0317 } 0318 } 0319 0320 // Fill lookup tables 0321 { 0322 if (dbTables.contains("storage_types")) { 0323 QSqlQuery q; 0324 if (!q.exec("DELETE FROM storage_types;")) { 0325 qWarning() << "Could not clear table storage_types" << db.lastError(); 0326 } 0327 } 0328 0329 QFile f(":/fill_storage_types.sql"); 0330 if (f.open(QFile::ReadOnly)) { 0331 QString sql = f.readAll(); 0332 Q_FOREACH(const QString &originType, KisResourceCacheDb::storageTypes) { 0333 QSqlQuery q(sql); 0334 q.addBindValue(originType); 0335 if (!q.exec()) { 0336 qWarning() << "Could not insert" << originType << db.lastError() << q.executedQuery(); 0337 return db.lastError(); 0338 } 0339 } 0340 infoResources << "Filled lookup table storage_types"; 0341 } 0342 else { 0343 return QSqlError("Error executing SQL", QString("Could not find SQL fill_storage_types.sql."), QSqlError::StatementError); 0344 } 0345 } 0346 0347 { 0348 if (dbTables.contains("resource_types")) { 0349 QSqlQuery q; 0350 if (!q.exec("DELETE FROM resource_types;")) { 0351 qWarning() << "Could not clear table resource_types" << db.lastError(); 0352 } 0353 } 0354 QFile f(":/fill_resource_types.sql"); 0355 if (f.open(QFile::ReadOnly)) { 0356 QString sql = f.readAll(); 0357 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { 0358 QSqlQuery q(sql); 0359 q.addBindValue(resourceType); 0360 if (!q.exec()) { 0361 qWarning() << "Could not insert" << resourceType << db.lastError() << q.executedQuery(); 0362 return db.lastError(); 0363 } 0364 } 0365 infoResources << "Filled lookup table resource_types"; 0366 } 0367 else { 0368 return QSqlError("Error executing SQL", QString("Could not find SQL fill_resource_types.sql."), QSqlError::StatementError); 0369 } 0370 } 0371 0372 if (!updateSchemaVersion()) { 0373 return QSqlError("Error executing SQL", QString("Could not update schema version."), QSqlError::StatementError); 0374 } 0375 0376 return QSqlError(); 0377 } 0378 0379 bool KisResourceCacheDb::initialize(const QString &location) 0380 { 0381 QSqlError err = createDatabase(location); 0382 0383 s_valid = !err.isValid(); 0384 switch (err.type()) { 0385 case QSqlError::NoError: 0386 s_lastError = QString(); 0387 break; 0388 case QSqlError::ConnectionError: 0389 s_lastError = QString("Could not initialize the resource cache database. Connection error: %1").arg(err.text()); 0390 break; 0391 case QSqlError::StatementError: 0392 s_lastError = QString("Could not initialize the resource cache database. Statement error: %1").arg(err.text()); 0393 break; 0394 case QSqlError::TransactionError: 0395 s_lastError = QString("Could not initialize the resource cache database. Transaction error: %1").arg(err.text()); 0396 break; 0397 case QSqlError::UnknownError: 0398 s_lastError = QString("Could not initialize the resource cache database. Unknown error: %1").arg(err.text()); 0399 break; 0400 } 0401 0402 // Delete all storages that are no longer known to the resource locator (including the memory storages) 0403 deleteTemporaryResources(); 0404 0405 return s_valid; 0406 } 0407 0408 QVector<int> KisResourceCacheDb::resourcesForStorage(const QString &resourceType, const QString &storageLocation) 0409 { 0410 QVector<int> result; 0411 0412 QSqlQuery q; 0413 0414 if (!q.prepare("SELECT resources.id\n" 0415 "FROM resources\n" 0416 ", resource_types\n" 0417 ", storages\n" 0418 "WHERE resources.resource_type_id = resource_types.id\n" 0419 "AND storages.id = resources.storage_id\n" 0420 "AND storages.location = :storage_location\n" 0421 "AND resource_types.name = :resource_type\n")) { 0422 0423 qWarning() << "Could not read and prepare resourcesForStorage" << q.lastError(); 0424 return result; 0425 } 0426 0427 q.bindValue(":resource_type", resourceType); 0428 q.bindValue(":storage_location", changeToEmptyIfNull(storageLocation)); 0429 0430 if (!q.exec()) { 0431 qWarning() << "Could not query resourceIdForResource" << q.boundValues() << q.lastError(); 0432 return result; 0433 } 0434 0435 while (q.next()) { 0436 result << q.value(0).toInt(); 0437 } 0438 0439 return result; 0440 } 0441 0442 int KisResourceCacheDb::resourceIdForResource(const QString &resourceFileName, const QString &resourceType, const QString &storageLocation) 0443 { 0444 //qDebug() << "resourceIdForResource" << resourceName << resourceFileName << resourceType << storageLocation; 0445 0446 QSqlQuery q; 0447 0448 if (!q.prepare("SELECT resources.id\n" 0449 "FROM resources\n" 0450 ", resource_types\n" 0451 ", storages\n" 0452 "WHERE resources.resource_type_id = resource_types.id\n" 0453 "AND storages.id = resources.storage_id\n" 0454 "AND storages.location = :storage_location\n" 0455 "AND resource_types.name = :resource_type\n" 0456 "AND resources.filename = :filename\n")) { 0457 qWarning() << "Could not read and prepare resourceIdForResource" << q.lastError(); 0458 return -1; 0459 } 0460 0461 q.bindValue(":filename", resourceFileName); 0462 q.bindValue(":resource_type", resourceType); 0463 q.bindValue(":storage_location", changeToEmptyIfNull(storageLocation)); 0464 0465 if (!q.exec()) { 0466 qWarning() << "Could not query resourceIdForResource" << q.boundValues() << q.lastError(); 0467 return -1; 0468 } 0469 0470 if (q.first()) { 0471 return q.value(0).toInt(); 0472 } 0473 0474 // couldn't be found in the `resources` table, but can still be in versioned_resources 0475 0476 if (!q.prepare("SELECT versioned_resources.resource_id\n" 0477 "FROM resources\n" 0478 ", resource_types\n" 0479 ", versioned_resources\n" 0480 ", storages\n" 0481 "WHERE resources.resource_type_id = resource_types.id\n" // join resources and resource_types by resource id 0482 "AND versioned_resources.resource_id = resources.id\n" // join versioned_resources and resources by resource id 0483 "AND storages.id = versioned_resources.storage_id\n" // join storages and versioned_resources by storage id 0484 "AND storages.location = :storage_location\n" // storage location must be the same as asked for 0485 "AND resource_types.name = :resource_type\n" // resource type must be the same as asked for 0486 "AND versioned_resources.filename = :filename\n")) { // filename must be the same as asked for 0487 qWarning() << "Could not read and prepare resourceIdForResource (in versioned resources)" << q.lastError(); 0488 return -1; 0489 } 0490 0491 q.bindValue(":filename", resourceFileName); 0492 q.bindValue(":resource_type", resourceType); 0493 q.bindValue(":storage_location", changeToEmptyIfNull(storageLocation)); 0494 0495 if (!q.exec()) { 0496 qWarning() << "Could not query resourceIdForResource (in versioned resources)" << q.boundValues() << q.lastError(); 0497 return -1; 0498 } 0499 0500 if (q.first()) { 0501 return q.value(0).toInt(); 0502 } 0503 0504 // commenting out, because otherwise it spams the console on every new resource in the local resources folder 0505 // qWarning() << "Could not find resource" << resourceName << resourceFileName << resourceType << storageLocation; 0506 return -1; 0507 0508 } 0509 0510 bool KisResourceCacheDb::resourceNeedsUpdating(int resourceId, QDateTime timestamp) 0511 { 0512 QSqlQuery q; 0513 if (!q.prepare("SELECT timestamp\n" 0514 "FROM versioned_resources\n" 0515 "WHERE resource_id = :resource_id\n" 0516 "AND version = (SELECT MAX(version)\n" 0517 " FROM versioned_resources\n" 0518 " WHERE resource_id = :resource_id);")) { 0519 qWarning() << "Could not prepare resourceNeedsUpdating statement" << q.lastError(); 0520 return false; 0521 } 0522 0523 q.bindValue(":resource_id", resourceId); 0524 0525 if (!q.exec()) { 0526 qWarning() << "Could not query for the most recent timestamp" << q.boundValues() << q.lastError(); 0527 return false; 0528 } 0529 0530 if (!q.first()) { 0531 qWarning() << "Inconsistent database: could not find a version for resource with Id" << resourceId; 0532 return false; 0533 } 0534 0535 QVariant resourceTimeStamp = q.value(0); 0536 0537 if (!resourceTimeStamp.isValid()) { 0538 qWarning() << "Could not retrieve timestamp from versioned_resources" << resourceId; 0539 return false; 0540 } 0541 0542 return (timestamp.toSecsSinceEpoch() > resourceTimeStamp.toInt()); 0543 } 0544 0545 bool KisResourceCacheDb::addResourceVersion(int resourceId, QDateTime timestamp, KisResourceStorageSP storage, KoResourceSP resource) 0546 { 0547 bool r = false; 0548 0549 0550 r = addResourceVersionImpl(resourceId, timestamp, storage, resource); 0551 0552 if (!r) return r; 0553 0554 r = makeResourceTheCurrentVersion(resourceId, resource); 0555 0556 return r; 0557 } 0558 0559 bool KisResourceCacheDb::addResourceVersionImpl(int resourceId, QDateTime timestamp, KisResourceStorageSP storage, KoResourceSP resource) 0560 { 0561 bool r = false; 0562 0563 // Create the new version. The resource is expected to have an updated version number, or 0564 // this will fail on the unique index on resource_id, storage_id and version. 0565 // 0566 // This function **only** adds to the versioned_resources table. 0567 // The resources table should be updated by the caller manually using 0568 // updateResourceTableForResourceIfNeeded() 0569 0570 Q_ASSERT(resource->version() >= 0); 0571 0572 QSqlQuery q; 0573 r = q.prepare("INSERT INTO versioned_resources \n" 0574 "(resource_id, storage_id, version, filename, timestamp, md5sum)\n" 0575 "VALUES\n" 0576 "( :resource_id\n" 0577 ", (SELECT id \n" 0578 " FROM storages \n" 0579 " WHERE location = :storage_location)\n" 0580 ", :version\n" 0581 ", :filename\n" 0582 ", :timestamp\n" 0583 ", :md5sum\n" 0584 ");"); 0585 0586 if (!r) { 0587 qWarning() << "Could not prepare addResourceVersion statement" << q.lastError(); 0588 return r; 0589 } 0590 0591 q.bindValue(":resource_id", resourceId); 0592 q.bindValue(":storage_location", changeToEmptyIfNull(KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()))); 0593 q.bindValue(":version", resource->version()); 0594 q.bindValue(":filename", resource->filename()); 0595 q.bindValue(":timestamp", timestamp.toSecsSinceEpoch()); 0596 KIS_SAFE_ASSERT_RECOVER_NOOP(!resource->md5Sum().isEmpty()); 0597 q.bindValue(":md5sum", resource->md5Sum()); 0598 r = q.exec(); 0599 if (!r) { 0600 0601 qWarning() << "Could not execute addResourceVersionImpl statement" << q.lastError() << resourceId << storage->name() << storage->location() << resource->name() << resource->filename() << "version" << resource->version(); 0602 return r; 0603 } 0604 0605 return r; 0606 } 0607 0608 bool KisResourceCacheDb::removeResourceVersionImpl(int resourceId, int version, KisResourceStorageSP storage) 0609 { 0610 bool r = false; 0611 0612 // Remove a version of the resource. This function **only** removes data from 0613 // the versioned_resources table. The resources table should be updated by 0614 // the caller manually using updateResourceTableForResourceIfNeeded() 0615 0616 QSqlQuery q; 0617 r = q.prepare("DELETE FROM versioned_resources \n" 0618 "WHERE resource_id = :resource_id\n" 0619 "AND version = :version\n" 0620 "AND storage_id = (SELECT id \n" 0621 " FROM storages \n" 0622 " WHERE location = :storage_location);"); 0623 0624 if (!r) { 0625 qWarning() << "Could not prepare removeResourceVersionImpl statement" << q.lastError(); 0626 return r; 0627 } 0628 0629 q.bindValue(":resource_id", resourceId); 0630 q.bindValue(":storage_location", changeToEmptyIfNull(KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()))); 0631 q.bindValue(":version", version); 0632 r = q.exec(); 0633 if (!r) { 0634 0635 qWarning() << "Could not execute removeResourceVersionImpl statement" << q.lastError() << resourceId << storage->name() << storage->location() << "version" << version; 0636 return r; 0637 } 0638 0639 return r; 0640 } 0641 0642 bool KisResourceCacheDb::updateResourceTableForResourceIfNeeded(int resourceId, const QString &resourceType, KisResourceStorageSP storage) 0643 { 0644 bool r = false; 0645 0646 int maxVersion = -1; 0647 { 0648 QSqlQuery q; 0649 r = q.prepare("SELECT MAX(version)\n" 0650 "FROM versioned_resources\n" 0651 "WHERE resource_id = :resource_id;"); 0652 if (!r) { 0653 qWarning() << "Could not prepare findMaxVersion statement" << q.lastError(); 0654 return r; 0655 } 0656 0657 q.bindValue(":resource_id", resourceId); 0658 0659 r = q.exec(); 0660 if (!r) { 0661 qWarning() << "Could not execute findMaxVersion query" << q.boundValues() << q.lastError(); 0662 return r; 0663 } 0664 0665 r = q.first(); 0666 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(r, false); 0667 0668 maxVersion = q.value(0).toInt(); 0669 } 0670 0671 QString maxVersionFilename; 0672 { 0673 QSqlQuery q; 0674 r = q.prepare("SELECT filename\n" 0675 "FROM versioned_resources\n" 0676 "WHERE resource_id = :resource_id\n" 0677 "AND version = :version;"); 0678 if (!r) { 0679 qWarning() << "Could not prepare findMaxVersionFilename statement" << q.lastError(); 0680 return r; 0681 } 0682 0683 q.bindValue(":resource_id", resourceId); 0684 q.bindValue(":version", maxVersion); 0685 0686 r = q.exec(); 0687 if (!r) { 0688 qWarning() << "Could not execute findMaxVersionFilename query" << q.boundValues() << q.lastError(); 0689 return r; 0690 } 0691 0692 if (!q.first()) { 0693 return removeResourceCompletely(resourceId); 0694 } else { 0695 maxVersionFilename = q.value(0).toString(); 0696 } 0697 } 0698 0699 QString currentFilename; 0700 { 0701 QSqlQuery q; 0702 r = q.prepare("SELECT filename\n" 0703 "FROM resources\n" 0704 "WHERE id = :resource_id;"); 0705 if (!r) { 0706 qWarning() << "Could not prepare findMaxVersion statement" << q.lastError(); 0707 return r; 0708 } 0709 0710 q.bindValue(":resource_id", resourceId); 0711 0712 r = q.exec(); 0713 if (!r) { 0714 qWarning() << "Could not execute findMaxVersion query" << q.boundValues() << q.lastError(); 0715 return r; 0716 } 0717 0718 r = q.first(); 0719 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(r, false); 0720 0721 currentFilename = q.value(0).toString(); 0722 } 0723 0724 if (currentFilename != maxVersionFilename) { 0725 const QString url = resourceType + "/" + maxVersionFilename; 0726 KoResourceSP resource = storage->resource(url); 0727 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(resource, false); 0728 resource->setVersion(maxVersion); 0729 resource->setMD5Sum(storage->resourceMd5(url)); 0730 r = makeResourceTheCurrentVersion(resourceId, resource); 0731 } 0732 0733 return r; 0734 } 0735 0736 bool KisResourceCacheDb::makeResourceTheCurrentVersion(int resourceId, KoResourceSP resource) 0737 { 0738 bool r = false; 0739 0740 QSqlQuery q; 0741 r = q.prepare("UPDATE resources\n" 0742 "SET name = :name\n" 0743 ", filename = :filename\n" 0744 ", tooltip = :tooltip\n" 0745 ", thumbnail = :thumbnail\n" 0746 ", status = 1\n" 0747 ", md5sum = :md5sum\n" 0748 "WHERE id = :id"); 0749 if (!r) { 0750 qWarning() << "Could not prepare updateResource statement" << q.lastError(); 0751 return r; 0752 } 0753 0754 q.bindValue(":name", resource->name()); 0755 q.bindValue(":filename", resource->filename()); 0756 q.bindValue(":tooltip", i18n(resource->name().toUtf8())); 0757 q.bindValue(":md5sum", resource->md5Sum()); 0758 0759 QBuffer buf; 0760 buf.open(QBuffer::WriteOnly); 0761 resource->thumbnail().save(&buf, "PNG"); 0762 buf.close(); 0763 q.bindValue(":thumbnail", buf.data()); 0764 q.bindValue(":id", resourceId); 0765 0766 r = q.exec(); 0767 if (!r) { 0768 qWarning() << "Could not update resource" << q.boundValues() << q.lastError(); 0769 } 0770 0771 return r; 0772 } 0773 0774 bool KisResourceCacheDb::removeResourceCompletely(int resourceId) 0775 { 0776 bool r = false; 0777 0778 { 0779 QSqlQuery q; 0780 r = q.prepare("DELETE FROM versioned_resources \n" 0781 "WHERE resource_id = :resource_id;"); 0782 0783 if (!r) { 0784 qWarning() << "Could not prepare removeResourceCompletely1 statement" << q.lastError(); 0785 return r; 0786 } 0787 0788 q.bindValue(":resource_id", resourceId); 0789 r = q.exec(); 0790 if (!r) { 0791 qWarning() << "Could not execute removeResourceCompletely1 statement" << q.lastError() << resourceId; 0792 return r; 0793 } 0794 } 0795 0796 { 0797 QSqlQuery q; 0798 r = q.prepare("DELETE FROM resources \n" 0799 "WHERE id = :resource_id;"); 0800 0801 if (!r) { 0802 qWarning() << "Could not prepare removeResourceCompletely2 statement" << q.lastError(); 0803 return r; 0804 } 0805 0806 q.bindValue(":resource_id", resourceId); 0807 r = q.exec(); 0808 if (!r) { 0809 qWarning() << "Could not execute removeResourceCompletely2 statement" << q.lastError() << resourceId; 0810 return r; 0811 } 0812 } 0813 0814 { 0815 QSqlQuery q; 0816 r = q.prepare("DELETE FROM resource_tags \n" 0817 "WHERE resource_id = :resource_id;"); 0818 0819 if (!r) { 0820 qWarning() << "Could not prepare removeResourceCompletely3 statement" << q.lastError(); 0821 return r; 0822 } 0823 0824 q.bindValue(":resource_id", resourceId); 0825 r = q.exec(); 0826 if (!r) { 0827 qWarning() << "Could not execute removeResourceCompletely3 statement" << q.lastError() << resourceId; 0828 return r; 0829 } 0830 } 0831 0832 return r; 0833 } 0834 0835 bool KisResourceCacheDb::getResourceIdFromFilename(QString filename, QString resourceType, QString storageLocation, int &outResourceId) 0836 { 0837 QSqlQuery q; 0838 0839 bool r = q.prepare("SELECT resources.id FROM resources\n" 0840 ", resource_types\n" 0841 ", storages\n" 0842 "WHERE resources.filename = :filename\n" // bind to filename 0843 "AND resource_types.id = resources.resource_type_id\n" // join resources_types + resources 0844 "AND resource_types.name = :resourceType\n" // bind to resource type 0845 "AND resources.storage_id = storages.id\n" // join resources + storages 0846 "AND storages.location = :storageLocation"); // bind to storage location 0847 0848 if (!r) { 0849 qWarning() << "Could not prepare getResourceIdFromFilename statement" << q.lastError() << q.executedQuery(); 0850 return r; 0851 } 0852 0853 q.bindValue(":filename", filename); 0854 q.bindValue(":resourceType", resourceType); 0855 q.bindValue(":storageLocation", changeToEmptyIfNull(storageLocation)); 0856 0857 r = q.exec(); 0858 if (!r) { 0859 qWarning() << "Could not execute getResourceIdFromFilename statement" << q.lastError() << filename << resourceType; 0860 return r; 0861 } 0862 0863 r = q.first(); 0864 if (r) { 0865 outResourceId = q.value("resources.id").toInt(); 0866 } 0867 0868 return r; 0869 } 0870 0871 bool KisResourceCacheDb::getResourceIdFromVersionedFilename(QString filename, QString resourceType, QString storageLocation, int &outResourceId) 0872 { 0873 QSqlQuery q; 0874 0875 bool r = q.prepare("SELECT resource_id FROM versioned_resources\n" 0876 ", resources\n" 0877 ", resource_types\n" 0878 ", storages\n" 0879 "WHERE versioned_resources.filename = :filename\n" // bind to filename 0880 "AND resources.id = versioned_resources.resource_id\n" // join resources + versioned_resources 0881 "AND resource_types.id = resources.resource_type_id\n" // join resources_types + resources 0882 "AND resource_types.name = :resourceType\n" // bind to resource type 0883 "AND resources.storage_id = storages.id\n" // join resources + storages 0884 "AND storages.location = :storageLocation"); // bind to storage location 0885 0886 if (!r) { 0887 qWarning() << "Could not prepare getResourceIdFromVersionedFilename statement" << q.lastError() << q.executedQuery(); 0888 return r; 0889 } 0890 0891 0892 q.bindValue(":filename", filename); 0893 q.bindValue(":resourceType", resourceType); 0894 q.bindValue(":storageLocation", changeToEmptyIfNull(storageLocation)); 0895 0896 r = q.exec(); 0897 if (!r) { 0898 qWarning() << "Could not execute getResourceIdFromVersionedFilename statement" << q.lastError() << filename << resourceType; 0899 return r; 0900 } 0901 0902 r = q.first(); 0903 if (r) { 0904 outResourceId = q.value("resource_id").toInt(); 0905 } 0906 0907 return r; 0908 } 0909 0910 bool KisResourceCacheDb::getAllVersionsLocations(int resourceId, QStringList &outVersionsLocationsList) 0911 { 0912 QSqlQuery q; 0913 bool r = q.prepare("SELECT filename FROM versioned_resources \n" 0914 "WHERE resource_id = :resource_id;"); 0915 0916 if (!r) { 0917 qWarning() << "Could not prepare getAllVersionsLocations statement" << q.lastError(); 0918 return r; 0919 } 0920 0921 q.bindValue(":resource_id", resourceId); 0922 r = q.exec(); 0923 if (!r) { 0924 qWarning() << "Could not execute getAllVersionsLocations statement" << q.lastError() << resourceId; 0925 return r; 0926 } 0927 0928 outVersionsLocationsList = QStringList(); 0929 while (q.next()) { 0930 outVersionsLocationsList << q.value("filename").toString(); 0931 } 0932 0933 return r; 0934 0935 } 0936 0937 bool KisResourceCacheDb::addResource(KisResourceStorageSP storage, QDateTime timestamp, KoResourceSP resource, const QString &resourceType) 0938 { 0939 bool r = false; 0940 0941 if (!s_valid) { 0942 qWarning() << "KisResourceCacheDb::addResource: The database is not valid"; 0943 return false; 0944 } 0945 0946 if (!resource || !resource->valid()) { 0947 qWarning() << "KisResourceCacheDb::addResource: The resource is not valid:" << resource->filename(); 0948 // We don't care about invalid resources and will just ignore them. 0949 return true; 0950 } 0951 bool temporary = (storage->type() == KisResourceStorage::StorageType::Memory); 0952 0953 // Check whether it already exists 0954 int resourceId = resourceIdForResource(resource->filename(), resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); 0955 if (resourceId > -1) { 0956 return true; 0957 } 0958 0959 QSqlQuery q; 0960 r = q.prepare("INSERT INTO resources \n" 0961 "(storage_id, resource_type_id, name, filename, tooltip, thumbnail, status, temporary, md5sum) \n" 0962 "VALUES \n" 0963 "((SELECT id " 0964 " FROM storages " 0965 " WHERE location = :storage_location)\n" 0966 ", (SELECT id\n" 0967 " FROM resource_types\n" 0968 " WHERE name = :resource_type)\n" 0969 ", :name\n" 0970 ", :filename\n" 0971 ", :tooltip\n" 0972 ", :thumbnail\n" 0973 ", :status\n" 0974 ", :temporary\n" 0975 ", :md5sum)"); 0976 0977 if (!r) { 0978 qWarning() << "Could not prepare addResource statement" << q.lastError(); 0979 return r; 0980 } 0981 0982 q.bindValue(":resource_type", resourceType); 0983 q.bindValue(":storage_location", changeToEmptyIfNull(KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()))); 0984 q.bindValue(":name", resource->name()); 0985 q.bindValue(":filename", resource->filename()); 0986 0987 QString translationContext; 0988 if (storage->type() == KisResourceStorage::StorageType::Bundle) { 0989 translationContext = "./krita/data/bundles/" + KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()) 0990 + ":" + resourceType + "/" + resource->filename(); 0991 } else if (storage->location() == "memory") { 0992 translationContext = "memory/" + resourceType + "/" + resource->filename(); 0993 } 0994 else if (resource->filename().endsWith(".myb", Qt::CaseInsensitive)) { 0995 translationContext = "./plugins/paintops/mypaint/brushes/" + resource->filename(); 0996 } else { 0997 translationContext = "./krita/data/" + resourceType + "/" + resource->filename(); 0998 } 0999 1000 { 1001 QByteArray ctx = translationContext.toUtf8(); 1002 QString translatedName = i18nc(ctx, resource->name().toUtf8()); 1003 if (translatedName == resource->name()) { 1004 // Try using the file name without the file extension, and replaces '_' with spaces. 1005 QString altName = QFileInfo(resource->filename()).completeBaseName().replace('_', ' '); 1006 QString altTranslatedName = i18nc(ctx, altName.toUtf8()); 1007 if (altName != altTranslatedName) { 1008 translatedName = altTranslatedName; 1009 } 1010 } 1011 q.bindValue(":tooltip", translatedName); 1012 } 1013 1014 QBuffer buf; 1015 buf.open(QBuffer::WriteOnly); 1016 resource->image().save(&buf, "PNG"); 1017 buf.close(); 1018 q.bindValue(":thumbnail", buf.data()); 1019 1020 q.bindValue(":status", resource->active()); 1021 q.bindValue(":temporary", (temporary ? 1 : 0)); 1022 q.bindValue(":md5sum", resource->md5Sum()); 1023 1024 r = q.exec(); 1025 if (!r) { 1026 qWarning() << "Could not execute addResource statement" << q.lastError() << q.boundValues(); 1027 return r; 1028 } 1029 resourceId = resourceIdForResource(resource->filename(), resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); 1030 1031 if (resourceId < 0) { 1032 1033 qWarning() << "Adding to database failed, resource id after adding is " << resourceId << "! (Probable reason: the resource has the same filename, storage, resource type as an existing resource). Resource is: " 1034 << resource->name() 1035 << resource->filename() 1036 << resourceType 1037 << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); 1038 return false; 1039 } 1040 1041 resource->setResourceId(resourceId); 1042 1043 if (!addResourceVersionImpl(resourceId, timestamp, storage, resource)) { 1044 qWarning() << "Could not add resource version" << resource; 1045 return false; 1046 } 1047 1048 if (!resource->metadata().isEmpty()) { 1049 return addMetaDataForId(resource->metadata(), resource->resourceId(), "resources"); 1050 } 1051 1052 return true; 1053 1054 1055 } 1056 1057 bool KisResourceCacheDb::addResources(KisResourceStorageSP storage, QString resourceType) 1058 { 1059 QSqlDatabase::database().transaction(); 1060 QSharedPointer<KisResourceStorage::ResourceIterator> iter = storage->resources(resourceType); 1061 while (iter->hasNext()) { 1062 iter->next(); 1063 1064 QSharedPointer<KisResourceStorage::ResourceIterator> verIt = 1065 iter->versions(); 1066 1067 int resourceId = -1; 1068 1069 while (verIt->hasNext()) { 1070 verIt->next(); 1071 1072 KoResourceSP resource = verIt->resource(); 1073 if (resource && resource->valid()) { 1074 resource->setVersion(verIt->guessedVersion()); 1075 resource->setMD5Sum(storage->resourceMd5(verIt->url())); 1076 1077 if (resourceId < 0) { 1078 if (addResource(storage, iter->lastModified(), resource, iter->type())) { 1079 resourceId = resource->resourceId(); 1080 } else { 1081 qWarning() << "Could not add resource" << resource->filename() << "to the database"; 1082 } 1083 } else { 1084 if (!addResourceVersion(resourceId, iter->lastModified(), storage, resource)) { 1085 qWarning() << "Could not add resource version" << resource->filename() << "to the database"; 1086 } 1087 } 1088 } 1089 } 1090 } 1091 QSqlDatabase::database().commit(); 1092 return true; 1093 } 1094 1095 bool KisResourceCacheDb::setResourceActive(int resourceId, bool active) 1096 { 1097 if (resourceId < 0) { 1098 qWarning() << "Invalid resource id; cannot remove resource"; 1099 return false; 1100 } 1101 QSqlQuery q; 1102 bool r = q.prepare("UPDATE resources\n" 1103 "SET status = :status\n" 1104 "WHERE id = :resource_id"); 1105 if (!r) { 1106 qWarning() << "Could not prepare removeResource query" << q.lastError(); 1107 } 1108 q.bindValue(":status", active); 1109 q.bindValue(":resource_id", resourceId); 1110 if (!q.exec()) { 1111 qWarning() << "Could not update resource" << resourceId << "to inactive" << q.lastError(); 1112 return false; 1113 } 1114 1115 return true; 1116 } 1117 1118 bool KisResourceCacheDb::tagResource(const QString &resourceFileName, KisTagSP tag, const QString &resourceType) 1119 { 1120 // Get tag id 1121 int tagId {-1}; 1122 { 1123 QFile f(":/select_tag.sql"); 1124 if (f.open(QFile::ReadOnly)) { 1125 QSqlQuery q; 1126 if (!q.prepare(f.readAll())) { 1127 qWarning() << "Could not read and prepare select_tag.sql" << q.lastError(); 1128 return false; 1129 } 1130 q.bindValue(":url", tag->url()); 1131 q.bindValue(":resource_type", resourceType); 1132 1133 if (!q.exec()) { 1134 qWarning() << "Could not query tags" << q.boundValues() << q.lastError(); 1135 return false; 1136 } 1137 1138 if (!q.first()) { 1139 qWarning() << "Could not find tag" << q.boundValues() << q.lastError(); 1140 return false; 1141 } 1142 1143 tagId = q.value(0).toInt(); 1144 } 1145 } 1146 1147 1148 // Get resource id 1149 QSqlQuery q; 1150 bool r = q.prepare("SELECT resources.id\n" 1151 "FROM resources\n" 1152 ", resource_types\n" 1153 "WHERE resources.resource_type_id = resource_types.id\n" 1154 "AND resource_types.name = :resource_type\n" 1155 "AND resources.filename = :resource_filename\n"); 1156 if (!r) { 1157 qWarning() << "Could not prepare tagResource query" << q.lastError(); 1158 return false; 1159 } 1160 1161 q.bindValue(":resource_type", resourceType); 1162 q.bindValue(":resource_filename", resourceFileName); 1163 1164 if (!q.exec()) { 1165 qWarning() << "Could not execute tagResource statement" << q.boundValues() << q.lastError(); 1166 return false; 1167 } 1168 1169 1170 while (q.next()) { 1171 1172 int resourceId = q.value(0).toInt(); 1173 1174 if (resourceId < 0) { 1175 qWarning() << "Could not find resource to tag" << resourceFileName << resourceType; 1176 continue; 1177 } 1178 1179 { 1180 QSqlQuery q; 1181 if (!q.prepare("SELECT COUNT(*)\n" 1182 "FROM resource_tags\n" 1183 "WHERE resource_id = :resource_id\n" 1184 "AND tag_id = :tag_id")) { 1185 qWarning() << "Could not prepare tagResource query 2" << q.lastError(); 1186 continue; 1187 } 1188 q.bindValue(":resource_id", resourceId); 1189 q.bindValue(":tag_id", tagId); 1190 1191 if (!q.exec()) { 1192 qWarning() << "Could not execute tagResource query 2" << q.lastError() << q.boundValues(); 1193 continue; 1194 } 1195 1196 q.first(); 1197 int count = q.value(0).toInt(); 1198 if (count > 0) { 1199 continue; 1200 } 1201 } 1202 1203 { 1204 QSqlQuery q; 1205 if (!q.prepare("INSERT INTO resource_tags\n" 1206 "(resource_id, tag_id)\n" 1207 "VALUES\n" 1208 "(:resource_id, :tag_id);")) { 1209 qWarning() << "Could not prepare tagResource insert statement" << q.lastError(); 1210 continue; 1211 } 1212 1213 q.bindValue(":resource_id", resourceId); 1214 q.bindValue(":tag_id", tagId); 1215 1216 if (!q.exec()) { 1217 qWarning() << "Could not execute tagResource stagement" << q.boundValues() << q.lastError(); 1218 continue; 1219 } 1220 } 1221 } 1222 return true; 1223 } 1224 1225 bool KisResourceCacheDb::hasTag(const QString &url, const QString &resourceType) 1226 { 1227 QFile f(":/select_tag.sql"); 1228 if (f.open(QFile::ReadOnly)) { 1229 QSqlQuery q; 1230 if (!q.prepare(f.readAll())) { 1231 qWarning() << "Could not read and prepare select_tag.sql" << q.lastError(); 1232 return false; 1233 } 1234 q.bindValue(":url", url); 1235 q.bindValue(":resource_type", resourceType); 1236 if (!q.exec()) { 1237 qWarning() << "Could not query tags" << q.boundValues() << q.lastError(); 1238 } 1239 return q.first(); 1240 } 1241 qWarning() << "Could not open select_tag.sql"; 1242 return false; 1243 } 1244 1245 bool KisResourceCacheDb::linkTagToStorage(const QString &url, const QString &resourceType, const QString &storageLocation) 1246 { 1247 QSqlQuery q; 1248 if (!q.prepare("INSERT INTO tags_storages\n" 1249 "(tag_id, storage_id)\n" 1250 "VALUES\n" 1251 "(\n" 1252 " ( SELECT id\n" 1253 " FROM tags\n" 1254 " WHERE url = :url\n" 1255 " AND resource_type_id = (SELECT id \n" 1256 " FROM resource_types\n" 1257 " WHERE name = :resource_type)" 1258 " )\n" 1259 ",( SELECT id\n" 1260 " FROM storages\n" 1261 " WHERE location = :storage_location\n" 1262 " )\n" 1263 ");")) { 1264 qWarning() << "Could not prepare add tag/storage statement" << q.lastError(); 1265 return false; 1266 } 1267 1268 q.bindValue(":url", url); 1269 q.bindValue(":resource_type", resourceType); 1270 q.bindValue(":storage_location", changeToEmptyIfNull(KisResourceLocator::instance()->makeStorageLocationRelative(storageLocation))); 1271 1272 if (!q.exec()) { 1273 qWarning() << "Could not insert tag/storage link" << q.boundValues() << q.lastError(); 1274 return false; 1275 } 1276 return true; 1277 } 1278 1279 1280 bool KisResourceCacheDb::addTag(const QString &resourceType, const QString storageLocation, KisTagSP tag) 1281 { 1282 1283 if (hasTag(tag->url(), resourceType)) { 1284 // Check whether this storage is already registered for this tag 1285 QSqlQuery q; 1286 if (!q.prepare("SELECT storages.location\n" 1287 "FROM tags_storages\n" 1288 ", tags\n" 1289 ", storages\n" 1290 "WHERE tags.id = tags_storages.tag_id\n" 1291 "AND storages.id = tags_storages.storage_id\n" 1292 "AND tags.resource_type_id = (SELECT id\n" 1293 " FROM resource_types\n" 1294 " WHERE name = :resource_type)\n" 1295 "AND tags.url = :url")) 1296 { 1297 qWarning() << "Could not prepare select tags from tags_storages query" << q.lastError(); 1298 } 1299 1300 q.bindValue(":url", tag->url()); 1301 q.bindValue(":resource_type", resourceType); 1302 1303 if (!q.exec()) { 1304 qWarning() << "Could not execute tags_storages query" << q.boundValues() << q.lastError(); 1305 } 1306 1307 // If this tag is not yet linked to the storage, link it 1308 if (!q.first()) { 1309 return linkTagToStorage(tag->url(), resourceType, storageLocation); 1310 } 1311 1312 return true; 1313 } 1314 1315 int tagId; 1316 1317 // Insert the tag 1318 { 1319 QSqlQuery q; 1320 if (!q.prepare("INSERT INTO tags\n" 1321 "(url, name, comment, resource_type_id, active, filename)\n" 1322 "VALUES\n" 1323 "( :url\n" 1324 ", :name\n" 1325 ", :comment\n" 1326 ", (SELECT id\n" 1327 " FROM resource_types\n" 1328 " WHERE name = :resource_type)\n" 1329 ", 1\n" 1330 ", :filename\n" 1331 ");")) { 1332 qWarning() << "Could not prepare insert tag statement" << q.lastError(); 1333 return false; 1334 } 1335 1336 q.bindValue(":url", tag->url()); 1337 q.bindValue(":name", tag->name(false)); 1338 q.bindValue(":comment", tag->comment(false)); 1339 q.bindValue(":resource_type", resourceType); 1340 q.bindValue(":filename", tag->filename()); 1341 1342 if (!q.exec()) { 1343 qWarning() << "Could not insert tag" << q.boundValues() << q.lastError(); 1344 } 1345 1346 tagId = q.lastInsertId().toInt(); 1347 } 1348 1349 { 1350 Q_FOREACH(const QString language, tag->names().keys()) { 1351 1352 QString name = tag->names()[language]; 1353 QString comment = name; 1354 if (tag->comments().contains(language)) { 1355 comment = tag->comments()[language]; 1356 } 1357 1358 QSqlQuery q; 1359 if (!q.prepare("INSERT INTO tag_translations\n" 1360 "( tag_id\n" 1361 ", language\n" 1362 ", name\n" 1363 ", comment\n" 1364 ")\n" 1365 "VALUES\n" 1366 "( :id\n" 1367 ", :language\n" 1368 ", :name\n" 1369 ", :comment\n" 1370 ");")) { 1371 qWarning() << "Could not prepare insert tag_translation query" << q.lastError(); 1372 } 1373 1374 q.bindValue(":id", tagId); 1375 q.bindValue(":language", language); 1376 q.bindValue(":name", name); 1377 q.bindValue(":comment", comment); 1378 1379 if (!q.exec()) { 1380 qWarning() << "Could not execute insert tag_translation query" << q.lastError() << q.boundValues(); 1381 } 1382 } 1383 1384 } 1385 1386 1387 linkTagToStorage(tag->url(), resourceType, storageLocation); 1388 1389 return true; 1390 } 1391 1392 bool KisResourceCacheDb::addTags(KisResourceStorageSP storage, QString resourceType) 1393 { 1394 QSqlDatabase::database().transaction(); 1395 QSharedPointer<KisResourceStorage::TagIterator> iter = storage->tags(resourceType); 1396 while(iter->hasNext()) { 1397 iter->next(); 1398 KisTagSP tag = iter->tag(); 1399 if (tag && tag->valid()) { 1400 if (!addTag(resourceType, storage->location(), tag)) { 1401 qWarning() << "Could not add tag" << tag << "to the database"; 1402 continue; 1403 } 1404 if (!tag->defaultResources().isEmpty()) { 1405 Q_FOREACH(const QString &resourceFileName, tag->defaultResources()) { 1406 if (!tagResource(resourceFileName, tag, resourceType)) { 1407 qWarning() << "Could not tag resource" << QFileInfo(resourceFileName).baseName() << "from" << storage->name() << "filename" << resourceFileName << "with tag" << iter->tag(); 1408 } 1409 } 1410 } 1411 } 1412 } 1413 QSqlDatabase::database().commit(); 1414 return true; 1415 } 1416 1417 bool KisResourceCacheDb::addStorage(KisResourceStorageSP storage, bool preinstalled) 1418 { 1419 bool r = true; 1420 1421 if (!s_valid) { 1422 qWarning() << "The database is not valid"; 1423 return false; 1424 } 1425 1426 { 1427 QSqlQuery q; 1428 r = q.prepare("SELECT * FROM storages WHERE location = :location"); 1429 q.bindValue(":location", changeToEmptyIfNull(KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()))); 1430 r = q.exec(); 1431 if (!r) { 1432 qWarning() << "Could not select from storages"; 1433 return r; 1434 } 1435 if (q.first()) { 1436 debugResource << "Storage already exists" << storage; 1437 return true; 1438 } 1439 } 1440 1441 // Insert the storage; 1442 { 1443 QSqlQuery q; 1444 1445 r = q.prepare("INSERT INTO storages\n " 1446 "(storage_type_id, location, timestamp, pre_installed, active, thumbnail)\n" 1447 "VALUES\n" 1448 "(:storage_type_id, :location, :timestamp, :pre_installed, :active, :thumbnail);"); 1449 1450 if (!r) { 1451 qWarning() << "Could not prepare query" << q.lastError(); 1452 return r; 1453 } 1454 1455 q.bindValue(":storage_type_id", static_cast<int>(storage->type())); 1456 q.bindValue(":location", changeToEmptyIfNull(KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()))); 1457 q.bindValue(":timestamp", storage->timestamp().toSecsSinceEpoch()); 1458 q.bindValue(":pre_installed", preinstalled ? 1 : 0); 1459 q.bindValue(":active", !disabledBundles.contains(storage->name())); 1460 1461 QBuffer buf; 1462 buf.open(QBuffer::WriteOnly); 1463 storage->thumbnail().save(&buf, "PNG"); 1464 buf.close(); 1465 q.bindValue(":thumbnail", buf.data()); 1466 1467 r = q.exec(); 1468 1469 if (!r) qWarning() << "Could not execute query" << q.lastError(); 1470 1471 } 1472 1473 // Insert the metadata 1474 { 1475 QStringList keys = storage->metaDataKeys(); 1476 if (keys.size() > 0) { 1477 1478 QSqlQuery q; 1479 if (!q.prepare("SELECT MAX(id)\n" 1480 "FROM storages\n")) { 1481 qWarning() << "Could not create select storages query for metadata" << q.lastError(); 1482 } 1483 1484 if (!q.exec()) { 1485 qWarning() << "Could not execute select storages query for metadata" << q.lastError(); 1486 } 1487 1488 q.first(); 1489 int id = q.value(0).toInt(); 1490 1491 QMap<QString, QVariant> metadata; 1492 1493 Q_FOREACH(const QString &key, storage->metaDataKeys()) { 1494 metadata[key] = storage->metaData(key); 1495 } 1496 1497 addMetaDataForId(metadata, id, "storages"); 1498 } 1499 } 1500 1501 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { 1502 if (!KisResourceCacheDb::addResources(storage, resourceType)) { 1503 qWarning() << "Failed to add all resources for storage" << storage; 1504 r = false; 1505 } 1506 } 1507 1508 return r; 1509 } 1510 1511 bool KisResourceCacheDb::addStorageTags(KisResourceStorageSP storage) 1512 { 1513 1514 bool r = true; 1515 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { 1516 if (!KisResourceCacheDb::addTags(storage, resourceType)) { 1517 qWarning() << "Failed to add all tags for storage" << storage; 1518 r = false; 1519 } 1520 } 1521 return r; 1522 } 1523 1524 bool KisResourceCacheDb::deleteStorage(QString location) 1525 { 1526 // location is already relative 1527 { 1528 QSqlQuery q; 1529 if (!q.prepare("DELETE FROM resources\n" 1530 "WHERE id IN (SELECT versioned_resources.resource_id\n" 1531 " FROM versioned_resources\n" 1532 " WHERE versioned_resources.storage_id = (SELECT storages.id\n" 1533 " FROM storages\n" 1534 " WHERE storages.location = :location)\n" 1535 " );")) { 1536 qWarning() << "Could not prepare delete resources query in deleteStorage" << q.lastError(); 1537 return false; 1538 } 1539 q.bindValue(":location", changeToEmptyIfNull(location)); 1540 if (!q.exec()) { 1541 qWarning() << "Could not execute delete resources query in deleteStorage" << q.lastError(); 1542 return false; 1543 } 1544 } 1545 1546 { 1547 QSqlQuery q; 1548 if (!q.prepare("DELETE FROM tags \n" 1549 "WHERE id IN (SELECT tags_storages.tag_id \n " 1550 " FROM tags_storages \n" 1551 " WHERE tags_storages.storage_id = \n" 1552 " (SELECT storages.id\n" 1553 " FROM storages\n" 1554 " WHERE storages.location = :location)\n" 1555 " );")) { 1556 qWarning() << "Could not prepare delete tag query" << q.lastError(); 1557 return false; 1558 } 1559 q.bindValue(":location", location); 1560 if (!q.exec()) { 1561 qWarning() << "Could not execute delete tag query" << q.lastError(); 1562 return false; 1563 } 1564 } 1565 1566 { 1567 QSqlQuery q; 1568 if (!q.prepare("DELETE FROM tags_storages \n" 1569 " WHERE tags_storages.storage_id = \n" 1570 " (SELECT storages.id\n" 1571 " FROM storages\n" 1572 " WHERE storages.location = :location);")) { 1573 qWarning() << "Could not prepare delete tag storage query" << q.lastError(); 1574 return false; 1575 } 1576 q.bindValue(":location", location); 1577 if (!q.exec()) { 1578 qWarning() << "Could not execute delete tag storage query" << q.lastError(); 1579 return false; 1580 } 1581 } 1582 1583 { 1584 QSqlQuery q; 1585 if (!q.prepare("DELETE FROM versioned_resources\n" 1586 "WHERE storage_id = (SELECT storages.id\n" 1587 " FROM storages\n" 1588 " WHERE storages.location = :location);")) { 1589 qWarning() << "Could not prepare delete versioned_resources query" << q.lastError(); 1590 return false; 1591 } 1592 q.bindValue(":location", changeToEmptyIfNull(location)); 1593 if (!q.exec()) { 1594 qWarning() << "Could not execute delete versioned_resources query" << q.lastError(); 1595 return false; 1596 } 1597 } 1598 1599 { 1600 QSqlQuery q; 1601 if (!q.prepare("DELETE FROM storages\n" 1602 "WHERE location = :location;")) { 1603 qWarning() << "Could not prepare delete storages query" << q.lastError(); 1604 return false; 1605 } 1606 q.bindValue(":location", changeToEmptyIfNull(location)); 1607 if (!q.exec()) { 1608 qWarning() << "Could not execute delete storages query" << q.lastError(); 1609 return false; 1610 } 1611 } 1612 return true; 1613 } 1614 1615 bool KisResourceCacheDb::deleteStorage(KisResourceStorageSP storage) 1616 { 1617 return deleteStorage(KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); 1618 } 1619 1620 namespace { 1621 struct ResourceVersion : public boost::less_than_comparable<ResourceVersion> 1622 { 1623 int resourceId = -1; 1624 int version = -1; 1625 QDateTime timestamp; 1626 QString url; 1627 1628 bool operator<(const ResourceVersion &rhs) const { 1629 return resourceId < rhs.resourceId || 1630 (resourceId == rhs.resourceId && version < rhs.version); 1631 } 1632 1633 struct CompareByResourceId { 1634 bool operator() (const ResourceVersion &lhs, const ResourceVersion &rhs) const { 1635 return lhs.resourceId < rhs.resourceId; 1636 } 1637 }; 1638 1639 1640 }; 1641 1642 [[maybe_unused]] 1643 QDebug operator<<(QDebug dbg, const ResourceVersion &ver) 1644 { 1645 dbg.nospace() << "ResourceVersion(" 1646 << ver.resourceId << ", " 1647 << ver.version << ", " 1648 << ver.timestamp << ", " 1649 << ver.url << ")"; 1650 1651 return dbg.space(); 1652 } 1653 } 1654 1655 bool KisResourceCacheDb::synchronizeStorage(KisResourceStorageSP storage) 1656 { 1657 QElapsedTimer t; 1658 t.start(); 1659 1660 QSqlDatabase::database().transaction(); 1661 1662 if (!s_valid) { 1663 qWarning() << "KisResourceCacheDb::addResource: The database is not valid"; 1664 return false; 1665 } 1666 1667 bool success = true; 1668 1669 // Find the storage in the database 1670 QSqlQuery q; 1671 if (!q.prepare("SELECT id\n" 1672 ", timestamp\n" 1673 ", pre_installed\n" 1674 "FROM storages\n" 1675 "WHERE location = :location\n")) { 1676 qWarning() << "Could not prepare storage timestamp statement" << q.lastError(); 1677 } 1678 1679 q.bindValue(":location", changeToEmptyIfNull(KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()))); 1680 if (!q.exec()) { 1681 qWarning() << "Could not execute storage timestamp statement" << q.boundValues() << q.lastError(); 1682 } 1683 1684 if (!q.first()) { 1685 // This is a new storage, the user must have dropped it in the path before restarting Krita, so add it. 1686 debugResource << "Adding storage to the database:" << storage; 1687 if (!addStorage(storage, false)) { 1688 qWarning() << "Could not add new storage" << storage->name() << "to the database"; 1689 success = false; 1690 } 1691 return true; 1692 } 1693 1694 storage->setStorageId(q.value("id").toInt()); 1695 1696 /// We compare resource versions one-by-one because the storage may have multiple 1697 /// versions of them 1698 1699 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { 1700 1701 /// Firstly, fetch information about the existing resources 1702 /// in the storage 1703 1704 QVector<ResourceVersion> resourcesInStorage; 1705 1706 /// A fake resourceId to group resources which are not yet present 1707 /// in the database. This value is always negative, therefore it 1708 /// cannot overlap with normal ids. 1709 1710 int nextInexistentResourceId = std::numeric_limits<int>::min(); 1711 1712 QSharedPointer<KisResourceStorage::ResourceIterator> iter = storage->resources(resourceType); 1713 while (iter->hasNext()) { 1714 iter->next(); 1715 1716 const int firstResourceVersionPosition = resourcesInStorage.size(); 1717 1718 int detectedResourceId = nextInexistentResourceId; 1719 QSharedPointer<KisResourceStorage::ResourceIterator> verIt = 1720 iter->versions(); 1721 1722 while (verIt->hasNext()) { 1723 verIt->next(); 1724 1725 // verIt->url() contains paths like "brushes/ink.png" or "brushes/subfolder/splash.png". 1726 // we need to cut off the first part and get "ink.png" in the first case, 1727 // but "subfolder/splash.png" in the second case in order for subfolders to work 1728 // so it cannot just use QFileInfo(verIt->url()).fileName() here. 1729 QString path = QDir::fromNativeSeparators(verIt->url()); // make sure it uses Unix separators 1730 int folderEndIdx = path.indexOf("/"); 1731 QString properFilenameWithSubfolders = path.right(path.length() - folderEndIdx - 1); 1732 int id = resourceIdForResource(properFilenameWithSubfolders, 1733 verIt->type(), 1734 KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); 1735 1736 ResourceVersion item; 1737 item.url = verIt->url(); 1738 item.version = verIt->guessedVersion(); 1739 1740 // we use lower precision than the normal QDateTime 1741 item.timestamp = QDateTime::fromSecsSinceEpoch(verIt->lastModified().toSecsSinceEpoch()); 1742 1743 item.resourceId = id; 1744 1745 if (detectedResourceId < 0 && id >= 0) { 1746 detectedResourceId = id; 1747 } 1748 1749 resourcesInStorage.append(item); 1750 } 1751 1752 /// Assign the detected resource id to all the versions of 1753 /// this resource (if they are not present in the database). 1754 /// If no id has been detected, then a fake one will be assigned. 1755 1756 for (int i = firstResourceVersionPosition; i < resourcesInStorage.size(); i++) { 1757 if (resourcesInStorage[i].resourceId < 0) { 1758 resourcesInStorage[i].resourceId = detectedResourceId; 1759 } 1760 } 1761 1762 nextInexistentResourceId++; 1763 } 1764 1765 1766 /// Secondly, fetch the resources present in the database 1767 1768 QVector<ResourceVersion> resourcesInDatabase; 1769 1770 QSqlQuery q; 1771 q.setForwardOnly(true); 1772 if (!q.prepare("SELECT versioned_resources.resource_id, versioned_resources.filename, versioned_resources.version, versioned_resources.timestamp\n" 1773 "FROM versioned_resources\n" 1774 ", resource_types\n" 1775 ", resources\n" 1776 "WHERE resources.resource_type_id = resource_types.id\n" 1777 "AND resources.id = versioned_resources.resource_id\n" 1778 "AND resource_types.name = :resource_type\n" 1779 "AND versioned_resources.storage_id == :storage_id")) { 1780 qWarning() << "Could not prepare resource by type query" << q.lastError(); 1781 success = false; 1782 continue; 1783 } 1784 1785 q.bindValue(":resource_type", resourceType); 1786 q.bindValue(":storage_id", int(storage->storageId())); 1787 1788 if (!q.exec()) { 1789 qWarning() << "Could not exec resource by type query" << q.boundValues() << q.lastError(); 1790 success = false; 1791 continue; 1792 } 1793 1794 while (q.next()) { 1795 ResourceVersion item; 1796 item.url = resourceType + "/" + q.value(1).toString(); 1797 item.version = q.value(2).toInt(); 1798 item.timestamp = QDateTime::fromSecsSinceEpoch(q.value(3).toInt()); 1799 item.resourceId = q.value(0).toInt(); 1800 1801 resourcesInDatabase.append(item); 1802 } 1803 1804 QSet<int> resourceIdForUpdate; 1805 1806 std::sort(resourcesInStorage.begin(), resourcesInStorage.end()); 1807 std::sort(resourcesInDatabase.begin(), resourcesInDatabase.end()); 1808 1809 auto itA = resourcesInStorage.begin(); 1810 auto endA = resourcesInStorage.end(); 1811 1812 auto itB = resourcesInDatabase.begin(); 1813 auto endB = resourcesInDatabase.end(); 1814 1815 /// The head of itA array contains some resources with fake 1816 /// (negative) resourceId. These resources are obviously new 1817 /// resources and should be added to the cache database. 1818 1819 while (itA != endA) { 1820 if (itA->resourceId >= 0) break; 1821 1822 KoResourceSP res = storage->resource(itA->url); 1823 1824 if (!res) { 1825 KisUsageLogger::log("Could not load resource " + itA->url); 1826 ++itA; 1827 continue; 1828 } 1829 1830 res->setVersion(itA->version); 1831 res->setMD5Sum(storage->resourceMd5(itA->url)); 1832 if (!res->valid()) { 1833 KisUsageLogger::log("Could not retrieve md5 for resource " + itA->url); 1834 ++itA; 1835 continue; 1836 } 1837 1838 const bool retval = addResource(storage, itA->timestamp, res, resourceType); 1839 if (!retval) { 1840 KisUsageLogger::log("Could not add resource " + itA->url); 1841 ++itA; 1842 continue; 1843 } 1844 1845 const int resourceId = res->resourceId(); 1846 KIS_SAFE_ASSERT_RECOVER(resourceId >= 0) { 1847 KisUsageLogger::log("Could not get id for resource " + itA->url); 1848 ++itA; 1849 continue; 1850 } 1851 1852 auto nextResource = std::upper_bound(itA, endA, *itA, ResourceVersion::CompareByResourceId()); 1853 for (auto it = std::next(itA); it != nextResource; ++it) { 1854 KoResourceSP res = storage->resource(it->url); 1855 res->setVersion(it->version); 1856 res->setMD5Sum(storage->resourceMd5(it->url)); 1857 if (!res->valid()) { 1858 continue; 1859 } 1860 1861 const bool retval = addResourceVersion(resourceId, it->timestamp, storage, res); 1862 KIS_SAFE_ASSERT_RECOVER(retval) { 1863 KisUsageLogger::log("Could not add version for resource " + itA->url); 1864 continue; 1865 } 1866 } 1867 1868 itA = nextResource; 1869 } 1870 1871 /// Now both arrays are sorted in resourceId/version/timestamp 1872 /// order. It lets us easily find the resources that are unique 1873 /// to the storage or database. If *itA < *itB, then the resource 1874 /// is present in the storage only and should be added to the 1875 /// database. If *itA > *itB, then the resource is present in 1876 /// the database only and should be removed (because it has been 1877 /// removed from the storage); 1878 1879 while (itA != endA || itB != endB) { 1880 if ((itA != endA && itB != endB && *itA < *itB) || 1881 itB == endB) { 1882 1883 // add a version to the database 1884 1885 KoResourceSP res = storage->resource(itA->url); 1886 if (res) { 1887 res->setVersion(itA->version); 1888 res->setMD5Sum(storage->resourceMd5(itA->url)); 1889 1890 const bool result = addResourceVersionImpl(itA->resourceId, itA->timestamp, storage, res); 1891 KIS_SAFE_ASSERT_RECOVER_NOOP(result); 1892 1893 resourceIdForUpdate.insert(itA->resourceId); 1894 } 1895 ++itA; 1896 1897 } else if ((itA != endA && itB != endB && *itA > *itB) || 1898 itA == endA) { 1899 1900 // remove a version from the database 1901 const bool result = removeResourceVersionImpl(itB->resourceId, itB->version, storage); 1902 KIS_SAFE_ASSERT_RECOVER_NOOP(result); 1903 resourceIdForUpdate.insert(itB->resourceId); 1904 ++itB; 1905 1906 } else { 1907 // resources are equal, just skip them 1908 ++itA; 1909 ++itB; 1910 } 1911 } 1912 1913 1914 /// In the main loop we modified the versioned_resource table only, 1915 /// now we should update the head resources table with the latest 1916 /// version of the resource (and upload the thumbnail as well) 1917 1918 for (auto it = resourceIdForUpdate.begin(); it != resourceIdForUpdate.end(); ++it) { 1919 updateResourceTableForResourceIfNeeded(*it, resourceType, storage); 1920 } 1921 } 1922 1923 QSqlDatabase::database().commit(); 1924 debugResource << "Synchronizing the storages took" << t.elapsed() << "milliseconds for" << storage->location(); 1925 1926 return success; 1927 } 1928 1929 void KisResourceCacheDb::deleteTemporaryResources() 1930 { 1931 QSqlDatabase::database().transaction(); 1932 1933 QSqlQuery q; 1934 1935 if (!q.prepare("DELETE FROM versioned_resources\n" 1936 "WHERE storage_id in (SELECT id\n" 1937 " FROM storages\n" 1938 " WHERE storage_type_id == :storage_type)")) 1939 { 1940 qWarning() << "Could not prepare delete versioned resources from Unknown or Memory storages query." << q.lastError(); 1941 } 1942 1943 q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); 1944 1945 if (!q.exec()) { 1946 qWarning() << "Could not execute delete versioned resources from Unknown or Memory storages query." << q.lastError(); 1947 } 1948 1949 if (!q.prepare("DELETE FROM resources\n" 1950 "WHERE storage_id in (SELECT id\n" 1951 " FROM storages\n" 1952 " WHERE storage_type_id == :storage_type)")) 1953 { 1954 qWarning() << "Could not prepare delete resources from Unknown or Memory storages query." << q.lastError(); 1955 } 1956 1957 q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); 1958 1959 if (!q.exec()) { 1960 qWarning() << "Could not execute delete resources from Unknown or Memory storages query." << q.lastError(); 1961 } 1962 1963 1964 if (!q.prepare("DELETE FROM versioned_resources\n" 1965 "WHERE resource_id IN (SELECT id FROM resources\n" 1966 " WHERE temporary = 1)")) { 1967 qWarning() << "Could not prepare delete temporary versioned resources query." << q.lastError(); 1968 } 1969 1970 if (!q.exec()) { 1971 qWarning() << "Could not execute delete temporary versioned resources query." << q.lastError(); 1972 } 1973 1974 if (!q.prepare("DELETE FROM resources\n" 1975 "WHERE temporary = 1")) { 1976 qWarning() << "Could not prepare delete temporary resources query." << q.lastError(); 1977 return; 1978 } 1979 1980 if (!q.exec()) { 1981 qWarning() << "Could not execute delete temporary resources query." << q.lastError(); 1982 } 1983 1984 if (!q.prepare("DELETE FROM storages\n" 1985 "WHERE storage_type_id == :storage_type\n")) 1986 { 1987 qWarning() << "Could not prepare delete Unknown or Memory storages query." << q.lastError(); 1988 } 1989 1990 q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); 1991 1992 if (!q.exec()) { 1993 qWarning() << "Could not execute delete Unknown or Memory storages query." << q.lastError(); 1994 } 1995 1996 QSqlDatabase::database().commit(); 1997 } 1998 1999 bool KisResourceCacheDb::registerResourceType(const QString &resourceType) 2000 { 2001 // Check whether the type already exists 2002 { 2003 QSqlQuery q; 2004 if (!q.prepare("SELECT count(*)\n" 2005 "FROM resource_types\n" 2006 "WHERE name = :resource_type\n")) { 2007 qWarning() << "Could not prepare select from resource_types query" << q.lastError(); 2008 return false; 2009 } 2010 q.bindValue(":resource_type", resourceType); 2011 if (!q.exec()) { 2012 qWarning() << "Could not execute select from resource_types query" << q.lastError(); 2013 return false; 2014 } 2015 q.first(); 2016 int rowCount = q.value(0).toInt(); 2017 if (rowCount > 0) { 2018 return true; 2019 } 2020 } 2021 // if not, add it 2022 QFile f(":/fill_resource_types.sql"); 2023 if (f.open(QFile::ReadOnly)) { 2024 QString sql = f.readAll(); 2025 QSqlQuery q(sql); 2026 q.addBindValue(resourceType); 2027 if (!q.exec()) { 2028 qWarning() << "Could not insert" << resourceType << q.lastError(); 2029 return false; 2030 } 2031 return true; 2032 } 2033 qWarning() << "Could not open fill_resource_types.sql"; 2034 return false; 2035 } 2036 2037 QMap<QString, QVariant> KisResourceCacheDb::metaDataForId(int id, const QString &tableName) 2038 { 2039 QMap<QString, QVariant> map; 2040 2041 QSqlQuery q; 2042 q.setForwardOnly(true); 2043 if (!q.prepare("SELECT key\n" 2044 ", value\n" 2045 "FROM metadata\n" 2046 "WHERE foreign_id = :id\n" 2047 "AND table_name = :table")) { 2048 qWarning() << "Could not prepare metadata query" << q.lastError(); 2049 return map; 2050 } 2051 2052 q.bindValue(":id", id); 2053 q.bindValue(":table", tableName); 2054 2055 if (!q.exec()) { 2056 qWarning() << "Could not execute metadata query" << q.lastError(); 2057 return map; 2058 } 2059 2060 while (q.next()) { 2061 QString key = q.value(0).toString(); 2062 QByteArray ba = q.value(1).toByteArray(); 2063 if (!ba.isEmpty()) { 2064 QDataStream ds(QByteArray::fromBase64(ba)); 2065 QVariant value; 2066 ds >> value; 2067 map[key] = value; 2068 } 2069 } 2070 2071 return map; 2072 } 2073 2074 bool KisResourceCacheDb::updateMetaDataForId(const QMap<QString, QVariant> map, int id, const QString &tableName) 2075 { 2076 QSqlDatabase::database().transaction(); 2077 2078 { 2079 QSqlQuery q; 2080 if (!q.prepare("DELETE FROM metadata\n" 2081 "WHERE foreign_id = :id\n" 2082 "AND table_name = :table\n")) { 2083 qWarning() << "Could not prepare delete metadata query" << q.lastError(); 2084 return false; 2085 } 2086 2087 q.bindValue(":id", id); 2088 q.bindValue(":table", tableName); 2089 2090 if (!q.exec()) { 2091 QSqlDatabase::database().rollback(); 2092 qWarning() << "Could not execute delete metadata query" << q.lastError(); 2093 return false; 2094 2095 } 2096 } 2097 2098 if (addMetaDataForId(map, id, tableName)) { 2099 QSqlDatabase::database().commit(); 2100 } 2101 else { 2102 QSqlDatabase::database().rollback(); 2103 } 2104 return true; 2105 } 2106 2107 bool KisResourceCacheDb::addMetaDataForId(const QMap<QString, QVariant> map, int id, const QString &tableName) 2108 { 2109 2110 QSqlQuery q; 2111 if (!q.prepare("INSERT INTO metadata\n" 2112 "(foreign_id, table_name, key, value)\n" 2113 "VALUES\n" 2114 "(:id, :table, :key, :value)")) { 2115 QSqlDatabase::database().rollback(); 2116 qWarning() << "Could not create insert metadata query" << q.lastError(); 2117 return false; 2118 } 2119 2120 QMap<QString, QVariant>::const_iterator iter = map.cbegin(); 2121 while (iter != map.cend()) { 2122 q.bindValue(":id", id); 2123 q.bindValue(":table", tableName); 2124 q.bindValue(":key", iter.key()); 2125 2126 QVariant v = iter.value(); 2127 if (!v.isNull() && v.isValid()) { 2128 QByteArray ba; 2129 QDataStream ds(&ba, QIODevice::WriteOnly); 2130 ds << v; 2131 ba = ba.toBase64(); 2132 q.bindValue(":value", QString::fromLatin1(ba)); 2133 2134 if (!q.exec()) { 2135 qWarning() << "Could not insert metadata" << q.lastError(); 2136 return false; 2137 } 2138 } 2139 ++iter; 2140 } 2141 return true; 2142 }