1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2007-04-16
7  * Description : Face database schema updater
8  *
9  * Copyright (C) 2007-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  * Copyright (C) 2010-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  * Copyright (C)      2020 by Nghia Duong <minhnghiaduong997 at gmail dot com>
12  *
13  * This program is free software; you can redistribute it
14  * and/or modify it under the terms of the GNU General
15  * Public License as published by the Free Software Foundation;
16  * either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #include "facedbschemaupdater.h"
27 
28 // KDE includes
29 
30 #include <klocalizedstring.h>
31 
32 // Local includes
33 
34 #include "digikam_debug.h"
35 #include "dbenginebackend.h"
36 #include "facedbaccess.h"
37 #include "facedb.h"
38 
39 namespace Digikam
40 {
41 
schemaVersion()42 int FaceDbSchemaUpdater::schemaVersion()
43 {
44     return 4;
45 }
46 
47 // -------------------------------------------------------------------------------------
48 
49 class Q_DECL_HIDDEN FaceDbSchemaUpdater::Private
50 {
51 public:
52 
Private()53     explicit Private()
54         : setError              (false),
55           currentVersion        (0),
56           currentRequiredVersion(0),
57           dbAccess              (nullptr),
58           observer              (nullptr)
59     {
60     }
61 
62     bool                    setError;
63 
64     int                     currentVersion;
65     int                     currentRequiredVersion;
66 
67     FaceDbAccess*           dbAccess;
68 
69     InitializationObserver* observer;
70 };
71 
FaceDbSchemaUpdater(FaceDbAccess * const dbAccess)72 FaceDbSchemaUpdater::FaceDbSchemaUpdater(FaceDbAccess* const dbAccess)
73     : d(new Private)
74 {
75     d->dbAccess = dbAccess;
76 }
77 
~FaceDbSchemaUpdater()78 FaceDbSchemaUpdater::~FaceDbSchemaUpdater()
79 {
80     delete d;
81 }
82 
setObserver(InitializationObserver * const observer)83 void FaceDbSchemaUpdater::setObserver(InitializationObserver* const observer)
84 {
85     d->observer = observer;
86 }
87 
update()88 bool FaceDbSchemaUpdater::update()
89 {
90     bool success = startUpdates();
91 
92     // even on failure, try to set current version - it may have incremented
93 
94     if (d->currentVersion)
95     {
96         d->dbAccess->db()->setSetting(QLatin1String("DBFaceVersion"), QString::number(d->currentVersion));
97     }
98 
99     if (d->currentRequiredVersion)
100     {
101         d->dbAccess->db()->setSetting(QLatin1String("DBFaceVersionRequired"), QString::number(d->currentRequiredVersion));
102     }
103 
104     return success;
105 }
106 
startUpdates()107 bool FaceDbSchemaUpdater::startUpdates()
108 {
109     // First step: do we have an empty database?
110 
111     QStringList tables = d->dbAccess->backend()->tables();
112 
113     if (tables.contains(QLatin1String("Identities"), Qt::CaseInsensitive))
114     {
115         // Find out schema version of db file
116 
117         QString version         = d->dbAccess->db()->setting(QLatin1String("DBFaceVersion"));
118         QString versionRequired = d->dbAccess->db()->setting(QLatin1String("DBFaceVersionRequired"));
119         qCDebug(DIGIKAM_FACEDB_LOG) << "Face database: have a structure version " << version;
120 
121         // mini schema update
122 
123         if (version.isEmpty() && d->dbAccess->parameters().isSQLite())
124         {
125             version = d->dbAccess->db()->setting(QLatin1String("DBVersion"));
126         }
127 
128         // We absolutely require the DBFaceVersion setting
129 
130         if (version.isEmpty())
131         {
132             // Something is damaged. Give up.
133 
134             qCWarning(DIGIKAM_FACEDB_LOG) << "DBFaceVersion not available! Giving up schema upgrading.";
135 
136             QString errorMsg = i18n("The database is not valid: "
137                                     "the \"DBFaceVersion\" setting does not exist. "
138                                     "The current database schema version cannot be verified. "
139                                     "Try to start with an empty database. ");
140 
141             d->dbAccess->setLastError(errorMsg);
142 
143             if (d->observer)
144             {
145                 d->observer->error(errorMsg);
146                 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
147             }
148 
149             return false;
150         }
151 
152         // current version describes the current state of the schema in the db,
153         // schemaVersion is the version required by the program.
154 
155         d->currentVersion = version.toInt();
156 
157         if (d->currentVersion > schemaVersion())
158         {
159             // trying to open a database with a more advanced than this FaceDbSchemaUpdater supports
160 
161             if (!versionRequired.isEmpty() && (versionRequired.toInt() <= schemaVersion()))
162             {
163                 // version required may be less than current version
164 
165                 return true;
166             }
167             else
168             {
169                 QString errorMsg = i18n("The database has been used with a more recent version of digiKam "
170                                         "and has been updated to a database schema which cannot be used with this version. "
171                                         "(This means this digiKam version is too old, or the database format is to recent.) "
172                                         "Please use the more recent version of digiKam that you used before.");
173 
174                 d->dbAccess->setLastError(errorMsg);
175 
176                 if (d->observer)
177                 {
178                     d->observer->error(errorMsg);
179                     d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
180                 }
181 
182                 return false;
183             }
184         }
185         else
186         {
187             return makeUpdates();
188         }
189     }
190     else
191     {
192         qCDebug(DIGIKAM_FACEDB_LOG) << "Face database: no database file available";
193 
194         DbEngineParameters parameters = d->dbAccess->parameters();
195 
196         // No legacy handling: start with a fresh db
197 
198         if (!createDatabase())
199         {
200             QString errorMsg = i18n("Failed to create tables in database.\n%1",
201                                     d->dbAccess->backend()->lastError());
202             d->dbAccess->setLastError(errorMsg);
203 
204             if (d->observer)
205             {
206                 d->observer->error(errorMsg);
207                 d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
208             }
209 
210             return false;
211         }
212 
213         return true;
214     }
215 }
216 
makeUpdates()217 bool FaceDbSchemaUpdater::makeUpdates()
218 {
219     if (d->currentVersion < schemaVersion())
220     {
221         if      (d->currentVersion == 1)
222         {
223             updateV1ToV2();
224         }
225         else if (d->currentVersion == 2)
226         {
227             updateV2ToV3();
228         }
229         else if (d->currentVersion == 3)
230         {
231             updateV3ToV4();
232         }
233     }
234 
235     return true;
236 }
237 
238 
createDatabase()239 bool FaceDbSchemaUpdater::createDatabase()
240 {
241     if (createTables() && createIndices() && createTriggers())
242     {
243         d->currentVersion         = schemaVersion();
244         d->currentRequiredVersion = 4;
245         return true;
246     }
247     else
248     {
249         return false;
250     }
251 }
252 
createTables()253 bool FaceDbSchemaUpdater::createTables()
254 {
255     // the creation order is important because of the foreign keys in MySQL
256 
257     return (
258             d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDB")))             &&
259             d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBFaceMatrices"))) &&
260             d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBKDTree")))
261            );
262 }
263 
createIndices()264 bool FaceDbSchemaUpdater::createIndices()
265 {
266     return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceIndices")));
267 }
268 
createTriggers()269 bool FaceDbSchemaUpdater::createTriggers()
270 {
271     return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceTriggers")));
272 }
273 
updateV1ToV2()274 bool FaceDbSchemaUpdater::updateV1ToV2()
275 {
276 /*
277     if (!d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction("UpdateDBSchemaFromV1ToV2")))
278     {
279         qError() << "Schema upgrade in DB from V1 to V2 failed!";
280         return false;
281     }
282 */
283 
284     d->currentVersion         = 2;
285     d->currentRequiredVersion = 1;
286 
287     return true;
288 }
289 
updateV2ToV3()290 bool FaceDbSchemaUpdater::updateV2ToV3()
291 {
292     d->currentVersion         = 3;
293     d->currentRequiredVersion = 3;
294     d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBFaceMatrices")));
295 
296     return true;
297 }
298 
updateV3ToV4()299 bool FaceDbSchemaUpdater::updateV3ToV4()
300 {
301     if (!(d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBFaceMatrices")))))
302     {
303         qCDebug(DIGIKAM_FACEDB_LOG) << "fail to recreate FaceMatrices table";
304 
305         return false;
306     }
307 
308     if (!(d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBKDTree")))))
309     {
310         qCDebug(DIGIKAM_FACEDB_LOG) << "fail to create KDTree table";
311 
312         return false;
313     }
314 
315     d->currentVersion         = 4;
316     d->currentRequiredVersion = 4;
317 
318     // TODO: retrain recognized identities
319 
320     return true;
321 }
322 
323 } // namespace Digikam
324