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