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: