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