File indexing completed on 2025-01-19 03:53:46

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-05-29
0007  * Description : Thumbnails database interface.
0008  *
0009  * SPDX-FileCopyrightText:      2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "thumbsdb.h"
0017 
0018 // Qt includes
0019 
0020 #include <QMap>
0021 
0022 // Local includes
0023 
0024 #include "digikam_debug.h"
0025 
0026 namespace Digikam
0027 {
0028 
0029 class Q_DECL_HIDDEN ThumbsDb::Private
0030 {
0031 
0032 public:
0033 
0034     explicit Private()
0035       : db(nullptr)
0036     {
0037     }
0038 
0039     ThumbsDbBackend* db;
0040 };
0041 
0042 ThumbsDb::ThumbsDb(ThumbsDbBackend* const backend)
0043     : d(new Private)
0044 {
0045     d->db = backend;
0046 }
0047 
0048 ThumbsDb::~ThumbsDb()
0049 {
0050     delete d;
0051 }
0052 
0053 bool ThumbsDb::setSetting(const QString& keyword, const QString& value )
0054 {
0055     QMap<QString, QVariant> parameters;
0056     parameters.insert(QLatin1String(":keyword"), keyword);
0057     parameters.insert(QLatin1String(":value"), value);
0058     BdEngineBackend::QueryState queryStateResult = d->db->execDBAction(d->db->getDBAction(QLatin1String("ReplaceThumbnailSetting")),
0059                                                                        parameters);
0060 
0061     return (queryStateResult == BdEngineBackend::NoErrors);
0062 }
0063 
0064 QString ThumbsDb::getSetting(const QString& keyword)
0065 {
0066     QMap<QString, QVariant> parameters;
0067     parameters.insert(QLatin1String(":keyword"), keyword);
0068     QList<QVariant> values;
0069 
0070     // TODO Should really check return status here
0071 
0072     BdEngineBackend::QueryState queryStateResult = d->db->execDBAction(d->db->getDBAction(QLatin1String("SelectThumbnailSetting")),
0073                                                                        parameters, &values);
0074 
0075     qCDebug(DIGIKAM_THUMBSDB_LOG) << "ThumbDB SelectThumbnailSetting val ret = "
0076                                   << (BdEngineBackend::QueryStateEnum)queryStateResult;
0077 
0078     if (!values.isEmpty())
0079     {
0080         return values.first().toString();
0081     }
0082 
0083     return QString();
0084 }
0085 
0086 QString ThumbsDb::getLegacySetting(const QString& keyword)
0087 {
0088     QMap<QString, QVariant> parameters;
0089     parameters.insert(QLatin1String(":keyword"), keyword);
0090     QList<QVariant> values;
0091 
0092     // TODO Should really check return status here
0093 
0094     BdEngineBackend::QueryState queryStateResult = d->db->execDBAction(d->db->getDBAction(QLatin1String("SelectThumbnailLegacySetting")),
0095                                                                        parameters, &values);
0096 
0097     qCDebug(DIGIKAM_THUMBSDB_LOG) << "ThumbDB SelectThumbnailLegacySetting val ret = "
0098                                   << (BdEngineBackend::QueryStateEnum)queryStateResult;
0099 
0100     if (!values.isEmpty())
0101     {
0102         return values.first().toString();
0103     }
0104 
0105     return QString();
0106 }
0107 
0108 ThumbsDbInfo ThumbsDb::fillThumbnailInfo(const QList<QVariant>& values)
0109 {
0110     if (values.isEmpty())
0111     {
0112         return ThumbsDbInfo();
0113     }
0114 
0115     ThumbsDbInfo info;
0116 
0117     info.id               = values.at(0).toInt();
0118     info.type             = (DatabaseThumbnail::Type)values.at(1).toInt();
0119     info.modificationDate = values.at(2).toDateTime();
0120     info.modificationDate.setTimeSpec(Qt::UTC);
0121     info.orientationHint  = values.at(3).toInt();
0122     info.data             = values.at(4).toByteArray();
0123 
0124     return info;
0125 }
0126 
0127 ThumbsDbInfo ThumbsDb::findByHash(const QString& uniqueHash, qlonglong fileSize)
0128 {
0129     QList<QVariant> values;
0130     d->db->execSql(QLatin1String("SELECT id, type, modificationDate, orientationHint, data "
0131                                  "FROM Thumbnails "
0132                                  " INNER JOIN UniqueHashes ON id = thumbId "
0133                                  "  WHERE uniqueHash=? AND fileSize=?;"),
0134                    uniqueHash, fileSize, &values);
0135 
0136     return fillThumbnailInfo(values);
0137 }
0138 
0139 ThumbsDbInfo ThumbsDb::findByFilePath(const QString& path)
0140 {
0141     QList<QVariant> values;
0142     d->db->execSql(QLatin1String("SELECT id, type, modificationDate, orientationHint, data "
0143                                  "FROM Thumbnails "
0144                                  " INNER JOIN FilePaths ON id = thumbId "
0145                                  "  WHERE path=?;"),
0146                    path, &values);
0147 
0148     return fillThumbnailInfo(values);
0149 }
0150 
0151 ThumbsDbInfo ThumbsDb::findByFilePath(const QString& path, const QString& uniqueHash)
0152 {
0153     ThumbsDbInfo info = findByFilePath(path);
0154 
0155     if (uniqueHash.isNull())
0156     {
0157         return info;
0158     }
0159 
0160     if (info.data.isNull())
0161     {
0162         return info;
0163     }
0164 
0165     // double check that thumbnail is not referenced by a different hash
0166 
0167     QList<QVariant> values;
0168     d->db->execSql(QLatin1String("SELECT uniqueHash FROM UniqueHashes WHERE thumbId=?;"),
0169                    info.id, &values);
0170 
0171     if (values.isEmpty())
0172     {
0173         return info;
0174     }
0175 
0176     Q_FOREACH (const QVariant& hash, values)
0177     {
0178         if (hash == uniqueHash)
0179         {   // cppcheck-suppress useStlAlgorithm
0180             return info;
0181         }
0182     }
0183 
0184     return ThumbsDbInfo();
0185 }
0186 
0187 ThumbsDbInfo ThumbsDb::findByCustomIdentifier(const QString& id)
0188 {
0189     QList<QVariant> values;
0190     d->db->execSql(QLatin1String("SELECT id, type, modificationDate, orientationHint, data "
0191                                  "FROM Thumbnails "
0192                                  " INNER JOIN CustomIdentifiers ON id = thumbId "
0193                                  "  WHERE identifier=?;"),
0194                    id, &values);
0195 
0196     return fillThumbnailInfo(values);
0197 }
0198 
0199 QList<int> ThumbsDb::findAll()
0200 {
0201     QList<QVariant> values;
0202     d->db->execSql(QLatin1String("SELECT id FROM Thumbnails;"),
0203                    &values);
0204 
0205     QList<int> thumbIds;
0206 
0207     Q_FOREACH (const QVariant& object, values)
0208     {
0209         thumbIds << object.toInt();
0210     }
0211 
0212     return thumbIds;
0213 }
0214 
0215 QHash<QString, int> ThumbsDb::getFilePathsWithThumbnail()
0216 {
0217     DbEngineSqlQuery query = d->db->prepareQuery(QString::fromLatin1("SELECT path, thumbId "
0218                                                         "FROM FilePaths "
0219                                                         " INNER JOIN Thumbnails ON thumbId = id "
0220                                                         "  WHERE type BETWEEN %1 AND %2;")
0221                                                  .arg(DatabaseThumbnail::PGF)
0222                                                  .arg(DatabaseThumbnail::PNG));
0223 
0224     if (!d->db->exec(query))
0225     {
0226         return QHash<QString, int>();
0227     }
0228 
0229     QHash <QString, int> filePaths;
0230 
0231     while (query.next())
0232     {
0233         filePaths[query.value(0).toString()] = query.value(1).toInt();
0234     }
0235 
0236     return filePaths;
0237 }
0238 
0239 BdEngineBackend::QueryState ThumbsDb::insertUniqueHash(const QString& uniqueHash, qlonglong fileSize, int thumbId)
0240 {
0241     return d->db->execSql(QLatin1String("REPLACE INTO UniqueHashes (uniqueHash, fileSize, thumbId) VALUES (?,?,?);"),
0242                           uniqueHash, fileSize, thumbId);
0243 }
0244 
0245 BdEngineBackend::QueryState ThumbsDb::insertFilePath(const QString& path, int thumbId)
0246 {
0247     return d->db->execSql(QLatin1String("REPLACE INTO FilePaths (path, thumbId) VALUES (?,?);"),
0248                           path, thumbId);
0249 }
0250 
0251 BdEngineBackend::QueryState ThumbsDb::insertCustomIdentifier(const QString& path, int thumbId)
0252 {
0253     return d->db->execSql(QLatin1String("REPLACE INTO CustomIdentifiers (identifier, thumbId) VALUES (?,?);"),
0254                           path, thumbId);
0255 }
0256 
0257 BdEngineBackend::QueryState ThumbsDb::remove(int thumbId)
0258 {
0259     return d->db->execSql(QLatin1String("DELETE FROM Thumbnails WHERE id=?;"), thumbId);
0260 }
0261 
0262 BdEngineBackend::QueryState ThumbsDb::removeByUniqueHash(const QString& uniqueHash, qlonglong fileSize)
0263 {
0264     // UniqueHashes + FilePaths entries are removed by trigger
0265 
0266     QMap<QString, QVariant> parameters;
0267     parameters.insert(QLatin1String(":uniqueHash"), uniqueHash);
0268     parameters.insert(QLatin1String(":filesize"),   fileSize);
0269 
0270     return d->db->execDBAction(d->db->getDBAction(QLatin1String("Delete_Thumbnail_ByUniqueHashId")),
0271                                parameters);
0272 }
0273 
0274 BdEngineBackend::QueryState ThumbsDb::removeByFilePath(const QString& path)
0275 {
0276     // UniqueHashes + FilePaths entries are removed by trigger
0277 
0278     QMap<QString, QVariant> parameters;
0279     parameters.insert(QLatin1String(":path"), path);
0280 
0281     return d->db->execDBAction(d->db->getDBAction(QLatin1String("Delete_Thumbnail_ByPath")),
0282                                parameters);
0283 }
0284 
0285 BdEngineBackend::QueryState ThumbsDb::removeByCustomIdentifier(const QString& id)
0286 {
0287     // UniqueHashes + FilePaths entries are removed by trigger
0288 
0289     QMap<QString, QVariant> parameters;
0290     parameters.insert(QLatin1String(":identifier"), id);
0291 
0292     return d->db->execDBAction(d->db->getDBAction(QLatin1String("Delete_Thumbnail_ByCustomIdentifier")),
0293                                parameters);
0294 }
0295 
0296 BdEngineBackend::QueryState ThumbsDb::insertThumbnail(const ThumbsDbInfo& info, QVariant* const lastInsertId)
0297 {
0298     QVariant id;
0299     BdEngineBackend::QueryState lastQueryState;
0300     lastQueryState = d->db->execSql(QLatin1String("INSERT INTO Thumbnails (type, modificationDate, orientationHint, data) VALUES (?, ?, ?, ?);"),
0301                                     info.type, info.modificationDate, info.orientationHint, info.data, nullptr, &id);
0302 
0303     if (BdEngineBackend::NoErrors == lastQueryState)
0304     {
0305         *lastInsertId = id.toInt();
0306     }
0307     else
0308     {
0309         *lastInsertId = -1;
0310     }
0311 
0312     return lastQueryState;
0313 }
0314 
0315 BdEngineBackend::QueryState ThumbsDb::renameByFilePath(const QString& oldPath, const QString& newPath)
0316 {
0317     return d->db->execSql(QLatin1String("UPDATE FilePaths SET path=? WHERE path=?;"),
0318                           newPath, oldPath);
0319 }
0320 
0321 BdEngineBackend::QueryState ThumbsDb::replaceThumbnail(const ThumbsDbInfo& info)
0322 {
0323     return d->db->execSql(QLatin1String("REPLACE INTO Thumbnails (id, type, modificationDate, orientationHint, data) VALUES(?, ?, ?, ?, ?);"),
0324                           QList<QVariant>() << info.id << info.type << info.modificationDate << info.orientationHint << info.data);
0325 }
0326 
0327 BdEngineBackend::QueryState ThumbsDb::updateModificationDate(int thumbId, const QDateTime& modificationDate)
0328 {
0329     return d->db->execSql(QLatin1String("UPDATE Thumbnails SET modificationDate=? WHERE id=?;"),
0330                           modificationDate, thumbId);
0331 }
0332 
0333 void ThumbsDb::replaceUniqueHash(const QString& oldUniqueHash, int oldFileSize,
0334                                  const QString& newUniqueHash, int newFileSize)
0335 {
0336     d->db->execSql(QLatin1String("UPDATE UniqueHashes SET uniqueHash=?, fileSize=? WHERE uniqueHash=? AND fileSize=?;"),
0337                    newUniqueHash, newFileSize, oldUniqueHash, oldFileSize);
0338 }
0339 
0340 bool ThumbsDb::integrityCheck()
0341 {
0342     QList<QVariant> values;
0343     d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("checkThumbnailsDbIntegrity")),
0344                         &values);
0345 
0346     switch (d->db->databaseType())
0347     {
0348         case BdEngineBackend::DbType::SQLite:
0349 
0350             // For SQLite the integrity check returns a single row with one string column "ok" on success and multiple rows on error.
0351 
0352             return ((values.size() == 1) && (values.first().toString().toLower().compare(QLatin1String("ok")) == 0));
0353 
0354         case BdEngineBackend::DbType::MySQL:
0355 
0356             // For MySQL, for every checked table, the table name, operation (check), message type (status) and the message text (ok on success)
0357             // are returned. So we check if there are four elements and if yes, whether the fourth element is "ok".
0358 /*
0359             qCDebug(DIGIKAM_DATABASE_LOG) << "MySQL check returned " << values.size() << " rows";
0360 */
0361             if ((values.size() % 4) != 0)
0362             {
0363                 return false;
0364             }
0365 
0366             for (QList<QVariant>::iterator it = values.begin() ; it != values.end() ; )
0367             {
0368                 QString tableName   = (*it).toString();
0369                 ++it;
0370                 QString operation   = (*it).toString();
0371                 ++it;
0372                 QString messageType = (*it).toString();
0373                 ++it;
0374                 QString messageText = (*it).toString();
0375                 ++it;
0376 
0377                 if (messageText.toLower().compare(QLatin1String("ok")) != 0)
0378                 {
0379                     qCDebug(DIGIKAM_DATABASE_LOG) << "Failed integrity check for table " << tableName
0380                                                   << ". Reason:" << messageText;
0381                     return false;
0382                 }
0383                 else
0384                 {
0385 /*
0386                     qCDebug(DIGIKAM_DATABASE_LOG) << "Passed integrity check for table " << tableName;
0387 */
0388                 }
0389             }
0390 
0391             // No error conditions. Db passed the integrity check.
0392 
0393             return true;
0394 
0395         default:
0396         {
0397             return false;
0398         }
0399     }
0400 }
0401 
0402 void ThumbsDb::vacuum()
0403 {
0404     d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("vacuumThumbnailsDB")));
0405 }
0406 
0407 } // namespace Digikam