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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-01-11
0007  * Description : shared image loading and caching
0008  *
0009  * SPDX-FileCopyrightText: 2005-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "loadingcache.h"
0016 
0017 // Qt includes
0018 
0019 #include <QCache>
0020 #include <QHash>
0021 
0022 // KDE includes
0023 
0024 #include <kmemoryinfo.h>
0025 
0026 // Local includes
0027 
0028 #include "digikam_debug.h"
0029 #include "iccsettings.h"
0030 #include "metaengine.h"
0031 #include "thumbnailsize.h"
0032 
0033 namespace Digikam
0034 {
0035 
0036 LoadingProcessListener::LoadingProcessListener()
0037 {
0038 }
0039 
0040 LoadingProcessListener::~LoadingProcessListener()
0041 {
0042 }
0043 
0044 // -----------------------------------------------------------------------------------
0045 
0046 LoadingProcess::LoadingProcess()
0047 {
0048 }
0049 
0050 LoadingProcess::~LoadingProcess()
0051 {
0052 }
0053 
0054 // -----------------------------------------------------------------------------------
0055 
0056 class Q_DECL_HIDDEN LoadingCache::Private
0057 {
0058 public:
0059 
0060     explicit Private(LoadingCache* const q)
0061       : watch(nullptr),
0062         q    (q)
0063     {
0064     }
0065 
0066     void mapImageFilePath(const QString& filePath, const QString& cacheKey);
0067     void mapThumbnailFilePath(const QString& filePath, const QString& cacheKey);
0068     void cleanUpImageFilePathHash();
0069     void cleanUpThumbnailFilePathHash();
0070     LoadingCacheFileWatch* fileWatch() const;
0071 
0072 public:
0073 
0074     QCache<QString, DImg>           imageCache;
0075     QCache<QString, QImage>         thumbnailImageCache;
0076     QCache<QString, QPixmap>        thumbnailPixmapCache;
0077     QMultiHash<QString, QString>    imageFilePathHash;
0078     QMultiHash<QString, QString>    thumbnailFilePathHash;
0079     QHash<LoadingProcess*, QString> loadingDict;
0080 
0081     /// Note: Don't make the mutex recursive, we need to use a wait condition on it
0082     QMutex                          mutex;
0083 
0084     QWaitCondition                  condVar;
0085 
0086     LoadingCacheFileWatch*          watch;
0087     LoadingCache*                   q;
0088 };
0089 
0090 LoadingCacheFileWatch* LoadingCache::Private::fileWatch() const
0091 {
0092     // install default watch if no watch is set yet
0093 
0094     if (!watch)
0095     {
0096         q->setFileWatch(new LoadingCacheFileWatch);
0097     }
0098 
0099     return watch;
0100 }
0101 
0102 void LoadingCache::Private::mapImageFilePath(const QString& filePath, const QString& cacheKey)
0103 {
0104     if (imageFilePathHash.size() > (5 * imageCache.size()))
0105     {
0106         cleanUpImageFilePathHash();
0107     }
0108 
0109     imageFilePathHash.insert(filePath, cacheKey);
0110 }
0111 
0112 void LoadingCache::Private::mapThumbnailFilePath(const QString& filePath, const QString& cacheKey)
0113 {
0114     if (thumbnailFilePathHash.size() > (5 * (thumbnailImageCache.size() + thumbnailPixmapCache.size())))
0115     {
0116         cleanUpThumbnailFilePathHash();
0117     }
0118 
0119     thumbnailFilePathHash.insert(filePath, cacheKey);
0120 }
0121 
0122 void LoadingCache::Private::cleanUpImageFilePathHash()
0123 {
0124     // Remove all entries from hash whose value is no longer a key in the cache
0125 
0126     QList<QString> keys = imageCache.keys();
0127     QMultiHash<QString, QString>::iterator it;
0128 
0129     for (it = imageFilePathHash.begin() ; it != imageFilePathHash.end() ; )
0130     {
0131         if (!keys.contains(it.value()))
0132         {
0133             fileWatch()->removeImage(it.key());
0134             it = imageFilePathHash.erase(it);
0135         }
0136         else
0137         {
0138             ++it;
0139         }
0140     }
0141 }
0142 
0143 void LoadingCache::Private::cleanUpThumbnailFilePathHash()
0144 {
0145     QList<QString> keys;
0146 
0147     keys += thumbnailImageCache.keys();
0148     keys += thumbnailPixmapCache.keys();
0149 
0150     QMultiHash<QString, QString>::iterator it;
0151 
0152     for (it = thumbnailFilePathHash.begin() ; it != thumbnailFilePathHash.end() ; )
0153     {
0154         if (!keys.contains(it.value()))
0155         {
0156             it = thumbnailFilePathHash.erase(it);
0157         }
0158         else
0159         {
0160             ++it;
0161         }
0162     }
0163 }
0164 
0165 LoadingCache* LoadingCache::m_instance = nullptr;
0166 
0167 LoadingCache* LoadingCache::cache()
0168 {
0169     if (!m_instance)
0170     {
0171         m_instance = new LoadingCache;
0172     }
0173 
0174     return m_instance;
0175 }
0176 
0177 void LoadingCache::cleanUp()
0178 {
0179     delete m_instance;
0180 }
0181 
0182 LoadingCache::LoadingCache()
0183     : d(new Private(this))
0184 {
0185     KMemoryInfo memInfo;
0186     setCacheSize(qBound(100, int(memInfo.totalPhysical() / 1024 / 1024 * 0.06), 1024));
0187 
0188     // the pixmap number should not be based on system memory, it's graphics memory
0189 
0190     setThumbnailCacheSize(10, 200);
0191 
0192     // good place to call it here as LoadingCache is a singleton
0193 
0194     qRegisterMetaType<LoadingDescription>("LoadingDescription");
0195     qRegisterMetaType<DImg>("DImg");
0196     qRegisterMetaType<MetaEngineData>("MetaEngineData");
0197 
0198     connect(IccSettings::instance(), SIGNAL(signalICCSettingsChanged(ICCSettingsContainer,ICCSettingsContainer)),
0199             this, SLOT(iccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer)));
0200 }
0201 
0202 LoadingCache::~LoadingCache()
0203 {
0204     delete d->watch;
0205     delete d;
0206     m_instance = nullptr;
0207 }
0208 
0209 DImg* LoadingCache::retrieveImage(const QString& cacheKey) const
0210 {
0211     QString filePath(d->imageFilePathHash.key(cacheKey));
0212     d->fileWatch()->checkFileWatch(filePath);
0213 
0214     return d->imageCache[cacheKey];
0215 }
0216 
0217 bool LoadingCache::putImage(const QString& cacheKey, const DImg& img, const QString& filePath) const
0218 {
0219     bool isInserted = false;
0220 
0221     if (isCacheable(img))
0222     {
0223         int cost   = img.numBytes() / 1024;
0224         isInserted = d->imageCache.insert(cacheKey, new DImg(img), cost);
0225 
0226         if (isInserted && !filePath.isEmpty())
0227         {
0228             d->mapImageFilePath(filePath, cacheKey);
0229             d->fileWatch()->addedImage(filePath);
0230         }
0231     }
0232 
0233     return isInserted;
0234 }
0235 
0236 void LoadingCache::removeImage(const QString& cacheKey)
0237 {
0238     d->imageCache.remove(cacheKey);
0239 }
0240 
0241 void LoadingCache::removeImages()
0242 {
0243     d->imageCache.clear();
0244 }
0245 
0246 bool LoadingCache::isCacheable(const DImg& img) const
0247 {
0248     // return whether image fits in cache
0249 
0250     return ((quint64)d->imageCache.maxCost() >= (img.numBytes() / 1024));
0251 }
0252 
0253 void LoadingCache::addLoadingProcess(LoadingProcess* const process)
0254 {
0255     d->loadingDict.insert(process, process->cacheKey());
0256 }
0257 
0258 LoadingProcess* LoadingCache::retrieveLoadingProcess(const QString& cacheKey) const
0259 {
0260     return d->loadingDict.key(cacheKey, nullptr);
0261 }
0262 
0263 void LoadingCache::removeLoadingProcess(LoadingProcess* const process)
0264 {
0265     d->loadingDict.remove(process);
0266 }
0267 
0268 void LoadingCache::notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description)
0269 {
0270     for (QHash<LoadingProcess*, QString>::const_iterator it = d->loadingDict.constBegin() ;
0271          it != d->loadingDict.constEnd() ; ++it)
0272     {
0273         it.key()->notifyNewLoadingProcess(process, description);
0274     }
0275 }
0276 
0277 void LoadingCache::setCacheSize(int megabytes)
0278 {
0279     qCDebug(DIGIKAM_GENERAL_LOG) << "Allowing a cache size of" << megabytes << "MB";
0280     d->imageCache.setMaxCost(megabytes * 1024);
0281 }
0282 
0283 quint64 LoadingCache::getCacheSize() const
0284 {
0285     return (d->imageCache.maxCost() * 1024);
0286 }
0287 
0288 // --- Thumbnails ----
0289 
0290 const QImage* LoadingCache::retrieveThumbnail(const QString& cacheKey) const
0291 {
0292     return d->thumbnailImageCache[cacheKey];
0293 }
0294 
0295 const QPixmap* LoadingCache::retrieveThumbnailPixmap(const QString& cacheKey) const
0296 {
0297     return d->thumbnailPixmapCache[cacheKey];
0298 }
0299 
0300 bool LoadingCache::hasThumbnailPixmap(const QString& cacheKey) const
0301 {
0302     return d->thumbnailPixmapCache.contains(cacheKey);
0303 }
0304 
0305 void LoadingCache::putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath)
0306 {
0307     int cost = thumb.sizeInBytes();
0308 
0309     if (d->thumbnailImageCache.insert(cacheKey, new QImage(thumb), cost))
0310     {
0311         d->mapThumbnailFilePath(filePath, cacheKey);
0312     }
0313 }
0314 
0315 void LoadingCache::putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath)
0316 {
0317     int cost = thumb.width() * thumb.height() * thumb.depth() / 8;
0318 
0319     if (d->thumbnailPixmapCache.insert(cacheKey, new QPixmap(thumb), cost))
0320     {
0321         d->mapThumbnailFilePath(filePath, cacheKey);
0322     }
0323 }
0324 
0325 void LoadingCache::removeThumbnail(const QString& cacheKey)
0326 {
0327     d->thumbnailImageCache.remove(cacheKey);
0328     d->thumbnailPixmapCache.remove(cacheKey);
0329 }
0330 
0331 void LoadingCache::removeThumbnails()
0332 {
0333     d->thumbnailImageCache.clear();
0334     d->thumbnailPixmapCache.clear();
0335 }
0336 
0337 void LoadingCache::setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps)
0338 {
0339     d->thumbnailImageCache.setMaxCost(numberOfQImages *
0340                                       ThumbnailSize::maxThumbsSize() *
0341                                       ThumbnailSize::maxThumbsSize() * 4);
0342 
0343     d->thumbnailPixmapCache.setMaxCost(numberOfQPixmaps *
0344                                        ThumbnailSize::maxThumbsSize() *
0345                                        ThumbnailSize::maxThumbsSize() * QPixmap::defaultDepth() / 8);
0346 }
0347 
0348 void LoadingCache::setFileWatch(LoadingCacheFileWatch* const watch)
0349 {
0350     delete d->watch;
0351     d->watch          = watch;
0352     d->watch->m_cache = this;
0353 }
0354 
0355 void LoadingCache::notifyFileChanged(const QString& filePath, bool notify)
0356 {
0357     QList<QString> keys = d->imageFilePathHash.values(filePath);
0358 
0359     Q_FOREACH (const QString& cacheKey, keys)
0360     {
0361         d->imageCache.remove(cacheKey);
0362     }
0363 
0364     keys = d->thumbnailFilePathHash.values(filePath);
0365 
0366     Q_FOREACH (const QString& cacheKey, keys)
0367     {
0368         d->thumbnailImageCache.remove(cacheKey);
0369         d->thumbnailPixmapCache.remove(cacheKey);
0370     }
0371 
0372     if (notify)
0373     {
0374         Q_EMIT fileChanged(filePath);
0375     }
0376 }
0377 
0378 void LoadingCache::iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous)
0379 {
0380     if ((current.enableCM           != previous.enableCM)           ||
0381         (current.useManagedPreviews != previous.useManagedPreviews) ||
0382         (current.monitorProfile     != previous.monitorProfile))
0383     {
0384         LoadingCache::CacheLock lock(this);
0385         removeImages();
0386         removeThumbnails();
0387     }
0388 }
0389 
0390 //---------------------------------------------------------------------------------------------------
0391 
0392 LoadingCacheFileWatch::LoadingCacheFileWatch()
0393     : m_cache(nullptr)
0394 {
0395 }
0396 
0397 LoadingCacheFileWatch::~LoadingCacheFileWatch()
0398 {
0399     if (m_cache)
0400     {
0401         LoadingCache::CacheLock lock(m_cache);
0402 
0403         if (m_cache->d->watch == this)
0404         {
0405             m_cache->d->watch = nullptr;
0406         }
0407     }
0408 }
0409 
0410 void LoadingCacheFileWatch::addedImage(const QString& filePath)
0411 {
0412     if (!m_cache || filePath.isEmpty())
0413     {
0414         return;
0415     }
0416 
0417     QFileInfo info(filePath);
0418 
0419     m_watchHash.insert(filePath, qMakePair(info.size(),
0420                                            info.lastModified()));
0421 }
0422 
0423 void LoadingCacheFileWatch::removeImage(const QString& filePath)
0424 {
0425     m_watchHash.remove(filePath);
0426 }
0427 
0428 void LoadingCacheFileWatch::checkFileWatch(const QString& filePath)
0429 {
0430     if (!m_cache || filePath.isEmpty())
0431     {
0432         return;
0433     }
0434 
0435     if (m_watchHash.contains(filePath))
0436     {
0437         QFileInfo info(filePath);
0438         QPair<qint64, QDateTime> pair = m_watchHash.value(filePath);
0439 
0440         if ((info.size()         != pair.first) ||
0441             (info.lastModified() != pair.second))
0442         {
0443             qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingCache file dirty:" << filePath;
0444 
0445             m_cache->notifyFileChanged(filePath);
0446             m_watchHash.remove(filePath);
0447         }
0448     }
0449 }
0450 
0451 void LoadingCacheFileWatch::notifyFileChanged(const QString& filePath)
0452 {
0453     if (m_cache)
0454     {
0455         LoadingCache::CacheLock lock(m_cache);
0456         m_cache->notifyFileChanged(filePath);
0457     }
0458 }
0459 
0460 //---------------------------------------------------------------------------------------------------
0461 
0462 LoadingCache::CacheLock::CacheLock(LoadingCache* const cache)
0463     : m_cache(cache)
0464 {
0465     m_cache->d->mutex.lock();
0466 }
0467 
0468 LoadingCache::CacheLock::~CacheLock()
0469 {
0470     m_cache->d->mutex.unlock();
0471 }
0472 
0473 void LoadingCache::CacheLock::wakeAll()
0474 {
0475     // obviously the mutex is locked when this function is called
0476 
0477     m_cache->d->condVar.wakeAll();
0478 }
0479 
0480 void LoadingCache::CacheLock::timedWait()
0481 {
0482     // same as above, the mutex is certainly locked
0483 
0484     m_cache->d->condVar.wait(&m_cache->d->mutex, 1000);
0485 }
0486 
0487 } // namespace Digikam
0488 
0489 #include "moc_loadingcache.cpp"