"Fossies" - the Fresh Open Source Software Archive

Member "digikam-6.3.0/core/libs/database/coredb/coredbschemaupdater.cpp" (4 Sep 2019, 60990 Bytes) of package /linux/misc/digikam-6.3.0.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "coredbschemaupdater.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.2.0_vs_6.3.0.

    1 /* ============================================================
    2  *
    3  * This file is a part of digiKam project
    4  * https://www.digikam.org
    5  *
    6  * Date        : 2007-04-16
    7  * Description : Core database Schema updater
    8  *
    9  * Copyright (C) 2007-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
   10  * Copyright (C) 2009-2019 by Gilles Caulier <caulier dot gilles at gmail dot com>
   11  *
   12  * This program is free software; you can redistribute it
   13  * and/or modify it under the terms of the GNU General
   14  * Public License as published by the Free Software Foundation;
   15  * either version 2, or (at your option)
   16  * any later version.
   17  *
   18  * This program is distributed in the hope that it will be useful,
   19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   21  * GNU General Public License for more details.
   22  *
   23  * ============================================================ */
   24 
   25 #include "coredbschemaupdater.h"
   26 
   27 // Qt includes
   28 
   29 #include <QFileInfo>
   30 #include <QFile>
   31 #include <QDir>
   32 #include <QUrl>
   33 #include <QUrlQuery>
   34 
   35 // KDE includes
   36 
   37 #include <klocalizedstring.h>
   38 #include <kconfiggroup.h>
   39 #include <ksharedconfig.h>
   40 
   41 // Local includes
   42 
   43 #include "drawdecoder.h"
   44 #include "digikam_debug.h"
   45 #include "coredbbackend.h"
   46 #include "coredbtransaction.h"
   47 #include "coredbchecker.h"
   48 #include "collectionmanager.h"
   49 #include "collectionlocation.h"
   50 #include "collectionscanner.h"
   51 #include "itemquerybuilder.h"
   52 #include "collectionscannerobserver.h"
   53 #include "digikam_config.h"
   54 
   55 namespace Digikam
   56 {
   57 
   58 int CoreDbSchemaUpdater::schemaVersion()
   59 {
   60     return 10;
   61 }
   62 
   63 int CoreDbSchemaUpdater::filterSettingsVersion()
   64 {
   65     return 8;
   66 }
   67 
   68 int CoreDbSchemaUpdater::uniqueHashVersion()
   69 {
   70     return 2;
   71 }
   72 
   73 bool CoreDbSchemaUpdater::isUniqueHashUpToDate()
   74 {
   75     return CoreDbAccess().db()->getUniqueHashVersion() >= uniqueHashVersion();
   76 }
   77 
   78 // --------------------------------------------------------------------------------------
   79 
   80 class Q_DECL_HIDDEN CoreDbSchemaUpdater::Private
   81 {
   82 
   83 public:
   84 
   85     explicit Private()
   86       : setError(false),
   87         backend(nullptr),
   88         albumDB(nullptr),
   89         dbAccess(nullptr),
   90         observer(nullptr)
   91     {
   92     }
   93 
   94     bool                    setError;
   95 
   96     QVariant                currentVersion;
   97     QVariant                currentRequiredVersion;
   98 
   99     CoreDbBackend*          backend;
  100     CoreDB*                 albumDB;
  101     DbEngineParameters      parameters;
  102 
  103     // legacy
  104     CoreDbAccess*           dbAccess;
  105 
  106     QString                 lastErrorMessage;
  107     InitializationObserver* observer;
  108 };
  109 
  110 CoreDbSchemaUpdater::CoreDbSchemaUpdater(CoreDB* const albumDB,
  111                                          CoreDbBackend* const backend,
  112                                          DbEngineParameters parameters)
  113     : d(new Private)
  114 {
  115     d->backend    = backend;
  116     d->albumDB    = albumDB;
  117     d->parameters = parameters;
  118 }
  119 
  120 CoreDbSchemaUpdater::~CoreDbSchemaUpdater()
  121 {
  122     delete d;
  123 }
  124 
  125 void CoreDbSchemaUpdater::setCoreDbAccess(CoreDbAccess* const dbAccess)
  126 {
  127     d->dbAccess = dbAccess;
  128 }
  129 
  130 const QString CoreDbSchemaUpdater::getLastErrorMessage()
  131 {
  132     return d->lastErrorMessage;
  133 }
  134 
  135 bool CoreDbSchemaUpdater::update()
  136 {
  137     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: running schema update";
  138     bool success = startUpdates();
  139 
  140     // cancelled?
  141     if (d->observer && !d->observer->continueQuery())
  142     {
  143         return false;
  144     }
  145 
  146     // even on failure, try to set current version - it may have incremented
  147     setVersionSettings();
  148 
  149     if (!success)
  150     {
  151         return false;
  152     }
  153 
  154     updateFilterSettings();
  155 
  156     if (d->observer)
  157     {
  158         d->observer->finishedSchemaUpdate(InitializationObserver::UpdateSuccess);
  159     }
  160 
  161     return success;
  162 }
  163 
  164 void CoreDbSchemaUpdater::setVersionSettings()
  165 {
  166     if (d->currentVersion.isValid())
  167     {
  168         d->albumDB->setSetting(QLatin1String("DBVersion"),
  169                                QString::number(d->currentVersion.toInt()));
  170     }
  171 
  172     if (d->currentRequiredVersion.isValid())
  173     {
  174         d->albumDB->setSetting(QLatin1String("DBVersionRequired"),
  175                                QString::number(d->currentRequiredVersion.toInt()));
  176     }
  177 }
  178 
  179 static QVariant safeToVariant(const QString& s)
  180 {
  181     if (s.isEmpty())
  182     {
  183         return QVariant();
  184     }
  185     else
  186     {
  187         return s.toInt();
  188     }
  189 }
  190 
  191 void CoreDbSchemaUpdater::readVersionSettings()
  192 {
  193     d->currentVersion         = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersion")));
  194     d->currentRequiredVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersionRequired")));
  195 }
  196 
  197 void CoreDbSchemaUpdater::setObserver(InitializationObserver* const observer)
  198 {
  199     d->observer = observer;
  200 }
  201 
  202 bool CoreDbSchemaUpdater::startUpdates()
  203 {
  204     if (!d->parameters.isSQLite())
  205     {
  206         // Do we have sufficient privileges
  207         QStringList insufficientRights;
  208         CoreDbPrivilegesChecker checker(d->parameters);
  209 
  210         if (!checker.checkPrivileges(insufficientRights))
  211         {
  212             qCDebug(DIGIKAM_COREDB_LOG) << "Core database: insufficient rights on database.";
  213 
  214             QString errorMsg = i18n("You have insufficient privileges on the database.\n"
  215                                     "Following privileges are not assigned to you:\n %1\n"
  216                                     "Check your privileges on the database and restart digiKam.",
  217                                     insufficientRights.join(QLatin1String(",\n")));
  218             d->lastErrorMessage = errorMsg;
  219 
  220             if (d->observer)
  221             {
  222                 d->observer->error(errorMsg);
  223                 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  224             }
  225 
  226             return false;
  227         }
  228     }
  229 
  230     // First step: do we have an empty database?
  231     QStringList tables = d->backend->tables();
  232 
  233     if (tables.contains(QLatin1String("Albums"), Qt::CaseInsensitive))
  234     {
  235         // Find out schema version of db file
  236         readVersionSettings();
  237         qCDebug(DIGIKAM_COREDB_LOG) << "Core database: have a structure version " << d->currentVersion.toInt();
  238 
  239         // We absolutely require the DBVersion setting
  240         if (!d->currentVersion.isValid())
  241         {
  242             // Something is damaged. Give up.
  243             qCDebug(DIGIKAM_COREDB_LOG) << "Core database: version not available! Giving up schema upgrading.";
  244 
  245             QString errorMsg = i18n("The database is not valid: "
  246                                     "the \"DBVersion\" setting does not exist. "
  247                                     "The current database schema version cannot be verified. "
  248                                     "Try to start with an empty database. ");
  249             d->lastErrorMessage=errorMsg;
  250 
  251             if (d->observer)
  252             {
  253                 d->observer->error(errorMsg);
  254                 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  255             }
  256 
  257             return false;
  258         }
  259 
  260         // current version describes the current state of the schema in the db,
  261         // schemaVersion is the version required by the program.
  262         if (d->currentVersion.toInt() > schemaVersion())
  263         {
  264             // trying to open a database with a more advanced than this CoreDbSchemaUpdater supports
  265             if (d->currentRequiredVersion.isValid() && d->currentRequiredVersion.toInt() <= schemaVersion())
  266             {
  267                 // version required may be less than current version
  268                 return true;
  269             }
  270             else
  271             {
  272                 QString errorMsg = i18n("The database has been used with a more recent version of digiKam "
  273                                         "and has been updated to a database schema which cannot be used with this version. "
  274                                         "(This means this digiKam version is too old, or the database format is too recent.) "
  275                                         "Please use the more recent version of digiKam that you used before. ");
  276                 d->lastErrorMessage=errorMsg;
  277 
  278                 if (d->observer)
  279                 {
  280                     d->observer->error(errorMsg);
  281                     d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  282                 }
  283 
  284                 return false;
  285             }
  286         }
  287         else
  288         {
  289             return makeUpdates();
  290         }
  291     }
  292     else
  293     {
  294         qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no database file available";
  295 
  296         // Legacy handling?
  297 
  298         // first test if there are older files that need to be upgraded.
  299         // This applies to "digikam.db" for 0.7 and "digikam3.db" for 0.8 and 0.9,
  300         // all only SQLite databases.
  301 
  302         // Version numbers used in this source file are a bit confused for the historic versions.
  303         // Version 1 is 0.6 (no db), Version 2 is 0.7 (SQLite 2),
  304         // Version 3 is 0.8-0.9,
  305         // Version 3 wrote the setting "DBVersion", "1",
  306         // Version 4 is 0.10, the digikam3.db file copied to digikam4.db,
  307         //  no schema changes.
  308         // Version 4 writes "4", and from now on version x writes "x".
  309         // Version 5 includes the schema changes from 0.9 to 0.10
  310         // Version 6 brought new tables for history and ImageTagProperties, with version 2.0
  311         // Version 7 brought the VideoMetadata table with 3.0
  312 
  313         if (d->parameters.isSQLite())
  314         {
  315             QFileInfo currentDBFile(d->parameters.databaseNameCore);
  316             QFileInfo digikam3DB(currentDBFile.dir(), QLatin1String("digikam3.db"));
  317 
  318             if (digikam3DB.exists())
  319             {
  320                 if (!copyV3toV4(digikam3DB.filePath(), currentDBFile.filePath()))
  321                 {
  322                     return false;
  323                 }
  324 
  325                 // d->currentVersion is now 4;
  326                 return makeUpdates();
  327             }
  328         }
  329 
  330         // No legacy handling: start with a fresh db
  331         if (!createDatabase() || !createFilterSettings())
  332         {
  333             QString errorMsg    = i18n("Failed to create tables in database.\n ") + d->backend->lastError();
  334             d->lastErrorMessage = errorMsg;
  335 
  336             if (d->observer)
  337             {
  338                 d->observer->error(errorMsg);
  339                 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  340             }
  341 
  342             return false;
  343         }
  344 
  345         return true;
  346     }
  347 }
  348 
  349 bool CoreDbSchemaUpdater::beginWrapSchemaUpdateStep()
  350 {
  351     if (!d->backend->beginTransaction())
  352     {
  353         QFileInfo currentDBFile(d->parameters.databaseNameCore);
  354         QString errorMsg = i18n("Failed to open a database transaction on your database file \"%1\". "
  355                                 "This is unusual. Please check that you can access the file and no "
  356                                 "other process has currently locked the file. "
  357                                 "If the problem persists you can get help from the digikam developers mailing list (see www.digikam.org/support). "
  358                                 "As well, please have a look at what digiKam prints on the console. ",
  359                                 QDir::toNativeSeparators(currentDBFile.filePath()));
  360         d->observer->error(errorMsg);
  361         d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  362         return false;
  363     }
  364 
  365     return true;
  366 }
  367 
  368 bool CoreDbSchemaUpdater::endWrapSchemaUpdateStep(bool stepOperationSuccess, const QString& errorMsg)
  369 {
  370     if (!stepOperationSuccess)
  371     {
  372         d->backend->rollbackTransaction();
  373 
  374         if (d->observer)
  375         {
  376             // error or cancelled?
  377             if (!d->observer->continueQuery())
  378             {
  379                 qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update cancelled by user";
  380             }
  381             else if (!d->setError)
  382             {
  383                 d->observer->error(errorMsg);
  384                 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  385             }
  386         }
  387 
  388         return false;
  389     }
  390 
  391     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt();
  392     d->backend->commitTransaction();
  393     return true;
  394 }
  395 
  396 bool CoreDbSchemaUpdater::makeUpdates()
  397 {
  398     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: makeUpdates " << d->currentVersion.toInt() << " to " << schemaVersion();
  399 
  400     if (d->currentVersion.toInt() < schemaVersion())
  401     {
  402         if (d->currentVersion.toInt() < 5)
  403         {
  404             if (!beginWrapSchemaUpdateStep())
  405             {
  406                 return false;
  407             }
  408 
  409             // v4 was always SQLite
  410             QFileInfo currentDBFile(d->parameters.databaseNameCore);
  411             QString errorMsg = i18n("The schema updating process from version 4 to 6 failed, "
  412                                     "caused by an error that we did not expect. "
  413                                     "You can try to discard your old database and start with an empty one. "
  414                                     "(In this case, please move the database files "
  415                                     "\"%1\" and \"%2\" from the directory \"%3\"). "
  416                                     "More probably you will want to report this error to the digikam developers mailing list (see www.digikam.org/support). "
  417                                     "As well, please have a look at what digiKam prints on the console. ",
  418                                     QLatin1String("digikam3.db"),
  419                                     QLatin1String("digikam4.db"),
  420                                     QDir::toNativeSeparators(currentDBFile.dir().path()));
  421 
  422             if (!endWrapSchemaUpdateStep(updateV4toV7(), errorMsg))
  423             {
  424                 return false;
  425             }
  426 
  427             qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating v4 to v6";
  428 
  429             // Still set these even in >= 1.4 because 0.10 - 1.3 may want to apply the updates if not set
  430             setLegacySettingEntries();
  431         }
  432 
  433         // Incremental updates, starting from version 5
  434         for (int v = d->currentVersion.toInt() ; v < schemaVersion() ; ++v)
  435         {
  436             int targetVersion = v + 1;
  437 
  438             if (!beginWrapSchemaUpdateStep())
  439             {
  440                 return false;
  441             }
  442 
  443             QString errorMsg = i18n("Failed to update the database schema from version %1 to version %2. "
  444                                     "Please read the error messages printed on the console and "
  445                                     "report this error as a bug at bugs.kde.org. ",
  446                                     d->currentVersion.toInt(),
  447                                     targetVersion);
  448 
  449             if (!endWrapSchemaUpdateStep(updateToVersion(targetVersion), errorMsg))
  450             {
  451                 return false;
  452             }
  453 
  454             qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt();
  455         }
  456 
  457         // add future updates here
  458     }
  459 
  460     return true;
  461 }
  462 
  463 void CoreDbSchemaUpdater::defaultFilterSettings(QStringList& defaultItemFilter, QStringList& defaultVideoFilter,
  464                                                 QStringList& defaultAudioFilter)
  465 {
  466     //NOTE for updating:
  467     //When changing anything here, just increment filterSettingsVersion() so that the changes take effect
  468 
  469     // https://en.wikipedia.org/wiki/Image_file_formats
  470 
  471     defaultItemFilter << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe")                                                 // JPEG
  472                        << QLatin1String("jp2") << QLatin1String("j2k")  << QLatin1String("jpx") << QLatin1String("jpc") << QLatin1String("pgx") // JPEG-2000
  473                        << QLatin1String("tif") << QLatin1String("tiff")                                                                         // TIFF
  474                        << QLatin1String("png")                                                                                                  // PNG
  475                        << QLatin1String("gif") << QLatin1String("xpm")  << QLatin1String("ppm") << QLatin1String("pnm") << QLatin1String("pgf")
  476                        << QLatin1String("bmp") << QLatin1String("pcx")
  477                        << QLatin1String("webp");
  478 
  479     // Raster graphics editor containers: https://en.wikipedia.org/wiki/Raster_graphics_editor
  480 
  481     defaultItemFilter << QLatin1String("xcf")
  482                        << QLatin1String("psd") << QLatin1String("psb")
  483                        << QLatin1String("kra") << QLatin1String("ora");
  484 
  485     // Raw images: https://en.wikipedia.org/wiki/Raw_image_format
  486 
  487     defaultItemFilter << DRawDecoder::rawFilesList();
  488 
  489     // Video files: https://en.wikipedia.org/wiki/Video_file_format
  490 
  491     defaultVideoFilter << QLatin1String("mpeg") << QLatin1String("mpg")  << QLatin1String("mpo") << QLatin1String("mpe") << QLatin1String("mts") << QLatin1String("vob")    // MPEG
  492                        << QLatin1String("avi")  << QLatin1String("divx")                                                                                                    // RIFF
  493                        << QLatin1String("wmv")  << QLatin1String("wmf")  << QLatin1String("asf")                                                                            // ASF
  494                        << QLatin1String("mp4")  << QLatin1String("3gp")  << QLatin1String("mov") << QLatin1String("3g2") << QLatin1String("m4v") << QLatin1String("m2v")    // QuickTime
  495                        << QLatin1String("mkv")  << QLatin1String("webm")                                                                                                    // Matroska
  496                        << QLatin1String("mng");                                                                                                                             // Animated PNG image
  497 
  498     // Audio files: https://en.wikipedia.org/wiki/Audio_file_format
  499 
  500     defaultAudioFilter << QLatin1String("ogg") << QLatin1String("oga") << QLatin1String("flac") << QLatin1String("wv")  << QLatin1String("ape") // Linux audio
  501                        << QLatin1String("mpc") << QLatin1String("au")                                                                           // Linux audio
  502                        << QLatin1String("m4b") << QLatin1String("aax") << QLatin1String("aa")                                                   // Book audio
  503                        << QLatin1String("mp3") << QLatin1String("aac")                                                                          // MPEG based audio
  504                        << QLatin1String("m4a") << QLatin1String("m4p") << QLatin1String("caf") << QLatin1String("aiff")                         // Apple audio
  505                        << QLatin1String("wma") << QLatin1String("wav");                                                                         // Windows audio
  506 }
  507 
  508 void CoreDbSchemaUpdater::defaultIgnoreDirectoryFilterSettings(QStringList& defaultIgnoreDirectoryFilter)
  509 {
  510     // NOTE: when update this section,
  511     // just increment filterSettingsVersion() so that the changes take effect
  512 
  513     defaultIgnoreDirectoryFilter << QLatin1String("@eaDir");
  514 }
  515 
  516 bool CoreDbSchemaUpdater::createFilterSettings()
  517 {
  518     QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter, defaultIgnoreDirectoryFilter;
  519     defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter);
  520     defaultIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter);
  521 
  522     d->albumDB->setFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter);
  523     d->albumDB->setIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter);
  524     d->albumDB->setSetting(QLatin1String("FilterSettingsVersion"),      QString::number(filterSettingsVersion()));
  525     d->albumDB->setSetting(QLatin1String("DcrawFilterSettingsVersion"), QString::number(DRawDecoder::rawFilesVersion()));
  526 
  527     return true;
  528 }
  529 
  530 bool CoreDbSchemaUpdater::updateFilterSettings()
  531 {
  532     QString filterVersion      = d->albumDB->getSetting(QLatin1String("FilterSettingsVersion"));
  533     QString dcrawFilterVersion = d->albumDB->getSetting(QLatin1String("DcrawFilterSettingsVersion"));
  534 
  535     if (filterVersion.toInt() < filterSettingsVersion() ||
  536         dcrawFilterVersion.toInt() < DRawDecoder::rawFilesVersion())
  537     {
  538         createFilterSettings();
  539     }
  540 
  541     return true;
  542 }
  543 
  544 bool CoreDbSchemaUpdater::createDatabase()
  545 {
  546     if ( createTables() && createIndices() && createTriggers())
  547     {
  548         setLegacySettingEntries();
  549 
  550         d->currentVersion = schemaVersion();
  551 
  552         // if we start with the V2 hash, version 6 is required
  553         d->albumDB->setUniqueHashVersion(uniqueHashVersion());
  554         d->currentRequiredVersion = schemaVersion();
  555 /*
  556         // Digikam for database version 5 can work with version 6, though not using the new features
  557         d->currentRequiredVersion = 5;
  558 */
  559         return true;
  560     }
  561     else
  562     {
  563         return false;
  564     }
  565 }
  566 
  567 bool CoreDbSchemaUpdater::createTables()
  568 {
  569     return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateDB")));
  570 }
  571 
  572 bool CoreDbSchemaUpdater::createIndices()
  573 {
  574     // TODO: see which more indices are needed
  575     // create indices
  576     return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateIndices")));
  577 }
  578 
  579 bool CoreDbSchemaUpdater::createTriggers()
  580 {
  581     return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateTriggers")));
  582 }
  583 
  584 bool CoreDbSchemaUpdater::updateUniqueHash()
  585 {
  586     if (isUniqueHashUpToDate())
  587     {
  588         return true;
  589     }
  590 
  591     readVersionSettings();
  592 
  593     {
  594         CoreDbTransaction transaction;
  595 
  596         CoreDbAccess().db()->setUniqueHashVersion(uniqueHashVersion());
  597 
  598         CollectionScanner scanner;
  599         scanner.setNeedFileCount(true);
  600         scanner.setUpdateHashHint();
  601 
  602         if (d->observer)
  603         {
  604             d->observer->connectCollectionScanner(&scanner);
  605             scanner.setObserver(d->observer);
  606         }
  607 
  608         scanner.completeScan();
  609 
  610         // earlier digikam does not know about the hash
  611         if (d->currentRequiredVersion.toInt() < 6)
  612         {
  613             d->currentRequiredVersion = 6;
  614             setVersionSettings();
  615         }
  616     }
  617     return true;
  618 }
  619 
  620 bool CoreDbSchemaUpdater::performUpdateToVersion(const QString& actionName, int newVersion, int newRequiredVersion)
  621 {
  622     if (d->observer)
  623     {
  624         if (!d->observer->continueQuery())
  625         {
  626             return false;
  627         }
  628 
  629         d->observer->moreSchemaUpdateSteps(1);
  630     }
  631 
  632     DbEngineAction updateAction = d->backend->getDBAction(actionName);
  633 
  634     if (updateAction.name.isNull())
  635     {
  636         QString errorMsg = i18n("The database update action cannot be found. Please ensure that "
  637                                 "the dbconfig.xml file of the current version of digiKam is installed "
  638                                 "at the correct place. ");
  639     }
  640 
  641     if (!d->backend->execDBAction(updateAction))
  642     {
  643         qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update to V" << newVersion << "failed!";
  644         // resort to default error message, set above
  645         return false;
  646     }
  647 
  648     if (d->observer)
  649     {
  650         if (!d->observer->continueQuery())
  651         {
  652             return false;
  653         }
  654 
  655         d->observer->schemaUpdateProgress(i18n("Updated schema to version %1.", newVersion));
  656     }
  657 
  658     d->currentVersion = newVersion;
  659     // Digikam for database version 5 can work with version 6, though not using the new features
  660     // Note: We do not upgrade the uniqueHash
  661     d->currentRequiredVersion = newRequiredVersion;
  662     return true;
  663 }
  664 
  665 bool CoreDbSchemaUpdater::updateToVersion(int targetVersion)
  666 {
  667     if (d->currentVersion != targetVersion-1)
  668     {
  669         qCDebug(DIGIKAM_COREDB_LOG) << "Core database: updateToVersion performs only incremental updates. Called to update from"
  670                                     << d->currentVersion << "to" << targetVersion << ", aborting.";
  671         return false;
  672     }
  673 
  674     switch (targetVersion)
  675     {
  676         case 6:
  677             // Digikam for database version 5 can work with version 6, though not using the new features
  678             // Note: We do not upgrade the uniqueHash
  679             return performUpdateToVersion(QLatin1String("UpdateSchemaFromV5ToV6"), 6, 5);
  680         case 7:
  681             // Digikam for database version 5 and 6 can work with version 7, though not using the support for video files.
  682             return performUpdateToVersion(QLatin1String("UpdateSchemaFromV6ToV7"), 7, 5);
  683             // NOTE: If you add a new update step, please check the d->currentVersion at the bottom of updateV4toV7
  684             // If the update already comes with createTables, createTriggers, we don't need the extra update here
  685         case 8:
  686             // Digikam for database version 7 can work with version 8, now using COLLATE utf8_general_ci for MySQL.
  687             return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 8, 5);
  688         case 9:
  689             // Digikam for database version 8 can work with version 9, now using COLLATE utf8_general_ci for MySQL.
  690             return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 9, 5);
  691         case 10:
  692             // Digikam for database version 9 can work with version 10, remove ImageHaarMatrix table and add manualOrder column.
  693             return performUpdateToVersion(QLatin1String("UpdateSchemaFromV9ToV10"), 10, 5);
  694         default:
  695             qCDebug(DIGIKAM_COREDB_LOG) << "Core database: unsupported update to version" << targetVersion;
  696             return false;
  697     }
  698 }
  699 
  700 bool CoreDbSchemaUpdater::copyV3toV4(const QString& digikam3DBPath, const QString& currentDBPath)
  701 {
  702     if (d->observer)
  703     {
  704         d->observer->moreSchemaUpdateSteps(2);
  705     }
  706 
  707     d->backend->close();
  708 
  709     // We cannot use KIO here because KIO only works from the main thread
  710     QFile oldFile(digikam3DBPath);
  711     QFile newFile(currentDBPath);
  712     // QFile won't override. Remove the empty db file created when a non-existent file is opened
  713     newFile.remove();
  714 
  715     if (!oldFile.copy(currentDBPath))
  716     {
  717         QString errorMsg = i18n("Failed to copy the old database file (\"%1\") "
  718                                 "to its new location (\"%2\"). "
  719                                 "Error message: \"%3\". "
  720                                 "Please make sure that the file can be copied, "
  721                                 "or delete it.",
  722                                 digikam3DBPath,
  723                                 currentDBPath,
  724                                 oldFile.errorString());
  725         d->lastErrorMessage = errorMsg;
  726         d->setError         = true;
  727 
  728         if (d->observer)
  729         {
  730             d->observer->error(errorMsg);
  731             d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  732         }
  733 
  734         return false;
  735     }
  736 
  737     if (d->observer)
  738     {
  739         d->observer->schemaUpdateProgress(i18n("Copied database file"));
  740     }
  741 
  742     if (!d->backend->open(d->parameters))
  743     {
  744         QString errorMsg = i18n("The old database file (\"%1\") has been copied "
  745                                 "to the new location (\"%2\") but it cannot be opened. "
  746                                 "Please delete both files and try again, "
  747                                 "starting with an empty database. ",
  748                                 digikam3DBPath,
  749                                 currentDBPath);
  750 
  751         d->lastErrorMessage = errorMsg;
  752         d->setError         = true;
  753 
  754         if (d->observer)
  755         {
  756             d->observer->error(errorMsg);
  757             d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  758         }
  759 
  760         return false;
  761     }
  762 
  763     if (d->observer)
  764     {
  765         d->observer->schemaUpdateProgress(i18n("Opened new database file"));
  766     }
  767 
  768     d->currentVersion = 4;
  769     return true;
  770 }
  771 
  772 static QStringList cleanUserFilterString(const QString& filterString)
  773 {
  774     // splits by either ; or space, removes "*.", trims
  775     QStringList filterList;
  776 
  777     QString wildcard(QLatin1String("*."));
  778     QChar dot(QLatin1Char('.'));
  779     Q_UNUSED(dot);
  780 
  781     QChar sep(QLatin1Char(';'));
  782     int i = filterString.indexOf( sep );
  783 
  784     if ( i == -1 && filterString.indexOf(QLatin1Char(' ')) != -1 )
  785     {
  786         sep = QChar(QLatin1Char(' '));
  787     }
  788 
  789     QStringList sepList = filterString.split(sep, QString::SkipEmptyParts);
  790 
  791     foreach(const QString& f, sepList)
  792     {
  793         if (f.startsWith(wildcard))
  794         {
  795             filterList << f.mid(2).trimmed().toLower();
  796         }
  797         else
  798         {
  799             filterList << f.trimmed().toLower();
  800         }
  801     }
  802 
  803     return filterList;
  804 }
  805 
  806 bool CoreDbSchemaUpdater::updateV4toV7()
  807 {
  808     qCDebug(DIGIKAM_COREDB_LOG) << "Core database : running updateV4toV7";
  809 
  810     if (d->observer)
  811     {
  812         if (!d->observer->continueQuery())
  813         {
  814             return false;
  815         }
  816 
  817         d->observer->moreSchemaUpdateSteps(11);
  818     }
  819 
  820     // This update was introduced from digikam version 0.9 to digikam 0.10
  821     // We operator on an SQLite3 database under a transaction (which will be rolled back on error)
  822 
  823     // --- Make space for new tables ---
  824     if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Albums RENAME TO AlbumsV3;")))
  825     {
  826         return false;
  827     }
  828 
  829     if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Images RENAME TO ImagesV3;")))
  830     {
  831         return false;
  832     }
  833 
  834     if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;")))
  835     {
  836         return false;
  837     }
  838 
  839     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: moved tables";
  840 
  841     // --- Drop some triggers and indices ---
  842 
  843     // Don't check for errors here. The "IF EXISTS" clauses seem not supported in SQLite
  844     d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_album;"));
  845     d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;"));
  846     d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tag;"));
  847     d->backend->execSql(QString::fromUtf8("DROP TRIGGER insert_tagstree;"));
  848     d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tagstree;"));
  849     d->backend->execSql(QString::fromUtf8("DROP TRIGGER move_tagstree;"));
  850     d->backend->execSql(QString::fromUtf8("DROP INDEX dir_index;"));
  851     d->backend->execSql(QString::fromUtf8("DROP INDEX tag_index;"));
  852 
  853     if (d->observer)
  854     {
  855         if (!d->observer->continueQuery())
  856         {
  857             return false;
  858         }
  859 
  860         d->observer->schemaUpdateProgress(i18n("Prepared table creation"));
  861     }
  862 
  863     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: dropped triggers";
  864 
  865     // --- Create new tables ---
  866 
  867     if (!createTables() || !createIndices())
  868     {
  869         return false;
  870     }
  871 
  872     if (d->observer)
  873     {
  874         if (!d->observer->continueQuery())
  875         {
  876             return false;
  877         }
  878 
  879         d->observer->schemaUpdateProgress(i18n("Created tables"));
  880     }
  881 
  882     // --- Populate AlbumRoots (from config) ---
  883 
  884     KSharedConfigPtr config  = KSharedConfig::openConfig();
  885     KConfigGroup group       = config->group(QLatin1String("Album Settings"));
  886     QString albumLibraryPath = group.readEntry(QLatin1String("Album Path"), QString());
  887 
  888     if (albumLibraryPath.isEmpty())
  889     {
  890         qCDebug(DIGIKAM_COREDB_LOG) << "Core database: Album library path from config file is empty. Aborting update.";
  891 
  892         QString errorMsg    = i18n("No album library path has been found in the configuration file. "
  893                                    "Giving up the schema updating process. "
  894                                    "Please try with an empty database, or repair your configuration.");
  895         d->lastErrorMessage = errorMsg;
  896         d->setError         = true;
  897 
  898         if (d->observer)
  899         {
  900             d->observer->error(errorMsg);
  901             d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  902         }
  903 
  904         return false;
  905     }
  906 
  907     QUrl albumLibrary(QUrl::fromLocalFile(albumLibraryPath));
  908     CollectionLocation location = CollectionManager::instance()->addLocation(albumLibrary, albumLibrary.fileName());
  909 
  910     if (location.isNull())
  911     {
  912         qCDebug(DIGIKAM_COREDB_LOG) << "Core database: failure to create a collection location. Aborting update.";
  913 
  914         QString errorMsg    = i18n("There was an error associating your albumLibraryPath (\"%1\") "
  915                                    "with a storage volume of your system. "
  916                                    "This problem may indicate that there is a problem with your installation. "
  917                                    "If you are working on Linux, check that HAL is installed and running. "
  918                                    "In any case, you can seek advice from the digikam developers mailing list (see www.digikam.org/support). "
  919                                    "The database updating process will now be aborted because we do not want "
  920                                    "to create a new database based on false assumptions from a broken installation.",
  921                                    albumLibraryPath);
  922         d->lastErrorMessage = errorMsg;
  923         d->setError         = true;
  924 
  925         if (d->observer)
  926         {
  927             d->observer->error(errorMsg);
  928             d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
  929         }
  930 
  931         return false;
  932     }
  933 
  934     if (d->observer)
  935     {
  936         if (!d->observer->continueQuery())
  937         {
  938             return false;
  939         }
  940 
  941         d->observer->schemaUpdateProgress(i18n("Configured one album root"));
  942     }
  943 
  944     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: inserted album root";
  945 
  946     // --- With the album root, populate albums ---
  947 
  948     if (!d->backend->execSql(QString::fromUtf8(
  949                                 "REPLACE INTO Albums "
  950                                 " (id, albumRoot, relativePath, date, caption, collection, icon) "
  951                                 "SELECT id, ?, url, date, caption, collection, icon "
  952                                 " FROM AlbumsV3;"
  953                             ),
  954                             location.id())
  955        )
  956     {
  957         return false;
  958     }
  959 
  960     if (d->observer)
  961     {
  962         if (!d->observer->continueQuery())
  963         {
  964             return false;
  965         }
  966 
  967         d->observer->schemaUpdateProgress(i18n("Imported albums"));
  968     }
  969 
  970     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated albums";
  971 
  972     // --- Add images ---
  973 
  974     if (!d->backend->execSql(QString::fromUtf8(
  975                                 "REPLACE INTO Images "
  976                                 " (id, album, name, status, category, modificationDate, fileSize, uniqueHash) "
  977                                 "SELECT id, dirid, name, ?, ?, NULL, NULL, NULL"
  978                                 " FROM ImagesV3;"
  979                             ),
  980                             DatabaseItem::Visible, DatabaseItem::UndefinedCategory)
  981        )
  982     {
  983         return false;
  984     }
  985 
  986     if (!d->dbAccess->backend()->execSql(QString::fromUtf8(
  987                                           "REPLACE INTO ImageInformation (imageId) SELECT id FROM Images;"))
  988        )
  989     {
  990         return false;
  991     }
  992 
  993     // remove orphan images that would not be removed by CollectionScanner
  994     d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE album NOT IN (SELECT id FROM Albums);"));
  995 
  996     if (d->observer)
  997     {
  998         if (!d->observer->continueQuery())
  999         {
 1000             return false;
 1001         }
 1002 
 1003         d->observer->schemaUpdateProgress(i18n("Imported images information"));
 1004     }
 1005 
 1006     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated Images";
 1007 
 1008     // --- Port searches ---
 1009 
 1010     if (!d->backend->execSql(QString::fromUtf8(
 1011                                 "REPLACE INTO Searches "
 1012                                 " (id, type, name, query) "
 1013                                 "SELECT id, ?, name, url"
 1014                                 " FROM SearchesV3;"),
 1015                             DatabaseSearch::LegacyUrlSearch)
 1016        )
 1017     {
 1018         return false;
 1019     }
 1020 
 1021     SearchInfo::List sList = d->albumDB->scanSearches();
 1022 
 1023     for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it)
 1024     {
 1025         QUrl url((*it).query);
 1026 
 1027         ItemQueryBuilder builder;
 1028         QString query = builder.convertFromUrlToXml(url);
 1029         QString name  = (*it).name;
 1030 
 1031         if (name == i18n("Last Search"))
 1032         {
 1033             name = i18n("Last Search (0.9)");
 1034         }
 1035 
 1036         if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch"))
 1037         {
 1038             d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, name, query);
 1039         }
 1040         else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword"))
 1041         {
 1042             d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, name, query);
 1043         }
 1044         else
 1045         {
 1046             d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, name, query);
 1047         }
 1048     }
 1049 
 1050     // --- Create triggers ---
 1051 
 1052     if (!createTriggers())
 1053     {
 1054         return false;
 1055     }
 1056 
 1057     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: created triggers";
 1058 
 1059     // --- Populate name filters ---
 1060 
 1061     createFilterSettings();
 1062 
 1063     // --- Set user settings from config ---
 1064 
 1065     QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter;
 1066     defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter);
 1067 
 1068     QSet<QString> configItemFilter, configVideoFilter, configAudioFilter;
 1069 
 1070     configItemFilter   = cleanUserFilterString(group.readEntry(QLatin1String("File Filter"),       QString())).toSet();
 1071     configItemFilter  += cleanUserFilterString(group.readEntry(QLatin1String("Raw File Filter"),   QString())).toSet();
 1072     configVideoFilter   = cleanUserFilterString(group.readEntry(QLatin1String("Movie File Filter"), QString())).toSet();
 1073     configAudioFilter   = cleanUserFilterString(group.readEntry(QLatin1String("Audio File Filter"), QString())).toSet();
 1074 
 1075     // remove those that are included in the default filter
 1076     configItemFilter.subtract(defaultItemFilter.toSet());
 1077     configVideoFilter.subtract(defaultVideoFilter.toSet());
 1078     configAudioFilter.subtract(defaultAudioFilter.toSet());
 1079 
 1080     d->albumDB->setUserFilterSettings(configItemFilter.toList(), configVideoFilter.toList(), configAudioFilter.toList());
 1081     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: set initial filter settings with user settings" << configItemFilter;
 1082 
 1083     if (d->observer)
 1084     {
 1085         if (!d->observer->continueQuery())
 1086         {
 1087             return false;
 1088         }
 1089 
 1090         d->observer->schemaUpdateProgress(i18n("Initialized and imported file suffix filter"));
 1091     }
 1092 
 1093     // --- do a full scan ---
 1094 
 1095     CollectionScanner scanner;
 1096 
 1097     if (d->observer)
 1098     {
 1099         d->observer->connectCollectionScanner(&scanner);
 1100         scanner.setObserver(d->observer);
 1101     }
 1102 
 1103     scanner.completeScan();
 1104 
 1105     if (d->observer)
 1106     {
 1107         if (!d->observer->continueQuery())
 1108         {
 1109             return false;
 1110         }
 1111 
 1112         d->observer->schemaUpdateProgress(i18n("Did the initial full scan"));
 1113     }
 1114 
 1115     // --- Port date, comment and rating (_after_ the scan) ---
 1116 
 1117     // Port ImagesV3.date -> ImageInformation.creationDate
 1118     if (!d->backend->execSql(QString::fromUtf8(
 1119                                 "UPDATE ImageInformation SET "
 1120                                 " creationDate=(SELECT datetime FROM ImagesV3 WHERE ImagesV3.id=ImageInformation.imageid) "
 1121                                 "WHERE imageid IN (SELECT id FROM ImagesV3);")
 1122                            )
 1123        )
 1124     {
 1125         return false;
 1126     }
 1127 
 1128     if (d->observer)
 1129     {
 1130         if (!d->observer->continueQuery())
 1131         {
 1132             return false;
 1133         }
 1134 
 1135         d->observer->schemaUpdateProgress(i18n("Imported creation dates"));
 1136     }
 1137 
 1138     // Port ImagesV3.comment to ItemComments
 1139 
 1140     // An author of NULL will inhibt the UNIQUE restriction to take effect (but #189080). Work around.
 1141     d->backend->execSql(QString::fromUtf8(
 1142                            "DELETE FROM ImageComments WHERE "
 1143                            "type=? AND language=? AND author IS NULL "
 1144                            "AND imageid IN ( SELECT id FROM ImagesV3 ); "),
 1145                        (int)DatabaseComment::Comment, QLatin1String("x-default"));
 1146 
 1147     if (!d->backend->execSql(QString::fromUtf8(
 1148                                 "REPLACE INTO ImageComments "
 1149                                 " (imageid, type, language, comment) "
 1150                                 "SELECT id, ?, ?, caption FROM ImagesV3;"
 1151                             ),
 1152                             (int)DatabaseComment::Comment, QLatin1String("x-default"))
 1153        )
 1154     {
 1155         return false;
 1156     }
 1157 
 1158     if (d->observer)
 1159     {
 1160         if (!d->observer->continueQuery())
 1161         {
 1162             return false;
 1163         }
 1164 
 1165         d->observer->schemaUpdateProgress(i18n("Imported comments"));
 1166     }
 1167 
 1168     // Port rating storage in ImageProperties to ItemInformation
 1169     if (!d->backend->execSql(QString::fromUtf8(
 1170                                 "UPDATE ImageInformation SET "
 1171                                 " rating=(SELECT value FROM ImageProperties "
 1172                                 "         WHERE ImageInformation.imageid=ImageProperties.imageid AND ImageProperties.property=?) "
 1173                                 "WHERE imageid IN (SELECT imageid FROM ImageProperties WHERE property=?);"
 1174                             ),
 1175                             QString::fromUtf8("Rating"), QString::fromUtf8("Rating"))
 1176        )
 1177     {
 1178         return false;
 1179     }
 1180 
 1181     d->backend->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), QString::fromUtf8("Rating"));
 1182     d->backend->execSql(QString::fromUtf8("UPDATE ImageInformation SET rating=0 WHERE rating<0;"));
 1183 
 1184     if (d->observer)
 1185     {
 1186         if (!d->observer->continueQuery())
 1187         {
 1188             return false;
 1189         }
 1190 
 1191         d->observer->schemaUpdateProgress(i18n("Imported ratings"));
 1192     }
 1193 
 1194     // --- Drop old tables ---
 1195 
 1196     d->backend->execSql(QString::fromUtf8("DROP TABLE ImagesV3;"));
 1197     d->backend->execSql(QString::fromUtf8("DROP TABLE AlbumsV3;"));
 1198     d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;"));
 1199 
 1200     if (d->observer)
 1201     {
 1202         d->observer->schemaUpdateProgress(i18n("Dropped v3 tables"));
 1203     }
 1204 
 1205     d->currentRequiredVersion = 5;
 1206     d->currentVersion         = 7;
 1207     qCDebug(DIGIKAM_COREDB_LOG) << "Core database: returning true from updating to 5";
 1208     return true;
 1209 }
 1210 
 1211 void CoreDbSchemaUpdater::setLegacySettingEntries()
 1212 {
 1213     d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true"));
 1214     d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true"));
 1215     d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true"));
 1216     d->albumDB->setSetting(QLatin1String("beta010Update1"),     QLatin1String("true"));
 1217     d->albumDB->setSetting(QLatin1String("beta010Update2"),     QLatin1String("true"));
 1218 }
 1219 
 1220 // ---------- Legacy code ------------
 1221 
 1222 void CoreDbSchemaUpdater::preAlpha010Update1()
 1223 {
 1224     QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update1"));
 1225 
 1226     if (!hasUpdate.isNull())
 1227     {
 1228         return;
 1229     }
 1230 
 1231     if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;")))
 1232     {
 1233         return;
 1234     }
 1235 
 1236     if ( !d->backend->execSql(
 1237              QString::fromUtf8( "CREATE TABLE IF NOT EXISTS Searches  \n"
 1238                                 " (id INTEGER PRIMARY KEY, \n"
 1239                                 "  type INTEGER, \n"
 1240                                 "  name TEXT NOT NULL, \n"
 1241                                 "  query TEXT NOT NULL);" ) ))
 1242     {
 1243         return;
 1244     }
 1245 
 1246     if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches "
 1247                                                 " (id, type, name, query) "
 1248                                                 "SELECT id, ?, name, url"
 1249                                                 " FROM SearchesV3;"),
 1250                              DatabaseSearch::LegacyUrlSearch)
 1251        )
 1252     {
 1253         return;
 1254     }
 1255 
 1256     SearchInfo::List sList = d->albumDB->scanSearches();
 1257 
 1258     for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it)
 1259     {
 1260         QUrl url((*it).query);
 1261 
 1262         ItemQueryBuilder builder;
 1263         QString query = builder.convertFromUrlToXml(url);
 1264 
 1265         if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch"))
 1266         {
 1267             d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, (*it).name, query);
 1268         }
 1269         else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword"))
 1270         {
 1271             d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, (*it).name, query);
 1272         }
 1273         else
 1274         {
 1275             d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, (*it).name, query);
 1276         }
 1277     }
 1278 
 1279     d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;"));
 1280 
 1281     d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true"));
 1282 }
 1283 
 1284 void CoreDbSchemaUpdater::preAlpha010Update2()
 1285 {
 1286     QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update2"));
 1287 
 1288     if (!hasUpdate.isNull())
 1289     {
 1290         return;
 1291     }
 1292 
 1293     if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImagePositions RENAME TO ItemPositionsTemp;")))
 1294     {
 1295         return;
 1296     }
 1297 
 1298     if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImageMetadata RENAME TO ImageMetadataTemp;")))
 1299     {
 1300         return;
 1301     }
 1302 
 1303     d->backend->execSql(
 1304         QString::fromUtf8("CREATE TABLE ItemPositions\n"
 1305                           " (imageid INTEGER PRIMARY KEY,\n"
 1306                           "  latitude TEXT,\n"
 1307                           "  latitudeNumber REAL,\n"
 1308                           "  longitude TEXT,\n"
 1309                           "  longitudeNumber REAL,\n"
 1310                           "  altitude REAL,\n"
 1311                           "  orientation REAL,\n"
 1312                           "  tilt REAL,\n"
 1313                           "  roll REAL,\n"
 1314                           "  accuracy REAL,\n"
 1315                           "  description TEXT);") );
 1316 
 1317     d->backend->execSql(QString::fromUtf8("REPLACE INTO ImagePositions "
 1318                                           " (imageid, latitude, latitudeNumber, longitude, longitudeNumber, "
 1319                                           "  altitude, orientation, tilt, roll, accuracy, description) "
 1320                                           "SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, "
 1321                                           "  altitude, orientation, tilt, roll, 0, description "
 1322                                           " FROM ItemPositionsTemp;"));
 1323 
 1324     d->backend->execSql(
 1325         QString::fromUtf8("CREATE TABLE ImageMetadata\n"
 1326                           " (imageid INTEGER PRIMARY KEY,\n"
 1327                           "  make TEXT,\n"
 1328                           "  model TEXT,\n"
 1329                           "  lens TEXT,\n"
 1330                           "  aperture REAL,\n"
 1331                           "  focalLength REAL,\n"
 1332                           "  focalLength35 REAL,\n"
 1333                           "  exposureTime REAL,\n"
 1334                           "  exposureProgram INTEGER,\n"
 1335                           "  exposureMode INTEGER,\n"
 1336                           "  sensitivity INTEGER,\n"
 1337                           "  flash INTEGER,\n"
 1338                           "  whiteBalance INTEGER,\n"
 1339                           "  whiteBalanceColorTemperature INTEGER,\n"
 1340                           "  meteringMode INTEGER,\n"
 1341                           "  subjectDistance REAL,\n"
 1342                           "  subjectDistanceCategory INTEGER);") );
 1343 
 1344     d->backend->execSql( QString::fromUtf8("INSERT INTO ImageMetadata "
 1345                                            " (imageid, make, model, lens, aperture, focalLength, focalLength35, "
 1346                                            "  exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, "
 1347                                            "  whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) "
 1348                                            "SELECT imageid, make, model, NULL, aperture, focalLength, focalLength35, "
 1349                                            "  exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, "
 1350                                            "  whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory "
 1351                                            "FROM ImageMetadataTemp;"));
 1352 
 1353     d->backend->execSql(QString::fromUtf8("DROP TABLE ItemPositionsTemp;"));
 1354     d->backend->execSql(QString::fromUtf8("DROP TABLE ImageMetadataTemp;"));
 1355 
 1356     d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true"));
 1357 }
 1358 
 1359 void CoreDbSchemaUpdater::preAlpha010Update3()
 1360 {
 1361     QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update3"));
 1362 
 1363     if (!hasUpdate.isNull())
 1364     {
 1365         return;
 1366     }
 1367 
 1368     d->backend->execSql(QString::fromUtf8("DROP TABLE ItemCopyright;"));
 1369     d->backend->execSql(QString::fromUtf8("CREATE TABLE ItemCopyright\n"
 1370                                           " (imageid INTEGER,\n"
 1371                                           "  property TEXT,\n"
 1372                                           "  value TEXT,\n"
 1373                                           "  extraValue TEXT,\n"
 1374                                           "  UNIQUE(imageid, property, value, extraValue));")
 1375     );
 1376 
 1377     d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true"));
 1378 }
 1379 
 1380 void CoreDbSchemaUpdater::beta010Update1()
 1381 {
 1382     QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update1"));
 1383 
 1384     if (!hasUpdate.isNull())
 1385     {
 1386         return;
 1387     }
 1388 
 1389     // if Image has been deleted
 1390     d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;"));
 1391     d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n"
 1392                                           "BEGIN\n"
 1393                                           "  DELETE FROM ImageTags\n"
 1394                                           "    WHERE imageid=OLD.id;\n"
 1395                                           "  DELETE From ImageHaarMatrix\n "
 1396                                           "    WHERE imageid=OLD.id;\n"
 1397                                           "  DELETE From ItemInformation\n "
 1398                                           "    WHERE imageid=OLD.id;\n"
 1399                                           "  DELETE From ImageMetadata\n "
 1400                                           "    WHERE imageid=OLD.id;\n"
 1401                                           "  DELETE From ItemPositions\n "
 1402                                           "    WHERE imageid=OLD.id;\n"
 1403                                           "  DELETE From ItemComments\n "
 1404                                           "    WHERE imageid=OLD.id;\n"
 1405                                           "  DELETE From ItemCopyright\n "
 1406                                           "    WHERE imageid=OLD.id;\n"
 1407                                           "  DELETE From ImageProperties\n "
 1408                                           "    WHERE imageid=OLD.id;\n"
 1409                                           "  UPDATE Albums SET icon=null \n "
 1410                                           "    WHERE icon=OLD.id;\n"
 1411                                           "  UPDATE Tags SET icon=null \n "
 1412                                           "    WHERE icon=OLD.id;\n"
 1413                                           "END;"));
 1414 
 1415     d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true"));
 1416 }
 1417 
 1418 void CoreDbSchemaUpdater::beta010Update2()
 1419 {
 1420     QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update2"));
 1421 
 1422     if (!hasUpdate.isNull())
 1423     {
 1424         return;
 1425     }
 1426 
 1427     // force rescan and creation of ImageInformation entry for videos and audio
 1428     d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE category=2 OR category=3;"));
 1429 
 1430     d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true"));
 1431 }
 1432 
 1433 bool CoreDbSchemaUpdater::createTablesV3()
 1434 {
 1435     if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Albums\n"
 1436                                                 " (id INTEGER PRIMARY KEY,\n"
 1437                                                 "  url TEXT NOT NULL UNIQUE,\n"
 1438                                                 "  date DATE NOT NULL,\n"
 1439                                                 "  caption TEXT,\n"
 1440                                                 "  collection TEXT,\n"
 1441                                                 "  icon INTEGER);") ))
 1442     {
 1443         return false;
 1444     }
 1445 
 1446     if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Tags\n"
 1447                                                 " (id INTEGER PRIMARY KEY,\n"
 1448                                                 "  pid INTEGER,\n"
 1449                                                 "  name TEXT NOT NULL,\n"
 1450                                                 "  icon INTEGER,\n"
 1451                                                 "  iconkde TEXT,\n"
 1452                                                 "  UNIQUE (name, pid));") ))
 1453     {
 1454         return false;
 1455     }
 1456 
 1457     if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE TagsTree\n"
 1458                                                 " (id INTEGER NOT NULL,\n"
 1459                                                 "  pid INTEGER NOT NULL,\n"
 1460                                                 "  UNIQUE (id, pid));") ))
 1461     {
 1462         return false;
 1463     }
 1464 
 1465     if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Images\n"
 1466                                                 " (id INTEGER PRIMARY KEY,\n"
 1467                                                 "  name TEXT NOT NULL,\n"
 1468                                                 "  dirid INTEGER NOT NULL,\n"
 1469                                                 "  caption TEXT,\n"
 1470                                                 "  datetime DATETIME,\n"
 1471                                                 "  UNIQUE (name, dirid));") ))
 1472     {
 1473         return false;
 1474     }
 1475 
 1476 
 1477     if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageTags\n"
 1478                                                 " (imageid INTEGER NOT NULL,\n"
 1479                                                 "  tagid INTEGER NOT NULL,\n"
 1480                                                 "  UNIQUE (imageid, tagid));") ))
 1481     {
 1482         return false;
 1483     }
 1484 
 1485     if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageProperties\n"
 1486                                                 " (imageid  INTEGER NOT NULL,\n"
 1487                                                 "  property TEXT    NOT NULL,\n"
 1488                                                 "  value    TEXT    NOT NULL,\n"
 1489                                                 "  UNIQUE (imageid, property));") ))
 1490     {
 1491         return false;
 1492     }
 1493 
 1494     if ( !d->backend->execSql( QString::fromUtf8("CREATE TABLE Searches  \n"
 1495                                                   " (id INTEGER PRIMARY KEY, \n"
 1496                                                   "  name TEXT NOT NULL UNIQUE, \n"
 1497                                                   "  url  TEXT NOT NULL);") ) )
 1498     {
 1499         return false;
 1500     }
 1501 
 1502     if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Settings         \n"
 1503                                                 "(keyword TEXT NOT NULL UNIQUE,\n"
 1504                                                 " value TEXT);") ))
 1505     {
 1506         return false;
 1507     }
 1508 
 1509     // TODO: see which more indices are needed
 1510     // create indices
 1511     d->backend->execSql(QString::fromUtf8("CREATE INDEX dir_index ON Images    (dirid);"));
 1512     d->backend->execSql(QString::fromUtf8("CREATE INDEX tag_index ON ImageTags (tagid);"));
 1513 
 1514     // create triggers
 1515 
 1516     // trigger: delete from Images/ImageTags/ImageProperties
 1517     // if Album has been deleted
 1518     d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_album DELETE ON Albums\n"
 1519                                           "BEGIN\n"
 1520                                           " DELETE FROM ImageTags\n"
 1521                                           "   WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n"
 1522                                           " DELETE From ImageProperties\n"
 1523                                           "   WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n"
 1524                                           " DELETE FROM Images\n"
 1525                                           "   WHERE dirid = OLD.id;\n"
 1526                                           "END;"));
 1527 
 1528     // trigger: delete from ImageTags/ImageProperties
 1529     // if Image has been deleted
 1530     d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n"
 1531                                           "BEGIN\n"
 1532                                           "  DELETE FROM ImageTags\n"
 1533                                           "    WHERE imageid=OLD.id;\n"
 1534                                           "  DELETE From ImageProperties\n "
 1535                                           "    WHERE imageid=OLD.id;\n"
 1536                                           "  UPDATE Albums SET icon=null \n "
 1537                                           "    WHERE icon=OLD.id;\n"
 1538                                           "  UPDATE Tags SET icon=null \n "
 1539                                           "    WHERE icon=OLD.id;\n"
 1540                                           "END;"));
 1541 
 1542     // trigger: delete from ImageTags if Tag has been deleted
 1543     d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tag DELETE ON Tags\n"
 1544                                           "BEGIN\n"
 1545                                           "  DELETE FROM ImageTags WHERE tagid=OLD.id;\n"
 1546                                           "END;"));
 1547 
 1548     // trigger: insert into TagsTree if Tag has been added
 1549     d->backend->execSql(QString::fromUtf8("CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags\n"
 1550                                           "BEGIN\n"
 1551                                           "  INSERT INTO TagsTree\n"
 1552                                           "    SELECT NEW.id, NEW.pid\n"
 1553                                           "    UNION\n"
 1554                                           "    SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid;\n"
 1555                                           "END;"));
 1556 
 1557     // trigger: delete from TagsTree if Tag has been deleted
 1558     d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tagstree DELETE ON Tags\n"
 1559                                           "BEGIN\n"
 1560                                           " DELETE FROM Tags\n"
 1561                                           "   WHERE id  IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n"
 1562                                           " DELETE FROM TagsTree\n"
 1563                                           "   WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n"
 1564                                           " DELETE FROM TagsTree\n"
 1565                                           "    WHERE id=OLD.id;\n"
 1566                                           "END;"));
 1567 
 1568     // trigger: delete from TagsTree if Tag has been deleted
 1569     d->backend->execSql(QString::fromUtf8("CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags\n"
 1570                                           "BEGIN\n"
 1571                                           "  DELETE FROM TagsTree\n"
 1572                                           "    WHERE\n"
 1573                                           "      ((id = OLD.id)\n"
 1574                                           "        OR\n"
 1575                                           "        id IN (SELECT id FROM TagsTree WHERE pid=OLD.id))\n"
 1576                                           "      AND\n"
 1577                                           "      pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id);\n"
 1578                                           "  INSERT INTO TagsTree\n"
 1579                                           "     SELECT NEW.id, NEW.pid\n"
 1580                                           "     UNION\n"
 1581                                           "     SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid\n"
 1582                                           "     UNION\n"
 1583                                           "     SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id\n"
 1584                                           "     UNION\n"
 1585                                           "     SELECT A.id, B.pid FROM TagsTree A, TagsTree B\n"
 1586                                           "        WHERE\n"
 1587                                           "        A.pid = NEW.id AND B.id = NEW.pid;\n"
 1588                                           "END;"));
 1589 
 1590     return true;
 1591 }
 1592 
 1593 } // namespace Digikam