File indexing completed on 2025-03-09 03:54: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 : Face database schema updater
0008  *
0009  * SPDX-FileCopyrightText: 2007-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText:      2020 by Nghia Duong <minhnghiaduong997 at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "facedbschemaupdater.h"
0018 
0019 // KDE includes
0020 
0021 #include <klocalizedstring.h>
0022 
0023 // Local includes
0024 
0025 #include "digikam_debug.h"
0026 #include "dbenginebackend.h"
0027 #include "facedbaccess.h"
0028 #include "facedb.h"
0029 
0030 namespace Digikam
0031 {
0032 
0033 int FaceDbSchemaUpdater::schemaVersion()
0034 {
0035     return 4;
0036 }
0037 
0038 // -------------------------------------------------------------------------------------
0039 
0040 class Q_DECL_HIDDEN FaceDbSchemaUpdater::Private
0041 {
0042 public:
0043 
0044     explicit Private()
0045         : setError              (false),
0046           currentVersion        (0),
0047           currentRequiredVersion(0),
0048           dbAccess              (nullptr),
0049           observer              (nullptr)
0050     {
0051     }
0052 
0053     bool                    setError;
0054 
0055     int                     currentVersion;
0056     int                     currentRequiredVersion;
0057 
0058     FaceDbAccess*           dbAccess;
0059 
0060     InitializationObserver* observer;
0061 };
0062 
0063 FaceDbSchemaUpdater::FaceDbSchemaUpdater(FaceDbAccess* const dbAccess)
0064     : d(new Private)
0065 {
0066     d->dbAccess = dbAccess;
0067 }
0068 
0069 FaceDbSchemaUpdater::~FaceDbSchemaUpdater()
0070 {
0071     delete d;
0072 }
0073 
0074 void FaceDbSchemaUpdater::setObserver(InitializationObserver* const observer)
0075 {
0076     d->observer = observer;
0077 }
0078 
0079 bool FaceDbSchemaUpdater::update()
0080 {
0081     bool success = startUpdates();
0082 
0083     // even on failure, try to set current version - it may have incremented
0084 
0085     if (d->currentVersion)
0086     {
0087         d->dbAccess->db()->setSetting(QLatin1String("DBFaceVersion"), QString::number(d->currentVersion));
0088     }
0089 
0090     if (d->currentRequiredVersion)
0091     {
0092         d->dbAccess->db()->setSetting(QLatin1String("DBFaceVersionRequired"), QString::number(d->currentRequiredVersion));
0093     }
0094 
0095     return success;
0096 }
0097 
0098 bool FaceDbSchemaUpdater::startUpdates()
0099 {
0100     // First step: do we have an empty database?
0101 
0102     QStringList tables = d->dbAccess->backend()->tables();
0103 
0104     if (tables.contains(QLatin1String("Identities"), Qt::CaseInsensitive))
0105     {
0106         // Find out schema version of db file
0107 
0108         QString version         = d->dbAccess->db()->setting(QLatin1String("DBFaceVersion"));
0109         QString versionRequired = d->dbAccess->db()->setting(QLatin1String("DBFaceVersionRequired"));
0110         qCDebug(DIGIKAM_FACEDB_LOG) << "Face database: have a structure version " << version;
0111 
0112         // mini schema update
0113 
0114         if (version.isEmpty() && d->dbAccess->parameters().isSQLite())
0115         {
0116             version = d->dbAccess->db()->setting(QLatin1String("DBVersion"));
0117         }
0118 
0119         // We absolutely require the DBFaceVersion setting
0120 
0121         if (version.isEmpty())
0122         {
0123             // Something is damaged. Give up.
0124 
0125             qCWarning(DIGIKAM_FACEDB_LOG) << "DBFaceVersion not available! Giving up schema upgrading.";
0126 
0127             QString errorMsg = i18n("The database is not valid: "
0128                                     "the \"DBFaceVersion\" setting does not exist. "
0129                                     "The current database schema version cannot be verified. "
0130                                     "Try to start with an empty database. ");
0131 
0132             d->dbAccess->setLastError(errorMsg);
0133 
0134             if (d->observer)
0135             {
0136                 d->observer->error(errorMsg);
0137                 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
0138             }
0139 
0140             return false;
0141         }
0142 
0143         // current version describes the current state of the schema in the db,
0144         // schemaVersion is the version required by the program.
0145 
0146         d->currentVersion = version.toInt();
0147 
0148         if (d->currentVersion > schemaVersion())
0149         {
0150             // trying to open a database with a more advanced than this FaceDbSchemaUpdater supports
0151 
0152             if (!versionRequired.isEmpty() && (versionRequired.toInt() <= schemaVersion()))
0153             {
0154                 // version required may be less than current version
0155 
0156                 return true;
0157             }
0158             else
0159             {
0160                 QString errorMsg = i18n("The database has been used with a more recent version of digiKam "
0161                                         "and has been updated to a database schema which cannot be used with this version. "
0162                                         "(This means this digiKam version is too old, or the database format is to recent.) "
0163                                         "Please use the more recent version of digiKam that you used before.");
0164 
0165                 d->dbAccess->setLastError(errorMsg);
0166 
0167                 if (d->observer)
0168                 {
0169                     d->observer->error(errorMsg);
0170                     d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
0171                 }
0172 
0173                 return false;
0174             }
0175         }
0176         else
0177         {
0178             return makeUpdates();
0179         }
0180     }
0181     else
0182     {
0183         qCDebug(DIGIKAM_FACEDB_LOG) << "Face database: no database file available";
0184 
0185         DbEngineParameters parameters = d->dbAccess->parameters();
0186 
0187         // No legacy handling: start with a fresh db
0188 
0189         if (!createDatabase())
0190         {
0191             QString errorMsg = i18n("Failed to create tables in database.\n%1",
0192                                     d->dbAccess->backend()->lastError());
0193             d->dbAccess->setLastError(errorMsg);
0194 
0195             if (d->observer)
0196             {
0197                 d->observer->error(errorMsg);
0198                 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
0199             }
0200 
0201             return false;
0202         }
0203 
0204         return true;
0205     }
0206 }
0207 
0208 bool FaceDbSchemaUpdater::makeUpdates()
0209 {
0210     if (d->currentVersion < schemaVersion())
0211     {
0212         if      (d->currentVersion == 1)
0213         {
0214             updateV1ToV2();
0215         }
0216         else if (d->currentVersion == 2)
0217         {
0218             updateV2ToV3();
0219         }
0220         else if (d->currentVersion == 3)
0221         {
0222             updateV3ToV4();
0223         }
0224     }
0225 
0226     return true;
0227 }
0228 
0229 
0230 bool FaceDbSchemaUpdater::createDatabase()
0231 {
0232     if (createTables() && createIndices() && createTriggers())
0233     {
0234         d->currentVersion         = schemaVersion();
0235         d->currentRequiredVersion = 4;
0236         return true;
0237     }
0238     else
0239     {
0240         return false;
0241     }
0242 }
0243 
0244 bool FaceDbSchemaUpdater::createTables()
0245 {
0246     // the creation order is important because of the foreign keys in MySQL
0247 
0248     return (
0249             d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDB")))             &&
0250             d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBFaceMatrices"))) &&
0251             d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBKDTree")))
0252            );
0253 }
0254 
0255 bool FaceDbSchemaUpdater::createIndices()
0256 {
0257     return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceIndices")));
0258 }
0259 
0260 bool FaceDbSchemaUpdater::createTriggers()
0261 {
0262     return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceTriggers")));
0263 }
0264 
0265 bool FaceDbSchemaUpdater::updateV1ToV2()
0266 {
0267 /*
0268     if (!d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction("UpdateDBSchemaFromV1ToV2")))
0269     {
0270         qError() << "Schema upgrade in DB from V1 to V2 failed!";
0271         return false;
0272     }
0273 */
0274 
0275     d->currentVersion         = 2;
0276     d->currentRequiredVersion = 1;
0277 
0278     return true;
0279 }
0280 
0281 bool FaceDbSchemaUpdater::updateV2ToV3()
0282 {
0283     d->currentVersion         = 3;
0284     d->currentRequiredVersion = 3;
0285     d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBFaceMatrices")));
0286 
0287     return true;
0288 }
0289 
0290 bool FaceDbSchemaUpdater::updateV3ToV4()
0291 {
0292     if (!(d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBFaceMatrices")))))
0293     {
0294         qCDebug(DIGIKAM_FACEDB_LOG) << "fail to recreate FaceMatrices table";
0295 
0296         return false;
0297     }
0298 
0299     if (!(d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBKDTree")))))
0300     {
0301         qCDebug(DIGIKAM_FACEDB_LOG) << "fail to create KDTree table";
0302 
0303         return false;
0304     }
0305 
0306     d->currentVersion         = 4;
0307     d->currentRequiredVersion = 4;
0308 
0309     // TODO: retrain recognized identities
0310 
0311     return true;
0312 }
0313 
0314 } // namespace Digikam