File indexing completed on 2025-04-27 03:58:09

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2007-07-20
0007  * Description : Loader for thumbnails - Database thumbnail storage
0008  *
0009  * SPDX-FileCopyrightText: 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2003-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "thumbnailcreator_p.h"
0018 
0019 namespace Digikam
0020 {
0021 
0022 void ThumbnailCreator::storeInDatabase(const ThumbnailInfo& info, const ThumbnailImage& image) const
0023 {
0024     ThumbsDbInfo dbInfo;
0025 
0026     // We rely on loadThumbsDbInfo() being called before, so we do not need to look up
0027     // by filepath of uniqueHash to find out if a thumb need to be replaced.
0028 
0029     dbInfo.id               = d->dbIdForReplacement;
0030     d->dbIdForReplacement   = -1;
0031     dbInfo.type             = DatabaseThumbnail::PGF;
0032     dbInfo.modificationDate = info.modificationDate;
0033     dbInfo.orientationHint  = image.exifOrientation;
0034 
0035     if      (dbInfo.type == DatabaseThumbnail::PGF)
0036     {
0037         // NOTE: see bug #233094: using PGF compression level 4 there. Do not use a value > 4,
0038         // else image is blurred due to down-sampling.
0039 
0040         if (!PGFUtils::writePGFImageData(image.qimage, dbInfo.data, 4))
0041         {
0042             qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot save PGF thumb in DB";
0043             return;
0044         }
0045     }
0046     else if (dbInfo.type == DatabaseThumbnail::JPEG)
0047     {
0048         QBuffer buffer(&dbInfo.data);
0049         buffer.open(QIODevice::WriteOnly);
0050         image.qimage.save(&buffer, "JPEG", 90);  // Here we will use JPEG quality = 90 to reduce artifacts.
0051         buffer.close();
0052 
0053         if (dbInfo.data.isNull())
0054         {
0055             qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot save JPEG thumb in DB";
0056             return;
0057         }
0058     }
0059     else if (dbInfo.type == DatabaseThumbnail::JPEG2000)
0060     {
0061         QBuffer buffer(&dbInfo.data);
0062         buffer.open(QIODevice::WriteOnly);
0063         image.qimage.save(&buffer, "JP2");
0064         buffer.close();
0065 
0066         if (dbInfo.data.isNull())
0067         {
0068             qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot save JPEG2000 thumb in DB";
0069             return;
0070         }
0071     }
0072     else if (dbInfo.type == DatabaseThumbnail::PNG)
0073     {
0074         QBuffer buffer(&dbInfo.data);
0075         buffer.open(QIODevice::WriteOnly);
0076         image.qimage.save(&buffer, "PNG", 0);
0077         buffer.close();
0078 
0079         if (dbInfo.data.isNull())
0080         {
0081             qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot save PNG thumb in DB";
0082             return;
0083         }
0084     }
0085 
0086     ThumbsDbAccess access;
0087     BdEngineBackend::QueryState lastQueryState = BdEngineBackend::QueryState(BdEngineBackend::ConnectionError);
0088 
0089     while (lastQueryState == BdEngineBackend::ConnectionError)
0090     {
0091         lastQueryState = access.backend()->beginTransaction();
0092 
0093         if (BdEngineBackend::NoErrors != lastQueryState)
0094         {
0095             continue;
0096         }
0097 
0098         // Insert thumbnail data
0099 
0100         if (dbInfo.id == -1)
0101         {
0102             QVariant id;
0103             lastQueryState = access.db()->insertThumbnail(dbInfo, &id);
0104 
0105             if (BdEngineBackend::NoErrors != lastQueryState)
0106             {
0107                 continue;
0108             }
0109             else
0110             {
0111                 dbInfo.id = id.toInt();
0112             }
0113         }
0114         else
0115         {
0116             lastQueryState = access.db()->replaceThumbnail(dbInfo);
0117 
0118             if (BdEngineBackend::NoErrors != lastQueryState)
0119             {
0120                 continue;
0121             }
0122         }
0123 
0124         // Insert lookup data used to locate thumbnail data
0125 
0126         if (!info.customIdentifier.isNull())
0127         {
0128             lastQueryState = access.db()->insertCustomIdentifier(info.customIdentifier, dbInfo.id);
0129 
0130             if (BdEngineBackend::NoErrors != lastQueryState)
0131             {
0132                 continue;
0133             }
0134         }
0135         else
0136         {
0137             if (!info.uniqueHash.isNull())
0138             {
0139                 lastQueryState = access.db()->insertUniqueHash(info.uniqueHash, info.fileSize, dbInfo.id);
0140 
0141                 if (BdEngineBackend::NoErrors != lastQueryState)
0142                 {
0143                     continue;
0144                 }
0145             }
0146 
0147             if (!info.filePath.isNull())
0148             {
0149                 lastQueryState = access.db()->insertFilePath(info.filePath, dbInfo.id);
0150 
0151                 if (BdEngineBackend::NoErrors != lastQueryState)
0152                 {
0153                     continue;
0154                 }
0155             }
0156         }
0157 
0158         lastQueryState = access.backend()->commitTransaction();
0159 
0160         if (BdEngineBackend::NoErrors != lastQueryState)
0161         {
0162             continue;
0163         }
0164     }
0165 }
0166 
0167 ThumbsDbInfo ThumbnailCreator::loadThumbsDbInfo(const ThumbnailInfo& info) const
0168 {
0169     ThumbsDbAccess access;
0170     ThumbsDbInfo   dbInfo;
0171 
0172     // Custom identifier takes precedence
0173 
0174     if (!info.customIdentifier.isEmpty())
0175     {
0176         dbInfo = access.db()->findByCustomIdentifier(info.customIdentifier);
0177     }
0178     else
0179     {
0180         if (!info.uniqueHash.isEmpty())
0181         {
0182             dbInfo = access.db()->findByHash(info.uniqueHash, info.fileSize);
0183         }
0184 
0185         if (dbInfo.data.isNull() && !info.filePath.isEmpty())
0186         {
0187             dbInfo = access.db()->findByFilePath(info.filePath, info.uniqueHash);
0188         }
0189     }
0190 
0191     // Store for use in storeInDatabase()
0192 
0193     d->dbIdForReplacement = dbInfo.id;
0194 
0195     return dbInfo;
0196 }
0197 
0198 bool ThumbnailCreator::isInDatabase(const ThumbnailInfo& info) const
0199 {
0200     ThumbsDbInfo dbInfo = loadThumbsDbInfo(info);
0201 
0202     if (dbInfo.data.isNull())
0203     {
0204         return false;
0205     }
0206 
0207     // Check modification date
0208 
0209     if (dbInfo.modificationDate < info.modificationDate)
0210     {
0211         return false;
0212     }
0213 
0214     return true;
0215 }
0216 
0217 ThumbnailImage ThumbnailCreator::loadFromDatabase(const ThumbnailInfo& info) const
0218 {
0219     ThumbsDbInfo dbInfo = loadThumbsDbInfo(info);
0220     ThumbnailImage image;
0221 
0222     if (dbInfo.data.isNull())
0223     {
0224         return ThumbnailImage();
0225     }
0226 
0227     // Check modification date
0228 
0229     if (dbInfo.modificationDate < info.modificationDate)
0230     {
0231         return ThumbnailImage();
0232     }
0233 
0234     // Read QImage from data blob
0235 
0236     if      (dbInfo.type == DatabaseThumbnail::PGF)
0237     {
0238         if (!PGFUtils::readPGFImageData(dbInfo.data, image.qimage))
0239         {
0240             qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot load PGF thumb from DB";
0241             return ThumbnailImage();
0242         }
0243     }
0244     else if (dbInfo.type == DatabaseThumbnail::JPEG)
0245     {
0246         QBuffer buffer(&dbInfo.data);
0247         buffer.open(QIODevice::ReadOnly);
0248         image.qimage.load(&buffer, "JPEG");
0249         buffer.close();
0250 
0251         if (dbInfo.data.isNull())
0252         {
0253             qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot load JPEG thumb from DB";
0254             return ThumbnailImage();
0255         }
0256     }
0257     else if (dbInfo.type == DatabaseThumbnail::JPEG2000)
0258     {
0259         QBuffer buffer(&dbInfo.data);
0260         buffer.open(QIODevice::ReadOnly);
0261         image.qimage.load(&buffer, "JP2");
0262         buffer.close();
0263 
0264         if (dbInfo.data.isNull())
0265         {
0266             qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot load JPEG2000 thumb from DB";
0267             return ThumbnailImage();
0268         }
0269     }
0270     else if (dbInfo.type == DatabaseThumbnail::PNG)
0271     {
0272         QBuffer buffer(&dbInfo.data);
0273         buffer.open(QIODevice::ReadOnly);
0274         image.qimage.load(&buffer, "PNG");
0275         buffer.close();
0276 
0277         if (dbInfo.data.isNull())
0278         {
0279             qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot load PNG thumb from DB";
0280             return ThumbnailImage();
0281         }
0282     }
0283 
0284     // Give priority to main database's rotation flag
0285     // NOTE: Breaks rotation of RAWs which do not contain JPEG previews
0286 
0287     image.exifOrientation = info.orientationHint;
0288 
0289     if ((image.exifOrientation == DMetadata::ORIENTATION_UNSPECIFIED) &&
0290         !info.filePath.isEmpty()                                      &&
0291         LoadSaveThread::infoProvider())
0292     {
0293         image.exifOrientation = LoadSaveThread::infoProvider()->orientationHint(info.filePath);
0294     }
0295 
0296     if (image.exifOrientation == DMetadata::ORIENTATION_UNSPECIFIED)
0297     {
0298         image.exifOrientation = dbInfo.orientationHint;
0299     }
0300 
0301     return image;
0302 }
0303 
0304 void ThumbnailCreator::deleteFromDatabase(const ThumbnailInfo& info) const
0305 {
0306     ThumbsDbAccess access;
0307     BdEngineBackend::QueryState lastQueryState = BdEngineBackend::QueryState(BdEngineBackend::ConnectionError);
0308 
0309     while (BdEngineBackend::ConnectionError == lastQueryState)
0310     {
0311         lastQueryState = access.backend()->beginTransaction();
0312 
0313         if (BdEngineBackend::NoErrors != lastQueryState)
0314         {
0315             continue;
0316         }
0317 
0318         if (!info.uniqueHash.isNull())
0319         {
0320             lastQueryState = access.db()->removeByUniqueHash(info.uniqueHash, info.fileSize);
0321 
0322             if (BdEngineBackend::NoErrors != lastQueryState)
0323             {
0324                 continue;
0325             }
0326         }
0327 
0328         if (!info.filePath.isNull())
0329         {
0330             lastQueryState = access.db()->removeByFilePath(info.filePath);
0331 
0332             if (BdEngineBackend::NoErrors != lastQueryState)
0333             {
0334                 continue;
0335             }
0336         }
0337 
0338         lastQueryState = access.backend()->commitTransaction();
0339 
0340         if (BdEngineBackend::NoErrors != lastQueryState)
0341         {
0342             continue;
0343         }
0344     }
0345 }
0346 
0347 } // namespace Digikam