File indexing completed on 2025-01-05 03:53:59
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2007-04-16 0007 * Description : Core database Schema updater 0008 * 0009 * SPDX-FileCopyrightText: 2007-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "coredbschemaupdater.h" 0017 0018 // Qt includes 0019 0020 #include <QFileInfo> 0021 #include <QFile> 0022 #include <QDir> 0023 #include <QUrl> 0024 #include <QUrlQuery> 0025 0026 // KDE includes 0027 0028 #include <klocalizedstring.h> 0029 #include <kconfiggroup.h> 0030 #include <ksharedconfig.h> 0031 0032 // Local includes 0033 0034 #include "drawdecoder.h" 0035 #include "digikam_debug.h" 0036 #include "coredbtransaction.h" 0037 #include "coredbchecker.h" 0038 #include "collectionmanager.h" 0039 #include "collectionlocation.h" 0040 #include "collectionscanner.h" 0041 #include "itemquerybuilder.h" 0042 #include "collectionscannerobserver.h" 0043 #include "digikam_config.h" 0044 #include "digikam_globals.h" 0045 0046 namespace Digikam 0047 { 0048 0049 int CoreDbSchemaUpdater::schemaVersion() 0050 { 0051 return 16; 0052 } 0053 0054 int CoreDbSchemaUpdater::filterSettingsVersion() 0055 { 0056 return 17; 0057 } 0058 0059 int CoreDbSchemaUpdater::uniqueHashVersion() 0060 { 0061 return 2; 0062 } 0063 0064 bool CoreDbSchemaUpdater::isUniqueHashUpToDate() 0065 { 0066 return (CoreDbAccess().db()->getUniqueHashVersion() >= uniqueHashVersion()); 0067 } 0068 0069 // -------------------------------------------------------------------------------------- 0070 0071 class Q_DECL_HIDDEN CoreDbSchemaUpdater::Private 0072 { 0073 0074 public: 0075 0076 explicit Private() 0077 : setError(false), 0078 backend (nullptr), 0079 albumDB (nullptr), 0080 dbAccess(nullptr), 0081 observer(nullptr) 0082 { 0083 } 0084 0085 bool setError; 0086 0087 QVariant currentVersion; 0088 QVariant currentRequiredVersion; 0089 0090 CoreDbBackend* backend; 0091 CoreDB* albumDB; 0092 DbEngineParameters parameters; 0093 0094 // legacy 0095 CoreDbAccess* dbAccess; 0096 0097 QString lastErrorMessage; 0098 InitializationObserver* observer; 0099 }; 0100 0101 CoreDbSchemaUpdater::CoreDbSchemaUpdater(CoreDB* const albumDB, 0102 CoreDbBackend* const backend, 0103 const DbEngineParameters& parameters) 0104 : d(new Private) 0105 { 0106 d->backend = backend; 0107 d->albumDB = albumDB; 0108 d->parameters = parameters; 0109 } 0110 0111 CoreDbSchemaUpdater::~CoreDbSchemaUpdater() 0112 { 0113 delete d; 0114 } 0115 0116 void CoreDbSchemaUpdater::setCoreDbAccess(CoreDbAccess* const dbAccess) 0117 { 0118 d->dbAccess = dbAccess; 0119 } 0120 0121 const QString CoreDbSchemaUpdater::getLastErrorMessage() 0122 { 0123 return d->lastErrorMessage; 0124 } 0125 0126 bool CoreDbSchemaUpdater::update() 0127 { 0128 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: running schema update"; 0129 bool success = startUpdates(); 0130 0131 // cancelled? 0132 0133 if (d->observer && !d->observer->continueQuery()) 0134 { 0135 return false; 0136 } 0137 0138 // even on failure, try to set current version - it may have incremented 0139 0140 setVersionSettings(); 0141 0142 if (!success) 0143 { 0144 return false; 0145 } 0146 0147 updateFilterSettings(); 0148 0149 if (d->observer) 0150 { 0151 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateSuccess); 0152 } 0153 0154 return success; 0155 } 0156 0157 void CoreDbSchemaUpdater::setVersionSettings() 0158 { 0159 if (d->currentVersion.isValid()) 0160 { 0161 d->albumDB->setSetting(QLatin1String("DBVersion"), 0162 QString::number(d->currentVersion.toInt())); 0163 } 0164 0165 if (d->currentRequiredVersion.isValid()) 0166 { 0167 d->albumDB->setSetting(QLatin1String("DBVersionRequired"), 0168 QString::number(d->currentRequiredVersion.toInt())); 0169 } 0170 } 0171 0172 static QVariant safeToVariant(const QString& s) 0173 { 0174 if (s.isEmpty()) 0175 { 0176 return QVariant(); 0177 } 0178 else 0179 { 0180 return s.toInt(); 0181 } 0182 } 0183 0184 void CoreDbSchemaUpdater::readVersionSettings() 0185 { 0186 d->currentVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersion"))); 0187 d->currentRequiredVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersionRequired"))); 0188 } 0189 0190 void CoreDbSchemaUpdater::setObserver(InitializationObserver* const observer) 0191 { 0192 d->observer = observer; 0193 } 0194 0195 bool CoreDbSchemaUpdater::startUpdates() 0196 { 0197 // Do we have sufficient privileges 0198 0199 QStringList insufficientRights; 0200 CoreDbPrivilegesChecker checker(d->parameters); 0201 0202 if (!checker.checkPrivileges(insufficientRights)) 0203 { 0204 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: insufficient rights on database."; 0205 0206 QString errorMsg = i18n("You have insufficient privileges on the database.\n" 0207 "Following privileges are not assigned to you:\n %1\n" 0208 "Check your privileges on the database and restart digiKam.", 0209 insufficientRights.join(QLatin1String(",\n"))); 0210 d->lastErrorMessage = errorMsg; 0211 0212 if (d->observer) 0213 { 0214 d->observer->error(errorMsg); 0215 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 0216 } 0217 0218 return false; 0219 } 0220 0221 // First step: do we have an empty database? 0222 0223 QStringList tables = d->backend->tables(); 0224 0225 if (tables.contains(QLatin1String("Albums"), Qt::CaseInsensitive)) 0226 { 0227 // Find out schema version of db file 0228 0229 readVersionSettings(); 0230 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: have a structure version " << d->currentVersion.toInt(); 0231 0232 // We absolutely require the DBVersion setting 0233 0234 if (!d->currentVersion.isValid()) 0235 { 0236 // Something is damaged. Give up. 0237 0238 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: version not available! Giving up schema upgrading."; 0239 0240 QString errorMsg = i18n("The database is not valid: " 0241 "the \"DBVersion\" setting does not exist. " 0242 "The current database schema version cannot be verified. " 0243 "Try to start with an empty database. "); 0244 d->lastErrorMessage=errorMsg; 0245 0246 if (d->observer) 0247 { 0248 d->observer->error(errorMsg); 0249 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 0250 } 0251 0252 return false; 0253 } 0254 0255 // current version describes the current state of the schema in the db, 0256 // schemaVersion is the version required by the program. 0257 0258 if (d->currentVersion.toInt() > schemaVersion()) 0259 { 0260 // trying to open a database with a more advanced than this CoreDbSchemaUpdater supports 0261 0262 if (d->currentRequiredVersion.isValid() && d->currentRequiredVersion.toInt() <= schemaVersion()) 0263 { 0264 // version required may be less than current version 0265 0266 return true; 0267 } 0268 else 0269 { 0270 QString errorMsg = i18n("The database has been used with a more recent version of digiKam " 0271 "and has been updated to a database schema which cannot be used with this version. " 0272 "(This means this digiKam version is too old, or the database format is too recent.) " 0273 "Please use the more recent version of digiKam that you used before. "); 0274 d->lastErrorMessage=errorMsg; 0275 0276 if (d->observer) 0277 { 0278 d->observer->error(errorMsg); 0279 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 0280 } 0281 0282 return false; 0283 } 0284 } 0285 else 0286 { 0287 return makeUpdates(); 0288 } 0289 } 0290 else 0291 { 0292 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no database file available"; 0293 0294 // Legacy handling? 0295 0296 // first test if there are older files that need to be upgraded. 0297 // This applies to "digikam.db" for 0.7 and "digikam3.db" for 0.8 and 0.9, 0298 // all only SQLite databases. 0299 0300 // Version numbers used in this source file are a bit confused for the historic versions. 0301 // Version 1 is 0.6 (no db), Version 2 is 0.7 (SQLite 2), 0302 // Version 3 is 0.8-0.9, 0303 // Version 3 wrote the setting "DBVersion", "1", 0304 // Version 4 is 0.10, the digikam3.db file copied to digikam4.db, no schema changes. 0305 // Version 4 writes "4", and from now on version x writes "x". 0306 // Version 5 includes the schema changes from 0.9 to 0.10 0307 // Version 6 brought new tables for history and ImageTagProperties, with version 2.0 0308 // Version 7 brought the VideoMetadata table with 3.0 0309 0310 if (d->parameters.isSQLite()) 0311 { 0312 QFileInfo currentDBFile(d->parameters.databaseNameCore); 0313 QFileInfo digikam3DB(currentDBFile.dir(), QLatin1String("digikam3.db")); 0314 0315 if (digikam3DB.exists()) 0316 { 0317 if (!copyV3toV4(digikam3DB.filePath(), currentDBFile.filePath())) 0318 { 0319 return false; 0320 } 0321 0322 // d->currentVersion is now 4; 0323 0324 return makeUpdates(); 0325 } 0326 } 0327 0328 // No legacy handling: start with a fresh db 0329 0330 if (!createDatabase() || !createFilterSettings()) 0331 { 0332 QString errorMsg = i18n("Failed to create tables in database.\n ") + d->backend->lastError(); 0333 d->lastErrorMessage = errorMsg; 0334 0335 if (d->observer) 0336 { 0337 d->observer->error(errorMsg); 0338 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 0339 } 0340 0341 return false; 0342 } 0343 0344 return true; 0345 } 0346 } 0347 0348 bool CoreDbSchemaUpdater::beginWrapSchemaUpdateStep() 0349 { 0350 if (!d->backend->beginTransaction()) 0351 { 0352 QFileInfo currentDBFile(d->parameters.databaseNameCore); 0353 QString errorMsg = i18n("Failed to open a database transaction on your database file \"%1\". " 0354 "This is unusual. Please check that you can access the file and no " 0355 "other process has currently locked the file. " 0356 "If the problem persists you can get help from the digikam developers mailing list (see www.digikam.org/support). " 0357 "As well, please have a look at what digiKam prints on the console. ", 0358 QDir::toNativeSeparators(currentDBFile.filePath())); 0359 d->observer->error(errorMsg); 0360 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 0361 0362 return false; 0363 } 0364 0365 return true; 0366 } 0367 0368 bool CoreDbSchemaUpdater::endWrapSchemaUpdateStep(bool stepOperationSuccess, const QString& errorMsg) 0369 { 0370 if (!stepOperationSuccess) 0371 { 0372 d->backend->rollbackTransaction(); 0373 0374 if (d->observer) 0375 { 0376 // error or cancelled? 0377 0378 if (!d->observer->continueQuery()) 0379 { 0380 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update cancelled by user"; 0381 } 0382 else if (!d->setError) 0383 { 0384 d->observer->error(errorMsg); 0385 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 0386 } 0387 } 0388 0389 return false; 0390 } 0391 0392 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); 0393 d->backend->commitTransaction(); 0394 0395 return true; 0396 } 0397 0398 bool CoreDbSchemaUpdater::makeUpdates() 0399 { 0400 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: makeUpdates " << d->currentVersion.toInt() << " to " << schemaVersion(); 0401 0402 if (d->currentVersion.toInt() < schemaVersion()) 0403 { 0404 if (d->currentVersion.toInt() < 5) 0405 { 0406 if (!beginWrapSchemaUpdateStep()) 0407 { 0408 return false; 0409 } 0410 0411 // v4 was always SQLite 0412 0413 QFileInfo currentDBFile(d->parameters.databaseNameCore); 0414 QString errorMsg = i18n("The schema updating process from version 4 to 6 failed, " 0415 "caused by an error that we did not expect. " 0416 "You can try to discard your old database and start with an empty one. " 0417 "(In this case, please move the database files " 0418 "\"%1\" and \"%2\" from the directory \"%3\"). " 0419 "More probably you will want to report this error to the digikam developers mailing list (see www.digikam.org/support). " 0420 "As well, please have a look at what digiKam prints on the console. ", 0421 QLatin1String("digikam3.db"), 0422 QLatin1String("digikam4.db"), 0423 QDir::toNativeSeparators(currentDBFile.dir().path())); 0424 0425 if (!endWrapSchemaUpdateStep(updateV4toV7(), errorMsg)) 0426 { 0427 return false; 0428 } 0429 0430 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating v4 to v6"; 0431 0432 // Still set these even in >= 1.4 because 0.10 - 1.3 may want to apply the updates if not set 0433 0434 setLegacySettingEntries(); 0435 } 0436 0437 // Incremental updates, starting from version 5 0438 0439 for (int v = d->currentVersion.toInt() ; v < schemaVersion() ; ++v) 0440 { 0441 int targetVersion = v + 1; 0442 0443 if (!beginWrapSchemaUpdateStep()) 0444 { 0445 return false; 0446 } 0447 0448 QString errorMsg; 0449 0450 if (d->parameters.internalServer) 0451 { 0452 errorMsg = i18n("Failed to update the database schema from version %1 to version %2.\n" 0453 "The cause could be a missing upgrade of the database to the current " 0454 "server version. Now start digiKam again to perform a required " 0455 "upgrade of the database.", 0456 d->currentVersion.toInt(), 0457 targetVersion); 0458 } 0459 else 0460 { 0461 errorMsg = i18n("Failed to update the database schema from version %1 to version %2.\n" 0462 "Please read the error messages printed on the console and " 0463 "report this error as a bug at bugs.kde.org.", 0464 d->currentVersion.toInt(), 0465 targetVersion); 0466 } 0467 0468 if (!endWrapSchemaUpdateStep(updateToVersion(targetVersion), errorMsg)) 0469 { 0470 return false; 0471 } 0472 0473 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); 0474 } 0475 0476 // add future updates here 0477 } 0478 0479 return true; 0480 } 0481 0482 void CoreDbSchemaUpdater::defaultFilterSettings(QStringList& defaultItemFilter, QStringList& defaultVideoFilter, 0483 QStringList& defaultAudioFilter) 0484 { 0485 // NOTE for updating: 0486 // When changing anything here, just increment filterSettingsVersion() so that the changes take effect 0487 0488 // https://en.wikipedia.org/wiki/Image_file_formats 0489 0490 defaultItemFilter << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe") // JPEG 0491 << QLatin1String("mpo") 0492 << QLatin1String("jp2") << QLatin1String("j2k") << QLatin1String("jpx") // JPEG-2000 0493 << QLatin1String("jpc") << QLatin1String("pgx") 0494 << QLatin1String("tif") << QLatin1String("tiff") // Tagged Image Format 0495 << QLatin1String("png") // Portable Network Graphic 0496 << QLatin1String("eps") // Encapsulated Postscript 0497 << QLatin1String("fit") << QLatin1String("fts") << QLatin1String("fits") // Flexible Image Transport System (https://fr.wikipedia.org/wiki/Flexible_Image_Transport_System) 0498 << QLatin1String("gif") // Graphics Interchange Format 0499 << QLatin1String("xbm") << QLatin1String("xpm") // X Bitmap and Pixmap 0500 << QLatin1String("ppm") << QLatin1String("pbm") << QLatin1String("pgm") // Portable Pixmap 0501 << QLatin1String("pnm") 0502 << QLatin1String("pic") // PICtor Image Format 0503 << QLatin1String("cur ") << QLatin1String("ico") << QLatin1String("icns") // Windows/Apple Cursor and Icon 0504 << QLatin1String("pgf") // Portable Graphics Format 0505 << QLatin1String("bmp") // Windows Bitmap 0506 << QLatin1String("pcx") // Picture Exchange 0507 << QLatin1String("tga") // Truevision Graphics Adapter 0508 << QLatin1String("sgi") << QLatin1String("rgb") << QLatin1String("rgba") // Silicon Graphics Image 0509 << QLatin1String("bw") 0510 << QLatin1String("heic") << QLatin1String("heif") << QLatin1String("hif") // High Efficiency Image File Format 0511 << QLatin1String("jxl") // JPEG-XL 0512 << QLatin1String("avif") // AV1 Image File Format 0513 << QLatin1String("wbmp") // Wireless Application Protocol Bitmap Format 0514 << QLatin1String("webp") // Web Photo 0515 ; 0516 0517 // Raster graphics editor containers: https://en.wikipedia.org/wiki/Raster_graphics_editor 0518 0519 defaultItemFilter << QLatin1String("xcf") << QLatin1String("psd") << QLatin1String("psb") 0520 << QLatin1String("kra") << QLatin1String("ora") << QLatin1String("wmf"); 0521 0522 // Raw images: https://en.wikipedia.org/wiki/Raw_image_format 0523 0524 defaultItemFilter << DRawDecoder::rawFilesList(); 0525 0526 // Video files: https://en.wikipedia.org/wiki/Video_file_format 0527 0528 defaultVideoFilter << QLatin1String("mpeg") << QLatin1String("mpg") << QLatin1String("mpe") // MPEG 0529 << QLatin1String("mts") << QLatin1String("vob") 0530 << QLatin1String("avi") << QLatin1String("divx") // RIFF 0531 << QLatin1String("wmv") << QLatin1String("asf") // ASF 0532 << QLatin1String("mp4") << QLatin1String("3gp") << QLatin1String("mov") // QuickTime 0533 << QLatin1String("3g2") << QLatin1String("m4v") << QLatin1String("m2v") 0534 << QLatin1String("mkv") << QLatin1String("webm") // Matroska 0535 << QLatin1String("mng") // Animated PNG image 0536 << QLatin1String("m2ts"); // Blu-ray 0537 0538 // Audio files: https://en.wikipedia.org/wiki/Audio_file_format 0539 0540 defaultAudioFilter << QLatin1String("ogg") << QLatin1String("oga") << QLatin1String("flac") // Linux audio 0541 << QLatin1String("wv") << QLatin1String("ape") << QLatin1String("mpc") 0542 << QLatin1String("au") 0543 << QLatin1String("m4b") << QLatin1String("aax") << QLatin1String("aa") // Book audio 0544 << QLatin1String("mp3") << QLatin1String("aac") // MPEG based audio 0545 << QLatin1String("m4a") << QLatin1String("m4p") << QLatin1String("caf") // Apple audio 0546 << QLatin1String("aiff") 0547 << QLatin1String("wma") << QLatin1String("wav"); // Windows audio 0548 } 0549 0550 void CoreDbSchemaUpdater::defaultIgnoreDirectoryFilterSettings(QStringList& defaultIgnoreDirectoryFilter) 0551 { 0552 // NOTE: when update this section, 0553 // just increment filterSettingsVersion() so that the changes take effect 0554 0555 defaultIgnoreDirectoryFilter << QLatin1String("@eaDir"); 0556 } 0557 0558 bool CoreDbSchemaUpdater::createFilterSettings() 0559 { 0560 QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter, defaultIgnoreDirectoryFilter; 0561 defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); 0562 defaultIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); 0563 0564 d->albumDB->setFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); 0565 d->albumDB->setIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); 0566 d->albumDB->setSetting(QLatin1String("FilterSettingsVersion"), QString::number(filterSettingsVersion())); 0567 d->albumDB->setSetting(QLatin1String("DcrawFilterSettingsVersion"), QString::number(DRawDecoder::rawFilesVersion())); 0568 0569 return true; 0570 } 0571 0572 bool CoreDbSchemaUpdater::updateFilterSettings() 0573 { 0574 QString filterVersion = d->albumDB->getSetting(QLatin1String("FilterSettingsVersion")); 0575 QString dcrawFilterVersion = d->albumDB->getSetting(QLatin1String("DcrawFilterSettingsVersion")); 0576 0577 if (filterVersion.toInt() < filterSettingsVersion() || 0578 dcrawFilterVersion.toInt() < DRawDecoder::rawFilesVersion()) 0579 { 0580 createFilterSettings(); 0581 } 0582 0583 return true; 0584 } 0585 0586 bool CoreDbSchemaUpdater::createDatabase() 0587 { 0588 if (createTables() && createIndices() && createTriggers()) 0589 { 0590 setLegacySettingEntries(); 0591 0592 d->currentVersion = schemaVersion(); 0593 0594 // if we start with the V2 hash, version 6 is required 0595 d->albumDB->setUniqueHashVersion(uniqueHashVersion()); 0596 d->currentRequiredVersion = schemaVersion(); 0597 /* 0598 // digiKam for database version 5 can work with version 6, though not using the new features 0599 d->currentRequiredVersion = 5; 0600 */ 0601 return true; 0602 } 0603 else 0604 { 0605 return false; 0606 } 0607 } 0608 0609 bool CoreDbSchemaUpdater::createTables() 0610 { 0611 return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateDB"))); 0612 } 0613 0614 bool CoreDbSchemaUpdater::createIndices() 0615 { 0616 // TODO: see which more indices are needed 0617 // create indices 0618 0619 return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateIndices"))); 0620 } 0621 0622 bool CoreDbSchemaUpdater::createTriggers() 0623 { 0624 return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateTriggers"))); 0625 } 0626 0627 bool CoreDbSchemaUpdater::updateUniqueHash() 0628 { 0629 if (isUniqueHashUpToDate()) 0630 { 0631 return true; 0632 } 0633 0634 readVersionSettings(); 0635 0636 { 0637 CoreDbTransaction transaction; 0638 0639 CoreDbAccess().db()->setUniqueHashVersion(uniqueHashVersion()); 0640 0641 CollectionScanner scanner; 0642 scanner.setNeedFileCount(true); 0643 scanner.setUpdateHashHint(); 0644 0645 if (d->observer) 0646 { 0647 d->observer->connectCollectionScanner(&scanner); 0648 scanner.setObserver(d->observer); 0649 } 0650 0651 scanner.completeScan(); 0652 0653 // earlier digiKam does not know about the hash 0654 0655 if (d->currentRequiredVersion.toInt() < 6) 0656 { 0657 d->currentRequiredVersion = 6; 0658 setVersionSettings(); 0659 } 0660 } 0661 return true; 0662 } 0663 0664 bool CoreDbSchemaUpdater::performUpdateToVersion(const QString& actionName, int newVersion, int newRequiredVersion) 0665 { 0666 if (d->observer) 0667 { 0668 if (!d->observer->continueQuery()) 0669 { 0670 return false; 0671 } 0672 0673 d->observer->moreSchemaUpdateSteps(1); 0674 } 0675 0676 DbEngineAction updateAction = d->backend->getDBAction(actionName); 0677 0678 if (updateAction.name.isNull()) 0679 { 0680 QString errorMsg = i18n("The database update action cannot be found. Please ensure that " 0681 "the dbconfig.xml file of the current version of digiKam is installed " 0682 "at the correct place. "); 0683 } 0684 0685 if (!d->backend->execDBAction(updateAction)) 0686 { 0687 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update to V" << newVersion << "failed!"; 0688 0689 // resort to default error message, set above 0690 0691 return false; 0692 } 0693 0694 if (d->observer) 0695 { 0696 if (!d->observer->continueQuery()) 0697 { 0698 return false; 0699 } 0700 0701 d->observer->schemaUpdateProgress(i18n("Updated schema to version %1.", newVersion)); 0702 } 0703 0704 d->currentVersion = newVersion; 0705 0706 // digiKam for database version 5 can work with version 6, though not using the new features 0707 // Note: We do not upgrade the uniqueHash 0708 0709 d->currentRequiredVersion = newRequiredVersion; 0710 0711 return true; 0712 } 0713 0714 bool CoreDbSchemaUpdater::updateToVersion(int targetVersion) 0715 { 0716 if (d->currentVersion != targetVersion-1) 0717 { 0718 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: updateToVersion performs only incremental updates. Called to update from" 0719 << d->currentVersion << "to" << targetVersion << ", aborting."; 0720 return false; 0721 } 0722 0723 switch (targetVersion) 0724 { 0725 case 6: 0726 { 0727 // digiKam for database version 5 can work with version 6, 0728 // though not using the new features. 0729 // Note: We do not upgrade the uniqueHash. 0730 0731 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV5ToV6"), 6, 5); 0732 } 0733 0734 case 7: 0735 { 0736 // digiKam for database version 5 and 6 can work with version 7, 0737 // though not using the support for video files. 0738 0739 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV6ToV7"), 7, 5); 0740 0741 // NOTE: If you add a new update step, please check the 0742 // d->currentVersion at the bottom of updateV4toV7. 0743 // If the update already comes with createTables, 0744 // createTriggers, we don't need the extra update here. 0745 } 0746 0747 case 8: 0748 { 0749 // digiKam for database version 7 can work with version 8, 0750 // now using COLLATE utf8_general_ci for MySQL. 0751 0752 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 8, 5); 0753 } 0754 0755 case 9: 0756 { 0757 // digiKam for database version 8 can work with version 9, 0758 // now using COLLATE utf8_general_ci for MySQL. 0759 0760 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 9, 5); 0761 } 0762 0763 case 10: 0764 { 0765 // digiKam for database version 9 can work with version 10, 0766 // remove ImageHaarMatrix table and add manualOrder column. 0767 0768 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV9ToV10"), 10, 5); 0769 } 0770 0771 case 11: 0772 { 0773 // digiKam for database version 10 can work with version 11, 0774 // add TagsTree table for MySQL. 0775 0776 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV10ToV11"), 11, 5); 0777 } 0778 0779 case 12: 0780 { 0781 // digiKam for database version 11 can work with version 12, 0782 // fix TagsTree triggers for MySQL. 0783 0784 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV11ToV12"), 12, 5); 0785 } 0786 0787 case 13: 0788 { 0789 // digiKam for database version 12 can work with version 13, 0790 // add index to the TagsTree table for MySQL. 0791 0792 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV12ToV13"), 13, 5); 0793 } 0794 0795 case 14: 0796 { 0797 // digiKam for database version 13 can work with version 14, 0798 // add modificationDate column to the Albums table. 0799 0800 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV13ToV14"), 14, 5); 0801 } 0802 0803 case 15: 0804 { 0805 // digiKam for database version 14 can work with version 15, 0806 // now using COLLATE utf8_bin for Albums:relativePath and Images:name in MySQL. 0807 0808 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV14ToV15"), 15, 5); 0809 } 0810 0811 case 16: 0812 { 0813 // digiKam for database version 15 can work with version 16, 0814 // add caseSensitivity column to the AlbumRoots table. 0815 0816 return performUpdateToVersion(QLatin1String("UpdateSchemaFromV15ToV16"), 16, 5); 0817 } 0818 0819 default: 0820 { 0821 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: unsupported update to version" << targetVersion; 0822 return false; 0823 } 0824 } 0825 } 0826 0827 bool CoreDbSchemaUpdater::copyV3toV4(const QString& digikam3DBPath, const QString& currentDBPath) 0828 { 0829 if (d->observer) 0830 { 0831 d->observer->moreSchemaUpdateSteps(2); 0832 } 0833 0834 d->backend->close(); 0835 0836 // We cannot use KIO here because KIO only works from the main thread 0837 0838 QFile oldFile(digikam3DBPath); 0839 QFile newFile(currentDBPath); 0840 0841 // QFile won't override. Remove the empty db file created when a non-existent file is opened 0842 0843 newFile.remove(); 0844 0845 if (!oldFile.copy(currentDBPath)) 0846 { 0847 QString errorMsg = i18n("Failed to copy the old database file (\"%1\") " 0848 "to its new location (\"%2\"). " 0849 "Error message: \"%3\". " 0850 "Please make sure that the file can be copied, " 0851 "or delete it.", 0852 digikam3DBPath, 0853 currentDBPath, 0854 oldFile.errorString()); 0855 d->lastErrorMessage = errorMsg; 0856 d->setError = true; 0857 0858 if (d->observer) 0859 { 0860 d->observer->error(errorMsg); 0861 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 0862 } 0863 0864 return false; 0865 } 0866 0867 if (d->observer) 0868 { 0869 d->observer->schemaUpdateProgress(i18n("Copied database file")); 0870 } 0871 0872 if (!d->backend->open(d->parameters)) 0873 { 0874 QString errorMsg = i18n("The old database file (\"%1\") has been copied " 0875 "to the new location (\"%2\") but it cannot be opened. " 0876 "Please delete both files and try again, " 0877 "starting with an empty database. ", 0878 digikam3DBPath, 0879 currentDBPath); 0880 0881 d->lastErrorMessage = errorMsg; 0882 d->setError = true; 0883 0884 if (d->observer) 0885 { 0886 d->observer->error(errorMsg); 0887 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 0888 } 0889 0890 return false; 0891 } 0892 0893 if (d->observer) 0894 { 0895 d->observer->schemaUpdateProgress(i18n("Opened new database file")); 0896 } 0897 0898 d->currentVersion = 4; 0899 0900 return true; 0901 } 0902 0903 static QStringList cleanUserFilterString(const QString& filterString) 0904 { 0905 // splits by either ; or space, removes "*.", trims 0906 0907 QStringList filterList; 0908 0909 QString wildcard(QLatin1String("*.")); 0910 QChar dot(QLatin1Char('.')); 0911 Q_UNUSED(dot); 0912 0913 QChar sep(QLatin1Char(';')); 0914 int i = filterString.indexOf( sep ); 0915 0916 if ( i == -1 && filterString.indexOf(QLatin1Char(' ')) != -1 ) 0917 { 0918 sep = QChar(QLatin1Char(' ')); 0919 } 0920 0921 QStringList sepList = filterString.split(sep, QT_SKIP_EMPTY_PARTS); 0922 0923 Q_FOREACH (const QString& f, sepList) 0924 { 0925 if (f.startsWith(wildcard)) 0926 { 0927 filterList << f.mid(2).trimmed().toLower(); 0928 } 0929 else 0930 { 0931 filterList << f.trimmed().toLower(); 0932 } 0933 } 0934 0935 return filterList; 0936 } 0937 0938 bool CoreDbSchemaUpdater::updateV4toV7() 0939 { 0940 qCDebug(DIGIKAM_COREDB_LOG) << "Core database : running updateV4toV7"; 0941 0942 if (d->observer) 0943 { 0944 if (!d->observer->continueQuery()) 0945 { 0946 return false; 0947 } 0948 0949 d->observer->moreSchemaUpdateSteps(11); 0950 } 0951 0952 // This update was introduced from digikam version 0.9 to digikam 0.10 0953 // We operator on an SQLite3 database under a transaction (which will be rolled back on error) 0954 0955 // --- Make space for new tables --- 0956 0957 if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Albums RENAME TO AlbumsV3;"))) 0958 { 0959 return false; 0960 } 0961 0962 if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Images RENAME TO ImagesV3;"))) 0963 { 0964 return false; 0965 } 0966 0967 if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) 0968 { 0969 return false; 0970 } 0971 0972 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: moved tables"; 0973 0974 // --- Drop some triggers and indices --- 0975 0976 // Don't check for errors here. The "IF EXISTS" clauses seem not supported in SQLite 0977 0978 d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_album;")); 0979 d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); 0980 d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tag;")); 0981 d->backend->execSql(QString::fromUtf8("DROP TRIGGER insert_tagstree;")); 0982 d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tagstree;")); 0983 d->backend->execSql(QString::fromUtf8("DROP TRIGGER move_tagstree;")); 0984 d->backend->execSql(QString::fromUtf8("DROP INDEX dir_index;")); 0985 d->backend->execSql(QString::fromUtf8("DROP INDEX tag_index;")); 0986 0987 if (d->observer) 0988 { 0989 if (!d->observer->continueQuery()) 0990 { 0991 return false; 0992 } 0993 0994 d->observer->schemaUpdateProgress(i18n("Prepared table creation")); 0995 } 0996 0997 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: dropped triggers"; 0998 0999 // --- Create new tables --- 1000 1001 if (!createTables() || !createIndices()) 1002 { 1003 return false; 1004 } 1005 1006 if (d->observer) 1007 { 1008 if (!d->observer->continueQuery()) 1009 { 1010 return false; 1011 } 1012 1013 d->observer->schemaUpdateProgress(i18n("Created tables")); 1014 } 1015 1016 // --- Populate AlbumRoots (from config) --- 1017 1018 KSharedConfigPtr config = KSharedConfig::openConfig(); 1019 KConfigGroup group = config->group(QLatin1String("Album Settings")); 1020 QString albumLibraryPath = group.readEntry(QLatin1String("Album Path"), QString()); 1021 1022 if (albumLibraryPath.isEmpty()) 1023 { 1024 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: Album library path from config file is empty. Aborting update."; 1025 1026 QString errorMsg = i18n("No album library path has been found in the configuration file. " 1027 "Giving up the schema updating process. " 1028 "Please try with an empty database, or repair your configuration."); 1029 d->lastErrorMessage = errorMsg; 1030 d->setError = true; 1031 1032 if (d->observer) 1033 { 1034 d->observer->error(errorMsg); 1035 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 1036 } 1037 1038 return false; 1039 } 1040 1041 QUrl albumLibrary(QUrl::fromLocalFile(albumLibraryPath)); 1042 CollectionLocation location = CollectionManager::instance()->addLocation(albumLibrary, albumLibrary.fileName()); 1043 1044 if (location.isNull()) 1045 { 1046 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: failure to create a collection location. Aborting update."; 1047 1048 QString errorMsg = i18n("There was an error associating your albumLibraryPath (\"%1\") " 1049 "with a storage volume of your system. " 1050 "This problem may indicate that there is a problem with your installation. " 1051 "If you are working on Linux, check that HAL is installed and running. " 1052 "In any case, you can seek advice from the digikam developers mailing list (see www.digikam.org/support). " 1053 "The database updating process will now be aborted because we do not want " 1054 "to create a new database based on false assumptions from a broken installation.", 1055 albumLibraryPath); 1056 d->lastErrorMessage = errorMsg; 1057 d->setError = true; 1058 1059 if (d->observer) 1060 { 1061 d->observer->error(errorMsg); 1062 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); 1063 } 1064 1065 return false; 1066 } 1067 1068 if (d->observer) 1069 { 1070 if (!d->observer->continueQuery()) 1071 { 1072 return false; 1073 } 1074 1075 d->observer->schemaUpdateProgress(i18n("Configured one album root")); 1076 } 1077 1078 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: inserted album root"; 1079 1080 // --- With the album root, populate albums --- 1081 1082 if (!d->backend->execSql(QString::fromUtf8( 1083 "REPLACE INTO Albums " 1084 " (id, albumRoot, relativePath, date, caption, collection, icon) " 1085 "SELECT id, ?, url, date, caption, collection, icon " 1086 " FROM AlbumsV3;" 1087 ), 1088 location.id()) 1089 ) 1090 { 1091 return false; 1092 } 1093 1094 if (d->observer) 1095 { 1096 if (!d->observer->continueQuery()) 1097 { 1098 return false; 1099 } 1100 1101 d->observer->schemaUpdateProgress(i18n("Imported albums")); 1102 } 1103 1104 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated albums"; 1105 1106 // --- Add images --- 1107 1108 if (!d->backend->execSql(QString::fromUtf8( 1109 "REPLACE INTO Images " 1110 " (id, album, name, status, category, modificationDate, fileSize, uniqueHash) " 1111 "SELECT id, dirid, name, ?, ?, NULL, NULL, NULL" 1112 " FROM ImagesV3;" 1113 ), 1114 DatabaseItem::Visible, DatabaseItem::UndefinedCategory) 1115 ) 1116 { 1117 return false; 1118 } 1119 1120 if (!d->dbAccess->backend()->execSql(QString::fromUtf8( 1121 "REPLACE INTO ImageInformation (imageId) SELECT id FROM Images;")) 1122 ) 1123 { 1124 return false; 1125 } 1126 1127 // remove orphan images that would not be removed by CollectionScanner 1128 1129 d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE album NOT IN (SELECT id FROM Albums);")); 1130 1131 if (d->observer) 1132 { 1133 if (!d->observer->continueQuery()) 1134 { 1135 return false; 1136 } 1137 1138 d->observer->schemaUpdateProgress(i18n("Imported images information")); 1139 } 1140 1141 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated Images"; 1142 1143 // --- Port searches --- 1144 1145 if (!d->backend->execSql(QString::fromUtf8( 1146 "REPLACE INTO Searches " 1147 " (id, type, name, query) " 1148 "SELECT id, ?, name, url" 1149 " FROM SearchesV3;"), 1150 DatabaseSearch::LegacyUrlSearch) 1151 ) 1152 { 1153 return false; 1154 } 1155 1156 SearchInfo::List sList = d->albumDB->scanSearches(); 1157 1158 for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) 1159 { 1160 QUrl url((*it).query); 1161 1162 ItemQueryBuilder builder; 1163 QString query = builder.convertFromUrlToXml(url); 1164 QString name = (*it).name; 1165 1166 if (name == i18n("Last Search")) 1167 { 1168 name = i18n("Last Search (0.9)"); 1169 } 1170 1171 if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) 1172 { 1173 d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, name, query); 1174 } 1175 else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) 1176 { 1177 d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, name, query); 1178 } 1179 else 1180 { 1181 d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, name, query); 1182 } 1183 } 1184 1185 // --- Create triggers --- 1186 1187 if (!createTriggers()) 1188 { 1189 return false; 1190 } 1191 1192 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: created triggers"; 1193 1194 // --- Populate name filters --- 1195 1196 createFilterSettings(); 1197 1198 // --- Set user settings from config --- 1199 1200 QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter; 1201 defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); 1202 1203 auto listFileFilter = cleanUserFilterString(group.readEntry(QLatin1String("File Filter"), QString())); 1204 auto listRawFileFilter = cleanUserFilterString(group.readEntry(QLatin1String("Raw File Filter"), QString())); 1205 auto listMovieFilter = cleanUserFilterString(group.readEntry(QLatin1String("Movie File Filter"), QString())); 1206 auto listAudioFilter = cleanUserFilterString(group.readEntry(QLatin1String("Audio File Filter"), QString())); 1207 1208 QSet<QString> configItemFilter(listFileFilter.begin(), listFileFilter.end()); 1209 configItemFilter += QSet<QString>(listRawFileFilter.begin(), listRawFileFilter.end()); 1210 QSet<QString> configVideoFilter(listMovieFilter.begin(), listMovieFilter.end()); 1211 QSet<QString> configAudioFilter(listAudioFilter.begin(), listAudioFilter.end()); 1212 1213 // remove those that are included in the default filter 1214 1215 configItemFilter.subtract(QSet<QString>(defaultItemFilter.begin(), defaultItemFilter.end())); 1216 configVideoFilter.subtract(QSet<QString>(defaultVideoFilter.begin(), defaultVideoFilter.end())); 1217 configAudioFilter.subtract(QSet<QString>(defaultAudioFilter.begin(), defaultAudioFilter.end())); 1218 1219 d->albumDB->setUserFilterSettings(configItemFilter.values(), configVideoFilter.values(), configAudioFilter.values()); 1220 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: set initial filter settings with user settings" << configItemFilter; 1221 1222 if (d->observer) 1223 { 1224 if (!d->observer->continueQuery()) 1225 { 1226 return false; 1227 } 1228 1229 d->observer->schemaUpdateProgress(i18n("Initialized and imported file suffix filter")); 1230 } 1231 1232 // --- do a full scan --- 1233 1234 CollectionScanner scanner; 1235 1236 if (d->observer) 1237 { 1238 d->observer->connectCollectionScanner(&scanner); 1239 scanner.setObserver(d->observer); 1240 } 1241 1242 scanner.completeScan(); 1243 1244 if (d->observer) 1245 { 1246 if (!d->observer->continueQuery()) 1247 { 1248 return false; 1249 } 1250 1251 d->observer->schemaUpdateProgress(i18n("Did the initial full scan")); 1252 } 1253 1254 // --- Port date, comment and rating (_after_ the scan) --- 1255 1256 // Port ImagesV3.date -> ImageInformation.creationDate 1257 1258 if (!d->backend->execSql(QString::fromUtf8( 1259 "UPDATE ImageInformation SET " 1260 " creationDate=(SELECT datetime FROM ImagesV3 WHERE ImagesV3.id=ImageInformation.imageid) " 1261 "WHERE imageid IN (SELECT id FROM ImagesV3);") 1262 ) 1263 ) 1264 { 1265 return false; 1266 } 1267 1268 if (d->observer) 1269 { 1270 if (!d->observer->continueQuery()) 1271 { 1272 return false; 1273 } 1274 1275 d->observer->schemaUpdateProgress(i18n("Imported creation dates")); 1276 } 1277 1278 // Port ImagesV3.comment to ItemComments 1279 1280 // An author of NULL will inhibt the UNIQUE restriction to take effect (but #189080). Work around. 1281 1282 d->backend->execSql(QString::fromUtf8( 1283 "DELETE FROM ImageComments WHERE " 1284 "type=? AND language=? AND author IS NULL " 1285 "AND imageid IN ( SELECT id FROM ImagesV3 ); "), 1286 (int)DatabaseComment::Comment, QLatin1String("x-default")); 1287 1288 if (!d->backend->execSql(QString::fromUtf8( 1289 "REPLACE INTO ImageComments " 1290 " (imageid, type, language, comment) " 1291 "SELECT id, ?, ?, caption FROM ImagesV3;" 1292 ), 1293 (int)DatabaseComment::Comment, QLatin1String("x-default")) 1294 ) 1295 { 1296 return false; 1297 } 1298 1299 if (d->observer) 1300 { 1301 if (!d->observer->continueQuery()) 1302 { 1303 return false; 1304 } 1305 1306 d->observer->schemaUpdateProgress(i18n("Imported comments")); 1307 } 1308 1309 // Port rating storage in ImageProperties to ItemInformation 1310 1311 if (!d->backend->execSql(QString::fromUtf8( 1312 "UPDATE ImageInformation SET " 1313 " rating=(SELECT value FROM ImageProperties " 1314 " WHERE ImageInformation.imageid=ImageProperties.imageid AND ImageProperties.property=?) " 1315 "WHERE imageid IN (SELECT imageid FROM ImageProperties WHERE property=?);" 1316 ), 1317 QString::fromUtf8("Rating"), QString::fromUtf8("Rating")) 1318 ) 1319 { 1320 return false; 1321 } 1322 1323 d->backend->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), QString::fromUtf8("Rating")); 1324 d->backend->execSql(QString::fromUtf8("UPDATE ImageInformation SET rating=0 WHERE rating<0;")); 1325 1326 if (d->observer) 1327 { 1328 if (!d->observer->continueQuery()) 1329 { 1330 return false; 1331 } 1332 1333 d->observer->schemaUpdateProgress(i18n("Imported ratings")); 1334 } 1335 1336 // --- Drop old tables --- 1337 1338 d->backend->execSql(QString::fromUtf8("DROP TABLE ImagesV3;")); 1339 d->backend->execSql(QString::fromUtf8("DROP TABLE AlbumsV3;")); 1340 d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); 1341 1342 if (d->observer) 1343 { 1344 d->observer->schemaUpdateProgress(i18n("Dropped v3 tables")); 1345 } 1346 1347 d->currentRequiredVersion = 5; 1348 d->currentVersion = 7; 1349 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: returning true from updating to 5"; 1350 1351 return true; 1352 } 1353 1354 void CoreDbSchemaUpdater::setLegacySettingEntries() 1355 { 1356 d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); 1357 d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); 1358 d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); 1359 d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); 1360 d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); 1361 } 1362 1363 // ---------- Legacy code ------------ 1364 1365 void CoreDbSchemaUpdater::preAlpha010Update1() 1366 { 1367 QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update1")); 1368 1369 if (!hasUpdate.isNull()) 1370 { 1371 return; 1372 } 1373 1374 if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) 1375 { 1376 return; 1377 } 1378 1379 if ( !d->backend->execSql( 1380 QString::fromUtf8( "CREATE TABLE IF NOT EXISTS Searches \n" 1381 " (id INTEGER PRIMARY KEY, \n" 1382 " type INTEGER, \n" 1383 " name TEXT NOT NULL, \n" 1384 " query TEXT NOT NULL);" ) )) 1385 { 1386 return; 1387 } 1388 1389 if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " 1390 " (id, type, name, query) " 1391 "SELECT id, ?, name, url" 1392 " FROM SearchesV3;"), 1393 DatabaseSearch::LegacyUrlSearch) 1394 ) 1395 { 1396 return; 1397 } 1398 1399 SearchInfo::List sList = d->albumDB->scanSearches(); 1400 1401 for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) 1402 { 1403 QUrl url((*it).query); 1404 1405 ItemQueryBuilder builder; 1406 QString query = builder.convertFromUrlToXml(url); 1407 1408 if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) 1409 { 1410 d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, (*it).name, query); 1411 } 1412 else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) 1413 { 1414 d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, (*it).name, query); 1415 } 1416 else 1417 { 1418 d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, (*it).name, query); 1419 } 1420 } 1421 1422 d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); 1423 1424 d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); 1425 } 1426 1427 void CoreDbSchemaUpdater::preAlpha010Update2() 1428 { 1429 QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update2")); 1430 1431 if (!hasUpdate.isNull()) 1432 { 1433 return; 1434 } 1435 1436 if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImagePositions RENAME TO ItemPositionsTemp;"))) 1437 { 1438 return; 1439 } 1440 1441 if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImageMetadata RENAME TO ImageMetadataTemp;"))) 1442 { 1443 return; 1444 } 1445 1446 d->backend->execSql( 1447 QString::fromUtf8("CREATE TABLE ItemPositions\n" 1448 " (imageid INTEGER PRIMARY KEY,\n" 1449 " latitude TEXT,\n" 1450 " latitudeNumber REAL,\n" 1451 " longitude TEXT,\n" 1452 " longitudeNumber REAL,\n" 1453 " altitude REAL,\n" 1454 " orientation REAL,\n" 1455 " tilt REAL,\n" 1456 " roll REAL,\n" 1457 " accuracy REAL,\n" 1458 " description TEXT);") ); 1459 1460 d->backend->execSql(QString::fromUtf8("REPLACE INTO ImagePositions " 1461 " (imageid, latitude, latitudeNumber, longitude, longitudeNumber, " 1462 " altitude, orientation, tilt, roll, accuracy, description) " 1463 "SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, " 1464 " altitude, orientation, tilt, roll, 0, description " 1465 " FROM ItemPositionsTemp;")); 1466 1467 d->backend->execSql( 1468 QString::fromUtf8("CREATE TABLE ImageMetadata\n" 1469 " (imageid INTEGER PRIMARY KEY,\n" 1470 " make TEXT,\n" 1471 " model TEXT,\n" 1472 " lens TEXT,\n" 1473 " aperture REAL,\n" 1474 " focalLength REAL,\n" 1475 " focalLength35 REAL,\n" 1476 " exposureTime REAL,\n" 1477 " exposureProgram INTEGER,\n" 1478 " exposureMode INTEGER,\n" 1479 " sensitivity INTEGER,\n" 1480 " flash INTEGER,\n" 1481 " whiteBalance INTEGER,\n" 1482 " whiteBalanceColorTemperature INTEGER,\n" 1483 " meteringMode INTEGER,\n" 1484 " subjectDistance REAL,\n" 1485 " subjectDistanceCategory INTEGER);") ); 1486 1487 d->backend->execSql( QString::fromUtf8("INSERT INTO ImageMetadata " 1488 " (imageid, make, model, lens, aperture, focalLength, focalLength35, " 1489 " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " 1490 " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) " 1491 "SELECT imageid, make, model, NULL, aperture, focalLength, focalLength35, " 1492 " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " 1493 " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory " 1494 "FROM ImageMetadataTemp;")); 1495 1496 d->backend->execSql(QString::fromUtf8("DROP TABLE ItemPositionsTemp;")); 1497 d->backend->execSql(QString::fromUtf8("DROP TABLE ImageMetadataTemp;")); 1498 1499 d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); 1500 } 1501 1502 void CoreDbSchemaUpdater::preAlpha010Update3() 1503 { 1504 QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update3")); 1505 1506 if (!hasUpdate.isNull()) 1507 { 1508 return; 1509 } 1510 1511 d->backend->execSql(QString::fromUtf8("DROP TABLE ItemCopyright;")); 1512 d->backend->execSql(QString::fromUtf8("CREATE TABLE ItemCopyright\n" 1513 " (imageid INTEGER,\n" 1514 " property TEXT,\n" 1515 " value TEXT,\n" 1516 " extraValue TEXT,\n" 1517 " UNIQUE(imageid, property, value, extraValue));") 1518 ); 1519 1520 d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); 1521 } 1522 1523 void CoreDbSchemaUpdater::beta010Update1() 1524 { 1525 QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update1")); 1526 1527 if (!hasUpdate.isNull()) 1528 { 1529 return; 1530 } 1531 1532 // if Image has been deleted 1533 1534 d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); 1535 d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" 1536 "BEGIN\n" 1537 " DELETE FROM ImageTags\n" 1538 " WHERE imageid=OLD.id;\n" 1539 " DELETE From ImageHaarMatrix\n " 1540 " WHERE imageid=OLD.id;\n" 1541 " DELETE From ItemInformation\n " 1542 " WHERE imageid=OLD.id;\n" 1543 " DELETE From ImageMetadata\n " 1544 " WHERE imageid=OLD.id;\n" 1545 " DELETE From ItemPositions\n " 1546 " WHERE imageid=OLD.id;\n" 1547 " DELETE From ItemComments\n " 1548 " WHERE imageid=OLD.id;\n" 1549 " DELETE From ItemCopyright\n " 1550 " WHERE imageid=OLD.id;\n" 1551 " DELETE From ImageProperties\n " 1552 " WHERE imageid=OLD.id;\n" 1553 " UPDATE Albums SET icon=null \n " 1554 " WHERE icon=OLD.id;\n" 1555 " UPDATE Tags SET icon=null \n " 1556 " WHERE icon=OLD.id;\n" 1557 "END;")); 1558 1559 d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); 1560 } 1561 1562 void CoreDbSchemaUpdater::beta010Update2() 1563 { 1564 QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update2")); 1565 1566 if (!hasUpdate.isNull()) 1567 { 1568 return; 1569 } 1570 1571 // force rescan and creation of ImageInformation entry for videos and audio 1572 1573 d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE category=2 OR category=3;")); 1574 1575 d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); 1576 } 1577 1578 bool CoreDbSchemaUpdater::createTablesV3() 1579 { 1580 if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Albums\n" 1581 " (id INTEGER PRIMARY KEY,\n" 1582 " url TEXT NOT NULL UNIQUE,\n" 1583 " date DATE NOT NULL,\n" 1584 " caption TEXT,\n" 1585 " collection TEXT,\n" 1586 " icon INTEGER);") )) 1587 { 1588 return false; 1589 } 1590 1591 if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Tags\n" 1592 " (id INTEGER PRIMARY KEY,\n" 1593 " pid INTEGER,\n" 1594 " name TEXT NOT NULL,\n" 1595 " icon INTEGER,\n" 1596 " iconkde TEXT,\n" 1597 " UNIQUE (name, pid));") )) 1598 { 1599 return false; 1600 } 1601 1602 if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE TagsTree\n" 1603 " (id INTEGER NOT NULL,\n" 1604 " pid INTEGER NOT NULL,\n" 1605 " UNIQUE (id, pid));") )) 1606 { 1607 return false; 1608 } 1609 1610 if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Images\n" 1611 " (id INTEGER PRIMARY KEY,\n" 1612 " name TEXT NOT NULL,\n" 1613 " dirid INTEGER NOT NULL,\n" 1614 " caption TEXT,\n" 1615 " datetime DATETIME,\n" 1616 " UNIQUE (name, dirid));") )) 1617 { 1618 return false; 1619 } 1620 1621 1622 if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageTags\n" 1623 " (imageid INTEGER NOT NULL,\n" 1624 " tagid INTEGER NOT NULL,\n" 1625 " UNIQUE (imageid, tagid));") )) 1626 { 1627 return false; 1628 } 1629 1630 if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageProperties\n" 1631 " (imageid INTEGER NOT NULL,\n" 1632 " property TEXT NOT NULL,\n" 1633 " value TEXT NOT NULL,\n" 1634 " UNIQUE (imageid, property));") )) 1635 { 1636 return false; 1637 } 1638 1639 if ( !d->backend->execSql( QString::fromUtf8("CREATE TABLE Searches \n" 1640 " (id INTEGER PRIMARY KEY, \n" 1641 " name TEXT NOT NULL UNIQUE, \n" 1642 " url TEXT NOT NULL);") ) ) 1643 { 1644 return false; 1645 } 1646 1647 if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Settings \n" 1648 "(keyword TEXT NOT NULL UNIQUE,\n" 1649 " value TEXT);") )) 1650 { 1651 return false; 1652 } 1653 1654 // TODO: see which more indices are needed 1655 // create indices 1656 1657 d->backend->execSql(QString::fromUtf8("CREATE INDEX dir_index ON Images (dirid);")); 1658 d->backend->execSql(QString::fromUtf8("CREATE INDEX tag_index ON ImageTags (tagid);")); 1659 1660 // create triggers 1661 1662 // trigger: delete from Images/ImageTags/ImageProperties 1663 // if Album has been deleted 1664 1665 d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_album DELETE ON Albums\n" 1666 "BEGIN\n" 1667 " DELETE FROM ImageTags\n" 1668 " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" 1669 " DELETE From ImageProperties\n" 1670 " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" 1671 " DELETE FROM Images\n" 1672 " WHERE dirid = OLD.id;\n" 1673 "END;")); 1674 1675 // trigger: delete from ImageTags/ImageProperties 1676 // if Image has been deleted 1677 1678 d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" 1679 "BEGIN\n" 1680 " DELETE FROM ImageTags\n" 1681 " WHERE imageid=OLD.id;\n" 1682 " DELETE From ImageProperties\n " 1683 " WHERE imageid=OLD.id;\n" 1684 " UPDATE Albums SET icon=null \n " 1685 " WHERE icon=OLD.id;\n" 1686 " UPDATE Tags SET icon=null \n " 1687 " WHERE icon=OLD.id;\n" 1688 "END;")); 1689 1690 // trigger: delete from ImageTags if Tag has been deleted 1691 1692 d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tag DELETE ON Tags\n" 1693 "BEGIN\n" 1694 " DELETE FROM ImageTags WHERE tagid=OLD.id;\n" 1695 "END;")); 1696 1697 // trigger: insert into TagsTree if Tag has been added 1698 1699 d->backend->execSql(QString::fromUtf8("CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags\n" 1700 "BEGIN\n" 1701 " INSERT INTO TagsTree\n" 1702 " SELECT NEW.id, NEW.pid\n" 1703 " UNION\n" 1704 " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid;\n" 1705 "END;")); 1706 1707 // trigger: delete from TagsTree if Tag has been deleted 1708 1709 d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tagstree DELETE ON Tags\n" 1710 "BEGIN\n" 1711 " DELETE FROM Tags\n" 1712 " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" 1713 " DELETE FROM TagsTree\n" 1714 " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" 1715 " DELETE FROM TagsTree\n" 1716 " WHERE id=OLD.id;\n" 1717 "END;")); 1718 1719 // trigger: delete from TagsTree if Tag has been deleted 1720 1721 d->backend->execSql(QString::fromUtf8("CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags\n" 1722 "BEGIN\n" 1723 " DELETE FROM TagsTree\n" 1724 " WHERE\n" 1725 " ((id = OLD.id)\n" 1726 " OR\n" 1727 " id IN (SELECT id FROM TagsTree WHERE pid=OLD.id))\n" 1728 " AND\n" 1729 " pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id);\n" 1730 " INSERT INTO TagsTree\n" 1731 " SELECT NEW.id, NEW.pid\n" 1732 " UNION\n" 1733 " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid\n" 1734 " UNION\n" 1735 " SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id\n" 1736 " UNION\n" 1737 " SELECT A.id, B.pid FROM TagsTree A, TagsTree B\n" 1738 " WHERE\n" 1739 " A.pid = NEW.id AND B.id = NEW.pid;\n" 1740 "END;")); 1741 1742 return true; 1743 } 1744 1745 } // namespace Digikam