File indexing completed on 2024-05-12 15:55:36

0001 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2021-2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 // SPDX-FileCopyrightText: 2023 Tobias Leupold <tl at stonemx dot de>
0004 //
0005 // SPDX-License-Identifier: GPL-2.0-or-later
0006 
0007 #include "Database.h"
0008 
0009 #include "DatabaseElement.h"
0010 
0011 #include <kpabase/Logging.h>
0012 #include <kpabase/UIDelegate.h>
0013 
0014 #include <KLocalizedString>
0015 #include <QCoreApplication>
0016 #include <QDir>
0017 #include <QFile>
0018 #include <QSqlDatabase>
0019 #include <QSqlDriver>
0020 #include <QSqlError>
0021 #include <QSqlQuery>
0022 #include <exiv2/exif.hpp>
0023 #include <exiv2/image.hpp>
0024 
0025 using namespace Exif;
0026 
0027 namespace
0028 {
0029 // schema version; bump it up whenever the database schema changes
0030 constexpr int DB_VERSION = 3;
0031 const Database::ElementList elements(int since = 0)
0032 {
0033     static Database::ElementList elms;
0034     static int sinceDBVersion[DB_VERSION] {};
0035 
0036     if (elms.count() == 0) {
0037         elms.append(new RationalExifElement("Exif.Photo.FocalLength"));
0038         elms.append(new RationalExifElement("Exif.Photo.ExposureTime"));
0039 
0040         elms.append(new RationalExifElement("Exif.Photo.ApertureValue"));
0041         elms.append(new RationalExifElement("Exif.Photo.FNumber"));
0042         // elms.append( new RationalExifElement( "Exif.Photo.FlashEnergy" ) );
0043 
0044         elms.append(new IntExifElement("Exif.Photo.Flash"));
0045         elms.append(new IntExifElement("Exif.Photo.Contrast"));
0046         elms.append(new IntExifElement("Exif.Photo.Sharpness"));
0047         elms.append(new IntExifElement("Exif.Photo.Saturation"));
0048         elms.append(new IntExifElement("Exif.Image.Orientation"));
0049         elms.append(new IntExifElement("Exif.Photo.MeteringMode"));
0050         elms.append(new IntExifElement("Exif.Photo.ISOSpeedRatings"));
0051         elms.append(new IntExifElement("Exif.Photo.ExposureProgram"));
0052 
0053         elms.append(new StringExifElement("Exif.Image.Make"));
0054         elms.append(new StringExifElement("Exif.Image.Model"));
0055         // gps info has been added in database schema version 2:
0056         sinceDBVersion[1] = elms.size();
0057         elms.append(new IntExifElement("Exif.GPSInfo.GPSVersionID")); // actually a byte value
0058         elms.append(new RationalExifElement("Exif.GPSInfo.GPSAltitude"));
0059         elms.append(new IntExifElement("Exif.GPSInfo.GPSAltitudeRef")); // actually a byte value
0060         elms.append(new StringExifElement("Exif.GPSInfo.GPSMeasureMode"));
0061         elms.append(new RationalExifElement("Exif.GPSInfo.GPSDOP"));
0062         elms.append(new RationalExifElement("Exif.GPSInfo.GPSImgDirection"));
0063         elms.append(new RationalExifElement("Exif.GPSInfo.GPSLatitude"));
0064         elms.append(new StringExifElement("Exif.GPSInfo.GPSLatitudeRef"));
0065         elms.append(new RationalExifElement("Exif.GPSInfo.GPSLongitude"));
0066         elms.append(new StringExifElement("Exif.GPSInfo.GPSLongitudeRef"));
0067         elms.append(new RationalExifElement("Exif.GPSInfo.GPSTimeStamp"));
0068         // lens info has been added in database schema version 3:
0069         sinceDBVersion[2] = elms.size();
0070         elms.append(new LensExifElement());
0071     }
0072 
0073     // query only for the newly added stuff:
0074     if (since > 0)
0075         return elms.mid(sinceDBVersion[since]);
0076 
0077     return elms;
0078 }
0079 
0080 bool isSQLiteDriverAvailable()
0081 {
0082 #ifdef QT_NO_SQL
0083     return false;
0084 #else
0085     return QSqlDatabase::isDriverAvailable(QString::fromLatin1("QSQLITE"));
0086 #endif
0087 }
0088 
0089 constexpr QFileDevice::Permissions FILE_PERMISSIONS { QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOther };
0090 }
0091 
0092 class Database::DatabasePrivate
0093 {
0094 public:
0095     DatabasePrivate(Database *q, const QString &exifDBFile, DB::UIDelegate &uiDelegate);
0096     ~DatabasePrivate();
0097 
0098     bool isOpen() const;
0099     bool isUsable() const;
0100     int DBFileVersion() const;
0101 
0102     QString getFileName() const;
0103 
0104 protected:
0105     Database *q_ptr;
0106     Q_DECLARE_PUBLIC(Database)
0107 
0108     enum DBSchemaChangeType { SchemaChanged,
0109                               SchemaAndDataChanged };
0110     void openDatabase();
0111     void populateDatabase();
0112     void updateDatabase();
0113     void createMetadataTable(DBSchemaChangeType change);
0114     static QString connectionName();
0115     bool insert(const DB::FileName &filename, Exiv2::ExifData);
0116     bool insert(const QList<DBExifInfo> &);
0117 
0118 private:
0119     mutable bool m_isFailed = false;
0120     DB::UIDelegate &m_ui;
0121     QSqlDatabase m_db;
0122     const QString m_fileName;
0123     bool m_isOpen = false;
0124     bool m_doUTF8Conversion = false;
0125     QSqlQuery *m_insertTransaction = nullptr;
0126     QString m_queryString;
0127 
0128     void showErrorAndFail(QSqlQuery &query) const;
0129     void showErrorAndFail(const QString &errorMessage, const QString &technicalInfo) const;
0130     void init();
0131     QSqlQuery *getInsertQuery();
0132     void concludeInsertQuery(QSqlQuery *);
0133 };
0134 
0135 Database::DatabasePrivate::DatabasePrivate(Database *q, const QString &exifDBFile, DB::UIDelegate &uiDelegate)
0136     : q_ptr(q)
0137     , m_ui(uiDelegate)
0138     , m_db(QSqlDatabase::addDatabase(QString::fromLatin1("QSQLITE"), QString::fromLatin1("exif")))
0139     , m_fileName(exifDBFile)
0140 {
0141     init();
0142 }
0143 
0144 void Exif::Database::DatabasePrivate::init()
0145 {
0146     Q_Q(Database);
0147     if (!q->isAvailable())
0148         return;
0149 
0150     m_isFailed = false;
0151     m_insertTransaction = nullptr;
0152     const bool dbExists = QFile::exists(m_fileName);
0153 
0154     openDatabase();
0155 
0156     if (!isOpen())
0157         return;
0158 
0159     if (!dbExists) {
0160         QFile::setPermissions(m_fileName, FILE_PERMISSIONS);
0161         populateDatabase();
0162     } else
0163         updateDatabase();
0164 }
0165 
0166 /**
0167  * @brief show and error message for the failed \p query and disable the Exif database.
0168  * The database is closed because at this point we can not trust the data inside.
0169  * @param query
0170  */
0171 void Database::DatabasePrivate::showErrorAndFail(QSqlQuery &query) const
0172 {
0173     const QString txt = i18n("<p>There was an error while accessing the Exif search database. "
0174                              "The error is likely due to a broken database file.</p>"
0175                              "<p>To fix this problem run Maintenance->Recreate Exif Search database.</p>"
0176                              "<hr/>"
0177                              "<p>For debugging: the command that was attempted to be executed was:<br/>%1</p>"
0178                              "<p>The error message obtained was:<br/>%2</p>",
0179                              query.lastQuery(), query.lastError().text());
0180 
0181     const QString technicalInfo = QString::fromUtf8("Error running query: %1\n Error was: %2")
0182                                       .arg(query.lastQuery(), query.lastError().text());
0183     showErrorAndFail(txt, technicalInfo);
0184 }
0185 
0186 void Database::DatabasePrivate::showErrorAndFail(const QString &errorMessage, const QString &technicalInfo) const
0187 {
0188     m_ui.information(DB::LogMessage { ExifLog(), technicalInfo }, errorMessage, i18n("Error in Exif database"), QString::fromLatin1("sql_error_in_exif_DB"));
0189     // disable exif db for now:
0190     m_isFailed = true;
0191 }
0192 
0193 Exif::Database::Database(const QString &sqliteFileName, DB::UIDelegate &uiDelegate)
0194     : d_ptr(new DatabasePrivate(this, sqliteFileName, uiDelegate))
0195 {
0196 }
0197 
0198 Database::~Database()
0199 {
0200     delete d_ptr;
0201 }
0202 
0203 void Exif::Database::DatabasePrivate::openDatabase()
0204 {
0205     m_db.setDatabaseName(m_fileName);
0206 
0207     m_isOpen = m_db.open();
0208     if (!m_isOpen) {
0209         const QString txt = i18n("<p>There was an error while opening the Exif search database.</p> "
0210                                  "<p>To fix this problem run Maintenance->Recreate Exif Search database.</p>"
0211                                  "<hr/>"
0212                                  "<p>The error message obtained was:<br/>%1</p>",
0213                                  m_db.lastError().text());
0214         const QString logMsg = QString::fromUtf8("Could not open Exif search database! "
0215                                                  "Error was: %1")
0216                                    .arg(m_db.lastError().text());
0217         showErrorAndFail(txt, logMsg);
0218         return;
0219     }
0220     // If SQLite in Qt has Unicode feature, it will convert queries to
0221     // UTF-8 automatically. Otherwise we should do the conversion to
0222     // be able to store any Unicode character.
0223     m_doUTF8Conversion = !m_db.driver()->hasFeature(QSqlDriver::Unicode);
0224 }
0225 
0226 Exif::Database::DatabasePrivate::~DatabasePrivate()
0227 {
0228     // We have to close the database before destroying the QSqlDatabase object,
0229     // otherwise Qt screams and kittens might die (see QSqlDatabase's
0230     // documentation)
0231     if (m_db.isOpen())
0232         m_db.close();
0233 }
0234 
0235 bool Exif::Database::DatabasePrivate::isOpen() const
0236 {
0237     return m_isOpen && !m_isFailed;
0238 }
0239 
0240 void Exif::Database::DatabasePrivate::populateDatabase()
0241 {
0242     createMetadataTable(SchemaAndDataChanged);
0243     QStringList attributes;
0244     const auto allElements = elements();
0245     for (const DatabaseElement *element : allElements) {
0246         attributes.append(element->createString());
0247     }
0248 
0249     QSqlQuery query(QString::fromLatin1("create table if not exists exif (filename string PRIMARY KEY, %1 )")
0250                         .arg(attributes.join(QString::fromLatin1(", "))),
0251                     m_db);
0252     if (!query.exec())
0253         showErrorAndFail(query);
0254 }
0255 
0256 void Exif::Database::DatabasePrivate::updateDatabase()
0257 {
0258     if (m_db.tables().isEmpty()) {
0259         const QString txt = i18n("<p>The Exif search database is corrupted and has no data.</p> "
0260                                  "<p>To fix this problem run Maintenance->Recreate Exif Search database.</p>");
0261         const QString logMsg = QString::fromUtf8("Database open but empty!");
0262         showErrorAndFail(txt, logMsg);
0263         return;
0264     }
0265 
0266     const int version = DBFileVersion();
0267     if (m_isFailed)
0268         return;
0269     if (version < DBVersion()) {
0270         // on the next update, we can just query the DB Version
0271         createMetadataTable(SchemaChanged);
0272     }
0273     // update schema
0274     if (version < DBVersion()) {
0275         QSqlQuery query(m_db);
0276         for (const DatabaseElement *e : elements(version)) {
0277             query.prepare(QString::fromLatin1("alter table exif add column %1")
0278                               .arg(e->createString()));
0279             if (!query.exec())
0280                 showErrorAndFail(query);
0281         }
0282     }
0283 }
0284 
0285 void Exif::Database::DatabasePrivate::createMetadataTable(DBSchemaChangeType change)
0286 {
0287     QSqlQuery query(m_db);
0288     query.prepare(QString::fromLatin1("create table if not exists settings (keyword TEXT PRIMARY KEY, value TEXT) without rowid"));
0289     if (!query.exec()) {
0290         showErrorAndFail(query);
0291         return;
0292     }
0293 
0294     query.prepare(QString::fromLatin1("insert or replace into settings (keyword, value) values('DBVersion','%1')").arg(Database::DBVersion()));
0295     if (!query.exec()) {
0296         showErrorAndFail(query);
0297         return;
0298     }
0299 
0300     if (change == SchemaAndDataChanged) {
0301         query.prepare(QString::fromLatin1("insert or replace into settings (keyword, value) values('GuaranteedDataVersion','%1')").arg(Database::DBVersion()));
0302         if (!query.exec())
0303             showErrorAndFail(query);
0304     }
0305 }
0306 
0307 bool Database::add(const DB::FileName &filename, Exiv2::ExifData data)
0308 {
0309     if (!isUsable())
0310         return false;
0311 
0312     Q_D(Database);
0313     // we might as well rename insert() to add()
0314     return d->insert(filename, data);
0315 }
0316 
0317 bool Exif::Database::add(const DB::FileName &fileName)
0318 {
0319     if (!isUsable())
0320         return false;
0321 
0322     try {
0323         const auto image = Exiv2::ImageFactory::open(fileName.absolute().toLocal8Bit().data());
0324         Q_ASSERT(image.get() != nullptr);
0325         image->readMetadata();
0326         Exiv2::ExifData &exifData = image->exifData();
0327         Q_D(Database);
0328         return d->insert(fileName, exifData);
0329     } catch (...) {
0330         qCWarning(ExifLog, "Error while reading exif information from %s", qPrintable(fileName.absolute()));
0331         return false;
0332     }
0333 }
0334 
0335 bool Exif::Database::add(const DB::FileNameList &list)
0336 {
0337     if (!isUsable())
0338         return false;
0339 
0340     QList<DBExifInfo> map;
0341 
0342     for (const DB::FileName &fileName : list) {
0343         try {
0344             const auto image = Exiv2::ImageFactory::open(fileName.absolute().toLocal8Bit().data());
0345             Q_ASSERT(image.get() != nullptr);
0346             image->readMetadata();
0347             map << DBExifInfo(fileName, image->exifData());
0348         } catch (...) {
0349             qCWarning(ExifLog, "Error while reading exif information from %s", qPrintable(fileName.absolute()));
0350         }
0351     }
0352     Q_D(Database);
0353     d->insert(map);
0354     return true;
0355 }
0356 
0357 void Exif::Database::remove(const DB::FileName &fileName)
0358 {
0359     if (!isUsable())
0360         return;
0361 
0362     Q_D(Database);
0363     QSqlQuery query(d->m_db);
0364     query.prepare(QString::fromLatin1("DELETE FROM exif WHERE fileName=?"));
0365     query.bindValue(0, fileName.absolute());
0366     if (!query.exec())
0367         d->showErrorAndFail(query);
0368 }
0369 
0370 void Exif::Database::remove(const DB::FileNameList &list)
0371 {
0372     if (!isUsable())
0373         return;
0374 
0375     Q_D(Database);
0376     d->m_db.transaction();
0377     QSqlQuery query(d->m_db);
0378     query.prepare(QString::fromLatin1("DELETE FROM exif WHERE fileName=?"));
0379     for (const DB::FileName &fileName : list) {
0380         query.bindValue(0, fileName.absolute());
0381         if (!query.exec()) {
0382             d->m_db.rollback();
0383             d->showErrorAndFail(query);
0384             return;
0385         }
0386     }
0387     d->m_db.commit();
0388 }
0389 
0390 QSqlQuery *Exif::Database::DatabasePrivate::getInsertQuery()
0391 {
0392     if (!isUsable())
0393         return nullptr;
0394     if (m_insertTransaction)
0395         return m_insertTransaction;
0396     if (m_queryString.isEmpty()) {
0397         QStringList formalList;
0398         const Database::ElementList elms = elements();
0399         for (const DatabaseElement *e : elms) {
0400             formalList.append(e->queryString());
0401         }
0402         m_queryString = QString::fromLatin1("INSERT OR REPLACE into exif values (?, %1) ").arg(formalList.join(QString::fromLatin1(", ")));
0403     }
0404     QSqlQuery *query = new QSqlQuery(m_db);
0405     if (query)
0406         query->prepare(m_queryString);
0407     return query;
0408 }
0409 
0410 void Exif::Database::DatabasePrivate::concludeInsertQuery(QSqlQuery *query)
0411 {
0412     if (m_insertTransaction)
0413         return;
0414     m_db.commit();
0415     delete query;
0416 }
0417 
0418 bool Exif::Database::startInsertTransaction()
0419 {
0420     if (!isUsable())
0421         return false;
0422 
0423     Q_D(Database);
0424     Q_ASSERT(d->m_insertTransaction == nullptr);
0425     d->m_insertTransaction = d->getInsertQuery();
0426     d->m_db.transaction();
0427     return (d->m_insertTransaction != nullptr);
0428 }
0429 
0430 bool Exif::Database::commitInsertTransaction()
0431 {
0432     if (!isUsable())
0433         return false;
0434 
0435     Q_D(Database);
0436     if (d->m_insertTransaction) {
0437         d->m_db.commit();
0438         delete d->m_insertTransaction;
0439         d->m_insertTransaction = nullptr;
0440     } else
0441         qCWarning(ExifLog, "Trying to commit transaction, but no transaction is active!");
0442     return true;
0443 }
0444 
0445 bool Exif::Database::abortInsertTransaction()
0446 {
0447     if (!isUsable())
0448         return false;
0449 
0450     Q_D(Database);
0451     if (d->m_insertTransaction) {
0452         d->m_db.rollback();
0453         delete d->m_insertTransaction;
0454         d->m_insertTransaction = nullptr;
0455     } else
0456         qCWarning(ExifLog, "Trying to abort transaction, but no transaction is active!");
0457     return true;
0458 }
0459 
0460 bool Exif::Database::DatabasePrivate::insert(const DB::FileName &filename, Exiv2::ExifData data)
0461 {
0462     if (!isUsable())
0463         return false;
0464 
0465     QSqlQuery *query = getInsertQuery();
0466     query->bindValue(0, filename.absolute());
0467     int i = 1;
0468     for (const DatabaseElement *e : elements()) {
0469         query->bindValue(i++, e->valueFromExif(data));
0470     }
0471 
0472     bool status = query->exec();
0473     if (!status)
0474         showErrorAndFail(*query);
0475     concludeInsertQuery(query);
0476     return status;
0477 }
0478 
0479 bool Exif::Database::DatabasePrivate::insert(const QList<DBExifInfo> &map)
0480 {
0481     if (!isUsable())
0482         return false;
0483 
0484     QSqlQuery *query = getInsertQuery();
0485     // not a const reference because DatabaseElement::valueFromExif uses operator[] on the exif datum
0486     for (DBExifInfo elt : map) {
0487         query->bindValue(0, elt.first.absolute());
0488         int i = 1;
0489         for (const DatabaseElement *e : elements()) {
0490             query->bindValue(i++, e->valueFromExif(elt.second));
0491         }
0492 
0493         if (!query->exec()) {
0494             showErrorAndFail(*query);
0495         }
0496     }
0497     concludeInsertQuery(query);
0498     return true;
0499 }
0500 
0501 QString Exif::Database::DatabasePrivate::getFileName() const
0502 {
0503     return m_fileName;
0504 }
0505 
0506 bool Exif::Database::isAvailable()
0507 {
0508     return isSQLiteDriverAvailable();
0509 }
0510 
0511 int Exif::Database::DatabasePrivate::DBFileVersion() const
0512 {
0513     // previous to KPA 4.6, there was no metadata table:
0514     if (!m_db.tables().contains(QString::fromLatin1("settings")))
0515         return 1;
0516 
0517     QSqlQuery query(QString::fromLatin1("SELECT value FROM settings WHERE keyword = 'DBVersion'"), m_db);
0518     if (!query.exec())
0519         showErrorAndFail(query);
0520 
0521     if (query.first()) {
0522         return query.value(0).toInt();
0523     }
0524     return 0;
0525 }
0526 
0527 int Exif::Database::DBFileVersion() const
0528 {
0529     Q_D(const Database);
0530     return d->DBFileVersion();
0531 }
0532 
0533 int Exif::Database::DBFileVersionGuaranteed() const
0534 {
0535     Q_D(const Database);
0536 
0537     // previous to KPA 4.6, there was no metadata table:
0538     if (!d->m_db.tables().contains(QString::fromLatin1("settings")))
0539         return 0;
0540 
0541     QSqlQuery query(QString::fromLatin1("SELECT value FROM settings WHERE keyword = 'GuaranteedDataVersion'"), d->m_db);
0542     if (!query.exec())
0543         d->showErrorAndFail(query);
0544 
0545     if (query.first()) {
0546         return query.value(0).toInt();
0547     }
0548     return 0;
0549 }
0550 
0551 int Exif::Database::DBVersion()
0552 {
0553     return DB_VERSION;
0554 }
0555 
0556 bool Exif::Database::DatabasePrivate::isUsable() const
0557 {
0558     return isSQLiteDriverAvailable() && isOpen();
0559 }
0560 bool Exif::Database::isUsable() const
0561 {
0562     Q_D(const Database);
0563     return d->isUsable();
0564 }
0565 
0566 bool Exif::Database::readFields(const DB::FileName &fileName, ElementList &fields) const
0567 {
0568     if (!isUsable())
0569         return false;
0570 
0571     bool foundIt = false;
0572     QStringList fieldList;
0573     for (const DatabaseElement *e : fields) {
0574         fieldList.append(e->columnName());
0575     }
0576 
0577     Q_D(const Database);
0578     QSqlQuery query(d->m_db);
0579     // the query returns a single value, so we don't need the overhead for random access:
0580     query.setForwardOnly(true);
0581 
0582     query.prepare(QString::fromLatin1("select %1 from exif where filename=?")
0583                       .arg(fieldList.join(QString::fromLatin1(", "))));
0584     query.bindValue(0, fileName.absolute());
0585 
0586     if (!query.exec()) {
0587         d->showErrorAndFail(query);
0588     }
0589     if (query.next()) {
0590         // file in exif db -> write back results
0591         int i = 0;
0592         for (DatabaseElement *e : fields) {
0593             e->setValue(query.value(i++));
0594         }
0595         foundIt = true;
0596     }
0597     return foundIt;
0598 }
0599 
0600 DB::FileNameSet Exif::Database::filesMatchingQuery(const QString &queryStr) const
0601 {
0602     if (!isUsable())
0603         return DB::FileNameSet();
0604 
0605     DB::FileNameSet result;
0606     Q_D(const Database);
0607     QSqlQuery query(queryStr, d->m_db);
0608 
0609     if (!query.exec())
0610         d->showErrorAndFail(query);
0611 
0612     else {
0613         if (d->m_doUTF8Conversion)
0614             while (query.next())
0615                 result.insert(DB::FileName::fromAbsolutePath(QString::fromUtf8(query.value(0).toByteArray())));
0616         else
0617             while (query.next())
0618                 result.insert(DB::FileName::fromAbsolutePath(query.value(0).toString()));
0619     }
0620 
0621     return result;
0622 }
0623 
0624 QList<QPair<QString, QString>> Exif::Database::cameras() const
0625 {
0626     QList<QPair<QString, QString>> result;
0627 
0628     if (!isUsable())
0629         return result;
0630 
0631     Q_D(const Database);
0632     QSqlQuery query(QString::fromLatin1("SELECT DISTINCT Exif_Image_Make, Exif_Image_Model FROM exif"), d->m_db);
0633     if (!query.exec()) {
0634         d->showErrorAndFail(query);
0635     } else {
0636         while (query.next()) {
0637             QString make = query.value(0).toString();
0638             QString model = query.value(1).toString();
0639             if (!make.isEmpty() && !model.isEmpty())
0640                 result.append(qMakePair(make, model));
0641         }
0642     }
0643 
0644     return result;
0645 }
0646 
0647 QList<QString> Exif::Database::lenses() const
0648 {
0649     QList<QString> result;
0650 
0651     if (!isUsable())
0652         return result;
0653 
0654     Q_D(const Database);
0655     QSqlQuery query(QString::fromLatin1("SELECT DISTINCT Exif_Photo_LensModel FROM exif"), d->m_db);
0656     if (!query.exec()) {
0657         d->showErrorAndFail(query);
0658     } else {
0659         while (query.next()) {
0660             QString lens = query.value(0).toString();
0661             if (!lens.isEmpty())
0662                 result.append(lens);
0663         }
0664     }
0665 
0666     return result;
0667 }
0668 
0669 int Database::size() const
0670 {
0671     if (!isUsable())
0672         return 0;
0673 
0674     Q_D(const Database);
0675     QSqlQuery query(QLatin1String("SELECT count(*) FROM exif"), d->m_db);
0676     int result = 0;
0677     if (!query.exec()) {
0678         d->showErrorAndFail(query);
0679     } else {
0680         if (query.first()) {
0681             result = query.value(0).toInt();
0682         }
0683     }
0684     return result;
0685 }
0686 
0687 void Exif::Database::recreate(const DB::FileNameList &allImageFiles, DB::AbstractProgressIndicator &progressIndicator)
0688 {
0689     progressIndicator.setMinimum(0);
0690     progressIndicator.setMaximum(allImageFiles.size());
0691 
0692     Q_D(Database);
0693     // We create a backup of the current database in case
0694     // the user presse 'cancel' or there is any error. In that case
0695     // we want to go back to the original DB.
0696 
0697     const QString origBackup = d->getFileName() + QLatin1String(".bak");
0698     d->m_db.close();
0699 
0700     QDir().remove(origBackup);
0701     QDir().rename(d->getFileName(), origBackup);
0702     d->init();
0703 
0704     // using a transaction here removes a *huge* overhead on the insert statements
0705     startInsertTransaction();
0706     int i = 0;
0707     for (const auto &fileName : allImageFiles) {
0708         progressIndicator.setValue(i++);
0709         add(fileName);
0710         if (i % 10) {
0711             auto app = QCoreApplication::instance();
0712             if (app)
0713                 app->processEvents();
0714         }
0715         if (progressIndicator.wasCanceled())
0716             break;
0717     }
0718 
0719     // PENDING(blackie) We should count the amount of files that did not succeeded and warn the user.
0720     if (progressIndicator.wasCanceled()) {
0721         abortInsertTransaction();
0722         d->m_db.close();
0723         QDir().remove(d->getFileName());
0724         QDir().rename(origBackup, d->getFileName());
0725         d->init();
0726     } else {
0727         commitInsertTransaction();
0728         QDir().remove(origBackup);
0729     }
0730 }
0731 
0732 // vi:expandtab:tabstop=4 shiftwidth=4: