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"