1 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
2 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
3 //
4 // SPDX-License-Identifier: GPL-2.0-or-later
5
6 #include "Database.h"
7
8 #include "DatabaseElement.h"
9
10 #include <kpabase/Logging.h>
11 #include <kpabase/UIDelegate.h>
12
13 #include <KLocalizedString>
14 #include <QCoreApplication>
15 #include <QDir>
16 #include <QFile>
17 #include <QSqlDatabase>
18 #include <QSqlDriver>
19 #include <QSqlError>
20 #include <QSqlQuery>
21 #include <exiv2/exif.hpp>
22 #include <exiv2/image.hpp>
23
24 using namespace Exif;
25
26 namespace
27 {
28 // schema version; bump it up whenever the database schema changes
29 constexpr int DB_VERSION = 3;
elements(int since=0)30 const Database::ElementList elements(int since = 0)
31 {
32 static Database::ElementList elms;
33 static int sinceDBVersion[DB_VERSION] {};
34
35 if (elms.count() == 0) {
36 elms.append(new RationalExifElement("Exif.Photo.FocalLength"));
37 elms.append(new RationalExifElement("Exif.Photo.ExposureTime"));
38
39 elms.append(new RationalExifElement("Exif.Photo.ApertureValue"));
40 elms.append(new RationalExifElement("Exif.Photo.FNumber"));
41 //elms.append( new RationalExifElement( "Exif.Photo.FlashEnergy" ) );
42
43 elms.append(new IntExifElement("Exif.Photo.Flash"));
44 elms.append(new IntExifElement("Exif.Photo.Contrast"));
45 elms.append(new IntExifElement("Exif.Photo.Sharpness"));
46 elms.append(new IntExifElement("Exif.Photo.Saturation"));
47 elms.append(new IntExifElement("Exif.Image.Orientation"));
48 elms.append(new IntExifElement("Exif.Photo.MeteringMode"));
49 elms.append(new IntExifElement("Exif.Photo.ISOSpeedRatings"));
50 elms.append(new IntExifElement("Exif.Photo.ExposureProgram"));
51
52 elms.append(new StringExifElement("Exif.Image.Make"));
53 elms.append(new StringExifElement("Exif.Image.Model"));
54 // gps info has been added in database schema version 2:
55 sinceDBVersion[1] = elms.size();
56 elms.append(new IntExifElement("Exif.GPSInfo.GPSVersionID")); // actually a byte value
57 elms.append(new RationalExifElement("Exif.GPSInfo.GPSAltitude"));
58 elms.append(new IntExifElement("Exif.GPSInfo.GPSAltitudeRef")); // actually a byte value
59 elms.append(new StringExifElement("Exif.GPSInfo.GPSMeasureMode"));
60 elms.append(new RationalExifElement("Exif.GPSInfo.GPSDOP"));
61 elms.append(new RationalExifElement("Exif.GPSInfo.GPSImgDirection"));
62 elms.append(new RationalExifElement("Exif.GPSInfo.GPSLatitude"));
63 elms.append(new StringExifElement("Exif.GPSInfo.GPSLatitudeRef"));
64 elms.append(new RationalExifElement("Exif.GPSInfo.GPSLongitude"));
65 elms.append(new StringExifElement("Exif.GPSInfo.GPSLongitudeRef"));
66 elms.append(new RationalExifElement("Exif.GPSInfo.GPSTimeStamp"));
67 // lens info has been added in database schema version 3:
68 sinceDBVersion[2] = elms.size();
69 elms.append(new LensExifElement());
70 }
71
72 // query only for the newly added stuff:
73 if (since > 0)
74 return elms.mid(sinceDBVersion[since]);
75
76 return elms;
77 }
78
isSQLiteDriverAvailable()79 bool isSQLiteDriverAvailable()
80 {
81 #ifdef QT_NO_SQL
82 return false;
83 #else
84 return QSqlDatabase::isDriverAvailable(QString::fromLatin1("QSQLITE"));
85 #endif
86 }
87 }
88
89 class Database::DatabasePrivate
90 {
91 public:
92 DatabasePrivate(Database *q, const QString &exifDBFile, DB::UIDelegate &uiDelegate);
93 ~DatabasePrivate();
94
95 bool isOpen() const;
96 bool isUsable() const;
97 int DBFileVersion() const;
98
99 QString getFileName() const;
100
101 protected:
102 Database *q_ptr;
103 Q_DECLARE_PUBLIC(Database)
104
105 enum DBSchemaChangeType { SchemaChanged,
106 SchemaAndDataChanged };
107 void openDatabase();
108 void populateDatabase();
109 void updateDatabase();
110 void createMetadataTable(DBSchemaChangeType change);
111 static QString connectionName();
112 bool insert(const DB::FileName &filename, Exiv2::ExifData);
113 bool insert(const QList<DBExifInfo> &);
114
115 private:
116 mutable bool m_isFailed = false;
117 DB::UIDelegate &m_ui;
118 QSqlDatabase m_db;
119 const QString m_fileName;
120 bool m_isOpen = false;
121 bool m_doUTF8Conversion = false;
122 QSqlQuery *m_insertTransaction = nullptr;
123 QString m_queryString;
124
125 void showErrorAndFail(QSqlQuery &query) const;
126 void showErrorAndFail(const QString &errorMessage, const QString &technicalInfo) const;
127 void init();
128 QSqlQuery *getInsertQuery();
129 void concludeInsertQuery(QSqlQuery *);
130 };
131
DatabasePrivate(Database * q,const QString & exifDBFile,DB::UIDelegate & uiDelegate)132 Database::DatabasePrivate::DatabasePrivate(Database *q, const QString &exifDBFile, DB::UIDelegate &uiDelegate)
133 : q_ptr(q)
134 , m_ui(uiDelegate)
135 , m_db(QSqlDatabase::addDatabase(QString::fromLatin1("QSQLITE"), QString::fromLatin1("exif")))
136 , m_fileName(exifDBFile)
137 {
138 init();
139 }
140
init()141 void Exif::Database::DatabasePrivate::init()
142 {
143 Q_Q(Database);
144 if (!q->isAvailable())
145 return;
146
147 m_isFailed = false;
148 m_insertTransaction = nullptr;
149 const bool dbExists = QFile::exists(m_fileName);
150
151 openDatabase();
152
153 if (!isOpen())
154 return;
155
156 if (!dbExists)
157 populateDatabase();
158 else
159 updateDatabase();
160 }
161
162 /**
163 * @brief show and error message for the failed \p query and disable the Exif database.
164 * The database is closed because at this point we can not trust the data inside.
165 * @param query
166 */
showErrorAndFail(QSqlQuery & query) const167 void Database::DatabasePrivate::showErrorAndFail(QSqlQuery &query) const
168 {
169 const QString txt = i18n("<p>There was an error while accessing the Exif search database. "
170 "The error is likely due to a broken database file.</p>"
171 "<p>To fix this problem run Maintenance->Recreate Exif Search database.</p>"
172 "<hr/>"
173 "<p>For debugging: the command that was attempted to be executed was:<br/>%1</p>"
174 "<p>The error message obtained was:<br/>%2</p>",
175 query.lastQuery(), query.lastError().text());
176
177 const QString technicalInfo = QString::fromUtf8("Error running query: %1\n Error was: %2")
178 .arg(query.lastQuery(), query.lastError().text());
179 showErrorAndFail(txt, technicalInfo);
180 }
181
showErrorAndFail(const QString & errorMessage,const QString & technicalInfo) const182 void Database::DatabasePrivate::showErrorAndFail(const QString &errorMessage, const QString &technicalInfo) const
183 {
184 m_ui.information(DB::LogMessage { ExifLog(), technicalInfo }, errorMessage, i18n("Error in Exif database"), QString::fromLatin1("sql_error_in_exif_DB"));
185 // disable exif db for now:
186 m_isFailed = true;
187 }
188
Database(const QString & sqliteFileName,DB::UIDelegate & uiDelegate)189 Exif::Database::Database(const QString &sqliteFileName, DB::UIDelegate &uiDelegate)
190 : d_ptr(new DatabasePrivate(this, sqliteFileName, uiDelegate))
191 {
192 }
193
~Database()194 Database::~Database()
195 {
196 delete d_ptr;
197 }
198
openDatabase()199 void Exif::Database::DatabasePrivate::openDatabase()
200 {
201 m_db.setDatabaseName(m_fileName);
202
203 m_isOpen = m_db.open();
204 if (!m_isOpen) {
205 const QString txt = i18n("<p>There was an error while opening the Exif search database.</p> "
206 "<p>To fix this problem run Maintenance->Recreate Exif Search database.</p>"
207 "<hr/>"
208 "<p>The error message obtained was:<br/>%1</p>",
209 m_db.lastError().text());
210 const QString logMsg = QString::fromUtf8("Could not open Exif search database! "
211 "Error was: %1")
212 .arg(m_db.lastError().text());
213 showErrorAndFail(txt, logMsg);
214 return;
215 }
216 // If SQLite in Qt has Unicode feature, it will convert queries to
217 // UTF-8 automatically. Otherwise we should do the conversion to
218 // be able to store any Unicode character.
219 m_doUTF8Conversion = !m_db.driver()->hasFeature(QSqlDriver::Unicode);
220 }
221
~DatabasePrivate()222 Exif::Database::DatabasePrivate::~DatabasePrivate()
223 {
224 // We have to close the database before destroying the QSqlDatabase object,
225 // otherwise Qt screams and kittens might die (see QSqlDatabase's
226 // documentation)
227 if (m_db.isOpen())
228 m_db.close();
229 }
230
isOpen() const231 bool Exif::Database::DatabasePrivate::isOpen() const
232 {
233 return m_isOpen && !m_isFailed;
234 }
235
populateDatabase()236 void Exif::Database::DatabasePrivate::populateDatabase()
237 {
238 createMetadataTable(SchemaAndDataChanged);
239 QStringList attributes;
240 const auto allElements = elements();
241 for (const DatabaseElement *element : allElements) {
242 attributes.append(element->createString());
243 }
244
245 QSqlQuery query(QString::fromLatin1("create table if not exists exif (filename string PRIMARY KEY, %1 )")
246 .arg(attributes.join(QString::fromLatin1(", "))),
247 m_db);
248 if (!query.exec())
249 showErrorAndFail(query);
250 }
251
updateDatabase()252 void Exif::Database::DatabasePrivate::updateDatabase()
253 {
254 if (m_db.tables().isEmpty()) {
255 const QString txt = i18n("<p>The Exif search database is corrupted and has no data.</p> "
256 "<p>To fix this problem run Maintenance->Recreate Exif Search database.</p>");
257 const QString logMsg = QString::fromUtf8("Database open but empty!");
258 showErrorAndFail(txt, logMsg);
259 return;
260 }
261
262 const int version = DBFileVersion();
263 if (m_isFailed)
264 return;
265 if (version < DBVersion()) {
266 // on the next update, we can just query the DB Version
267 createMetadataTable(SchemaChanged);
268 }
269 // update schema
270 if (version < DBVersion()) {
271 QSqlQuery query(m_db);
272 for (const DatabaseElement *e : elements(version)) {
273 query.prepare(QString::fromLatin1("alter table exif add column %1")
274 .arg(e->createString()));
275 if (!query.exec())
276 showErrorAndFail(query);
277 }
278 }
279 }
280
createMetadataTable(DBSchemaChangeType change)281 void Exif::Database::DatabasePrivate::createMetadataTable(DBSchemaChangeType change)
282 {
283 QSqlQuery query(m_db);
284 query.prepare(QString::fromLatin1("create table if not exists settings (keyword TEXT PRIMARY KEY, value TEXT) without rowid"));
285 if (!query.exec()) {
286 showErrorAndFail(query);
287 return;
288 }
289
290 query.prepare(QString::fromLatin1("insert or replace into settings (keyword, value) values('DBVersion','%1')").arg(Database::DBVersion()));
291 if (!query.exec()) {
292 showErrorAndFail(query);
293 return;
294 }
295
296 if (change == SchemaAndDataChanged) {
297 query.prepare(QString::fromLatin1("insert or replace into settings (keyword, value) values('GuaranteedDataVersion','%1')").arg(Database::DBVersion()));
298 if (!query.exec())
299 showErrorAndFail(query);
300 }
301 }
302
add(const DB::FileName & filename,Exiv2::ExifData data)303 bool Database::add(const DB::FileName &filename, Exiv2::ExifData data)
304 {
305 if (!isUsable())
306 return false;
307
308 Q_D(Database);
309 // we might as well rename insert() to add()
310 return d->insert(filename, data);
311 }
312
add(const DB::FileName & fileName)313 bool Exif::Database::add(const DB::FileName &fileName)
314 {
315 if (!isUsable())
316 return false;
317
318 try {
319 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(fileName.absolute().toLocal8Bit().data());
320 Q_ASSERT(image.get() != nullptr);
321 image->readMetadata();
322 Exiv2::ExifData &exifData = image->exifData();
323 Q_D(Database);
324 return d->insert(fileName, exifData);
325 } catch (...) {
326 qCWarning(ExifLog, "Error while reading exif information from %s", qPrintable(fileName.absolute()));
327 return false;
328 }
329 }
330
add(const DB::FileNameList & list)331 bool Exif::Database::add(const DB::FileNameList &list)
332 {
333 if (!isUsable())
334 return false;
335
336 QList<DBExifInfo> map;
337
338 for (const DB::FileName &fileName : list) {
339 try {
340 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(fileName.absolute().toLocal8Bit().data());
341 Q_ASSERT(image.get() != nullptr);
342 image->readMetadata();
343 map << DBExifInfo(fileName, image->exifData());
344 } catch (...) {
345 qCWarning(ExifLog, "Error while reading exif information from %s", qPrintable(fileName.absolute()));
346 }
347 }
348 Q_D(Database);
349 d->insert(map);
350 return true;
351 }
352
remove(const DB::FileName & fileName)353 void Exif::Database::remove(const DB::FileName &fileName)
354 {
355 if (!isUsable())
356 return;
357
358 Q_D(Database);
359 QSqlQuery query(d->m_db);
360 query.prepare(QString::fromLatin1("DELETE FROM exif WHERE fileName=?"));
361 query.bindValue(0, fileName.absolute());
362 if (!query.exec())
363 d->showErrorAndFail(query);
364 }
365
remove(const DB::FileNameList & list)366 void Exif::Database::remove(const DB::FileNameList &list)
367 {
368 if (!isUsable())
369 return;
370
371 Q_D(Database);
372 d->m_db.transaction();
373 QSqlQuery query(d->m_db);
374 query.prepare(QString::fromLatin1("DELETE FROM exif WHERE fileName=?"));
375 for (const DB::FileName &fileName : list) {
376 query.bindValue(0, fileName.absolute());
377 if (!query.exec()) {
378 d->m_db.rollback();
379 d->showErrorAndFail(query);
380 return;
381 }
382 }
383 d->m_db.commit();
384 }
385
getInsertQuery()386 QSqlQuery *Exif::Database::DatabasePrivate::getInsertQuery()
387 {
388 if (!isUsable())
389 return nullptr;
390 if (m_insertTransaction)
391 return m_insertTransaction;
392 if (m_queryString.isEmpty()) {
393 QStringList formalList;
394 const Database::ElementList elms = elements();
395 for (const DatabaseElement *e : elms) {
396 formalList.append(e->queryString());
397 }
398 m_queryString = QString::fromLatin1("INSERT OR REPLACE into exif values (?, %1) ").arg(formalList.join(QString::fromLatin1(", ")));
399 }
400 QSqlQuery *query = new QSqlQuery(m_db);
401 if (query)
402 query->prepare(m_queryString);
403 return query;
404 }
405
concludeInsertQuery(QSqlQuery * query)406 void Exif::Database::DatabasePrivate::concludeInsertQuery(QSqlQuery *query)
407 {
408 if (m_insertTransaction)
409 return;
410 m_db.commit();
411 delete query;
412 }
413
startInsertTransaction()414 bool Exif::Database::startInsertTransaction()
415 {
416 if (!isUsable())
417 return false;
418
419 Q_D(Database);
420 Q_ASSERT(d->m_insertTransaction == nullptr);
421 d->m_insertTransaction = d->getInsertQuery();
422 d->m_db.transaction();
423 return (d->m_insertTransaction != nullptr);
424 }
425
commitInsertTransaction()426 bool Exif::Database::commitInsertTransaction()
427 {
428 if (!isUsable())
429 return false;
430
431 Q_D(Database);
432 if (d->m_insertTransaction) {
433 d->m_db.commit();
434 delete d->m_insertTransaction;
435 d->m_insertTransaction = nullptr;
436 } else
437 qCWarning(ExifLog, "Trying to commit transaction, but no transaction is active!");
438 return true;
439 }
440
abortInsertTransaction()441 bool Exif::Database::abortInsertTransaction()
442 {
443 if (!isUsable())
444 return false;
445
446 Q_D(Database);
447 if (d->m_insertTransaction) {
448 d->m_db.rollback();
449 delete d->m_insertTransaction;
450 d->m_insertTransaction = nullptr;
451 } else
452 qCWarning(ExifLog, "Trying to abort transaction, but no transaction is active!");
453 return true;
454 }
455
insert(const DB::FileName & filename,Exiv2::ExifData data)456 bool Exif::Database::DatabasePrivate::insert(const DB::FileName &filename, Exiv2::ExifData data)
457 {
458 if (!isUsable())
459 return false;
460
461 QSqlQuery *query = getInsertQuery();
462 query->bindValue(0, filename.absolute());
463 int i = 1;
464 for (const DatabaseElement *e : elements()) {
465 query->bindValue(i++, e->valueFromExif(data));
466 }
467
468 bool status = query->exec();
469 if (!status)
470 showErrorAndFail(*query);
471 concludeInsertQuery(query);
472 return status;
473 }
474
insert(const QList<DBExifInfo> & map)475 bool Exif::Database::DatabasePrivate::insert(const QList<DBExifInfo> &map)
476 {
477 if (!isUsable())
478 return false;
479
480 QSqlQuery *query = getInsertQuery();
481 // not a const reference because DatabaseElement::valueFromExif uses operator[] on the exif datum
482 for (DBExifInfo elt : map) {
483 query->bindValue(0, elt.first.absolute());
484 int i = 1;
485 for (const DatabaseElement *e : elements()) {
486 query->bindValue(i++, e->valueFromExif(elt.second));
487 }
488
489 if (!query->exec()) {
490 showErrorAndFail(*query);
491 }
492 }
493 concludeInsertQuery(query);
494 return true;
495 }
496
getFileName() const497 QString Exif::Database::DatabasePrivate::getFileName() const
498 {
499 return m_fileName;
500 }
501
isAvailable()502 bool Exif::Database::isAvailable()
503 {
504 return isSQLiteDriverAvailable();
505 }
506
DBFileVersion() const507 int Exif::Database::DatabasePrivate::DBFileVersion() const
508 {
509 // previous to KPA 4.6, there was no metadata table:
510 if (!m_db.tables().contains(QString::fromLatin1("settings")))
511 return 1;
512
513 QSqlQuery query(QString::fromLatin1("SELECT value FROM settings WHERE keyword = 'DBVersion'"), m_db);
514 if (!query.exec())
515 showErrorAndFail(query);
516
517 if (query.first()) {
518 return query.value(0).toInt();
519 }
520 return 0;
521 }
522
DBFileVersion() const523 int Exif::Database::DBFileVersion() const
524 {
525 Q_D(const Database);
526 return d->DBFileVersion();
527 }
528
DBFileVersionGuaranteed() const529 int Exif::Database::DBFileVersionGuaranteed() const
530 {
531 Q_D(const Database);
532
533 // previous to KPA 4.6, there was no metadata table:
534 if (!d->m_db.tables().contains(QString::fromLatin1("settings")))
535 return 0;
536
537 QSqlQuery query(QString::fromLatin1("SELECT value FROM settings WHERE keyword = 'GuaranteedDataVersion'"), d->m_db);
538 if (!query.exec())
539 d->showErrorAndFail(query);
540
541 if (query.first()) {
542 return query.value(0).toInt();
543 }
544 return 0;
545 }
546
DBVersion()547 int Exif::Database::DBVersion()
548 {
549 return DB_VERSION;
550 }
551
isUsable() const552 bool Exif::Database::DatabasePrivate::isUsable() const
553 {
554 return isSQLiteDriverAvailable() && isOpen();
555 }
isUsable() const556 bool Exif::Database::isUsable() const
557 {
558 Q_D(const Database);
559 return d->isUsable();
560 }
561
readFields(const DB::FileName & fileName,ElementList & fields) const562 bool Exif::Database::readFields(const DB::FileName &fileName, ElementList &fields) const
563 {
564 if (!isUsable())
565 return false;
566
567 bool foundIt = false;
568 QStringList fieldList;
569 for (const DatabaseElement *e : fields) {
570 fieldList.append(e->columnName());
571 }
572
573 Q_D(const Database);
574 QSqlQuery query(d->m_db);
575 // the query returns a single value, so we don't need the overhead for random access:
576 query.setForwardOnly(true);
577
578 query.prepare(QString::fromLatin1("select %1 from exif where filename=?")
579 .arg(fieldList.join(QString::fromLatin1(", "))));
580 query.bindValue(0, fileName.absolute());
581
582 if (!query.exec()) {
583 d->showErrorAndFail(query);
584 }
585 if (query.next()) {
586 // file in exif db -> write back results
587 int i = 0;
588 for (DatabaseElement *e : fields) {
589 e->setValue(query.value(i++));
590 }
591 foundIt = true;
592 }
593 return foundIt;
594 }
595
filesMatchingQuery(const QString & queryStr) const596 DB::FileNameSet Exif::Database::filesMatchingQuery(const QString &queryStr) const
597 {
598 if (!isUsable())
599 return DB::FileNameSet();
600
601 DB::FileNameSet result;
602 Q_D(const Database);
603 QSqlQuery query(queryStr, d->m_db);
604
605 if (!query.exec())
606 d->showErrorAndFail(query);
607
608 else {
609 if (d->m_doUTF8Conversion)
610 while (query.next())
611 result.insert(DB::FileName::fromAbsolutePath(QString::fromUtf8(query.value(0).toByteArray())));
612 else
613 while (query.next())
614 result.insert(DB::FileName::fromAbsolutePath(query.value(0).toString()));
615 }
616
617 return result;
618 }
619
cameras() const620 QList<QPair<QString, QString>> Exif::Database::cameras() const
621 {
622 QList<QPair<QString, QString>> result;
623
624 if (!isUsable())
625 return result;
626
627 Q_D(const Database);
628 QSqlQuery query(QString::fromLatin1("SELECT DISTINCT Exif_Image_Make, Exif_Image_Model FROM exif"), d->m_db);
629 if (!query.exec()) {
630 d->showErrorAndFail(query);
631 } else {
632 while (query.next()) {
633 QString make = query.value(0).toString();
634 QString model = query.value(1).toString();
635 if (!make.isEmpty() && !model.isEmpty())
636 result.append(qMakePair(make, model));
637 }
638 }
639
640 return result;
641 }
642
lenses() const643 QList<QString> Exif::Database::lenses() const
644 {
645 QList<QString> result;
646
647 if (!isUsable())
648 return result;
649
650 Q_D(const Database);
651 QSqlQuery query(QString::fromLatin1("SELECT DISTINCT Exif_Photo_LensModel FROM exif"), d->m_db);
652 if (!query.exec()) {
653 d->showErrorAndFail(query);
654 } else {
655 while (query.next()) {
656 QString lens = query.value(0).toString();
657 if (!lens.isEmpty())
658 result.append(lens);
659 }
660 }
661
662 return result;
663 }
664
size() const665 int Database::size() const
666 {
667 if (!isUsable())
668 return 0;
669
670 Q_D(const Database);
671 QSqlQuery query(QLatin1String("SELECT count(*) FROM exif"), d->m_db);
672 int result = 0;
673 if (!query.exec()) {
674 d->showErrorAndFail(query);
675 } else {
676 if (query.first()) {
677 result = query.value(0).toInt();
678 }
679 }
680 return result;
681 }
682
recreate(const DB::FileNameList & allImageFiles,DB::AbstractProgressIndicator & progressIndicator)683 void Exif::Database::recreate(const DB::FileNameList &allImageFiles, DB::AbstractProgressIndicator &progressIndicator)
684 {
685 progressIndicator.setMinimum(0);
686 progressIndicator.setMaximum(allImageFiles.size());
687
688 Q_D(Database);
689 // We create a backup of the current database in case
690 // the user presse 'cancel' or there is any error. In that case
691 // we want to go back to the original DB.
692
693 const QString origBackup = d->getFileName() + QLatin1String(".bak");
694 d->m_db.close();
695
696 QDir().remove(origBackup);
697 QDir().rename(d->getFileName(), origBackup);
698 d->init();
699
700 // using a transaction here removes a *huge* overhead on the insert statements
701 startInsertTransaction();
702 int i = 0;
703 for (const auto &fileName : allImageFiles) {
704 progressIndicator.setValue(i++);
705 add(fileName);
706 if (i % 10) {
707 auto app = QCoreApplication::instance();
708 if (app)
709 app->processEvents();
710 }
711 if (progressIndicator.wasCanceled())
712 break;
713 }
714
715 // PENDING(blackie) We should count the amount of files that did not succeeded and warn the user.
716 if (progressIndicator.wasCanceled()) {
717 abortInsertTransaction();
718 d->m_db.close();
719 QDir().remove(d->getFileName());
720 QDir().rename(origBackup, d->getFileName());
721 d->init();
722 } else {
723 commitInsertTransaction();
724 QDir().remove(origBackup);
725 }
726 }
727
728 // vi:expandtab:tabstop=4 shiftwidth=4:
729