"Fossies" - the Fresh Open Source Software Archive

Member "digikam-6.3.0/core/libs/threadimageio/fileio/loadingcache.cpp" (4 Sep 2019, 14482 Bytes) of package /linux/misc/digikam-6.3.0.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "loadingcache.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.2.0_vs_6.3.0.

    1 /* ============================================================
    2  *
    3  * This file is a part of digiKam project
    4  * https://www.digikam.org
    5  *
    6  * Date        : 2006-01-11
    7  * Description : shared image loading and caching
    8  *
    9  * Copyright (C) 2005-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
   10  *
   11  * This program is free software; you can redistribute it
   12  * and/or modify it under the terms of the GNU General
   13  * Public License as published by the Free Software Foundation;
   14  * either version 2, or (at your option)
   15  * any later version.
   16  *
   17  * This program is distributed in the hope that it will be useful,
   18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20  * GNU General Public License for more details.
   21  *
   22  * ============================================================ */
   23 
   24 #include "loadingcache.h"
   25 
   26 // Qt includes
   27 
   28 #include <QCoreApplication>
   29 #include <QEvent>
   30 #include <QCache>
   31 #include <QMap>
   32 
   33 // Local includes
   34 
   35 #include "digikam_debug.h"
   36 #include "iccsettings.h"
   37 #include "kmemoryinfo.h"
   38 #include "dmetadata.h"
   39 #include "thumbnailsize.h"
   40 
   41 namespace Digikam
   42 {
   43 
   44 class Q_DECL_HIDDEN LoadingCache::Private
   45 {
   46 public:
   47 
   48     explicit Private(LoadingCache* const q)
   49         : q(q)
   50     {
   51         // Note: Don't make the mutex recursive, we need to use a wait condition on it
   52         watch = nullptr;
   53     }
   54 
   55     void mapImageFilePath(const QString& filePath, const QString& cacheKey);
   56     void mapThumbnailFilePath(const QString& filePath, const QString& cacheKey);
   57     void cleanUpImageFilePathHash();
   58     void cleanUpThumbnailFilePathHash();
   59     LoadingCacheFileWatch* fileWatch() const;
   60 
   61 public:
   62 
   63     QCache<QString, DImg>           imageCache;
   64     QCache<QString, QImage>         thumbnailImageCache;
   65     QCache<QString, QPixmap>        thumbnailPixmapCache;
   66     QMultiMap<QString, QString>     imageFilePathHash;
   67     QMultiMap<QString, QString>     thumbnailFilePathHash;
   68     QMap<QString, LoadingProcess*>  loadingDict;
   69     QMutex                          mutex;
   70     QWaitCondition                  condVar;
   71     LoadingCacheFileWatch*          watch;
   72     LoadingCache*                   q;
   73 };
   74 
   75 LoadingCacheFileWatch* LoadingCache::Private::fileWatch() const
   76 {
   77     // install default watch if no watch is set yet
   78     if (!watch)
   79     {
   80         q->setFileWatch(new ClassicLoadingCacheFileWatch);
   81     }
   82 
   83     return watch;
   84 }
   85 
   86 void LoadingCache::Private::mapImageFilePath(const QString& filePath, const QString& cacheKey)
   87 {
   88     if (imageFilePathHash.size() > 5*imageCache.size())
   89     {
   90         cleanUpImageFilePathHash();
   91     }
   92 
   93     imageFilePathHash.insert(filePath, cacheKey);
   94 }
   95 
   96 void LoadingCache::Private::mapThumbnailFilePath(const QString& filePath, const QString& cacheKey)
   97 {
   98     if (thumbnailFilePathHash.size() > 5*(thumbnailImageCache.size() + thumbnailPixmapCache.size()))
   99     {
  100         cleanUpThumbnailFilePathHash();
  101     }
  102 
  103     thumbnailFilePathHash.insert(filePath, cacheKey);
  104 }
  105 
  106 void LoadingCache::Private::cleanUpImageFilePathHash()
  107 {
  108     // Remove all entries from hash whose value is no longer a key in the cache
  109     QSet<QString> keys = imageCache.keys().toSet();
  110     QMultiMap<QString, QString>::iterator it;
  111 
  112     for (it = imageFilePathHash.begin() ; it != imageFilePathHash.end() ; )
  113     {
  114         if (!keys.contains(it.value()))
  115         {
  116             it = imageFilePathHash.erase(it);
  117         }
  118         else
  119         {
  120             ++it;
  121         }
  122     }
  123 }
  124 
  125 void LoadingCache::Private::cleanUpThumbnailFilePathHash()
  126 {
  127     QSet<QString> keys;
  128     keys += thumbnailImageCache.keys().toSet();
  129     keys += thumbnailPixmapCache.keys().toSet();
  130     QMultiMap<QString, QString>::iterator it;
  131 
  132     for (it = thumbnailFilePathHash.begin() ; it != thumbnailFilePathHash.end() ; )
  133     {
  134         if (!keys.contains(it.value()))
  135         {
  136             it = thumbnailFilePathHash.erase(it);
  137         }
  138         else
  139         {
  140             ++it;
  141         }
  142     }
  143 }
  144 
  145 LoadingCache* LoadingCache::m_instance = nullptr;
  146 
  147 LoadingCache* LoadingCache::cache()
  148 {
  149     if (!m_instance)
  150     {
  151         m_instance = new LoadingCache;
  152     }
  153 
  154     return m_instance;
  155 }
  156 
  157 void LoadingCache::cleanUp()
  158 {
  159     delete m_instance;
  160 }
  161 
  162 LoadingCache::LoadingCache()
  163     : d(new Private(this))
  164 {
  165     KMemoryInfo memory = KMemoryInfo::currentInfo();
  166     setCacheSize(qBound(60, int(memory.megabytes(KMemoryInfo::TotalRam)*0.05), 200));
  167     setThumbnailCacheSize(5, 100); // the pixmap number should not be based on system memory, it's graphics memory
  168 
  169     // good place to call it here as LoadingCache is a singleton
  170     qRegisterMetaType<LoadingDescription>("LoadingDescription");
  171     qRegisterMetaType<DImg>("DImg");
  172     qRegisterMetaType<DMetadata>("DMetadata");
  173 
  174     connect(IccSettings::instance(), SIGNAL(settingsChanged(ICCSettingsContainer,ICCSettingsContainer)),
  175             this, SLOT(iccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer)));
  176 }
  177 
  178 LoadingCache::~LoadingCache()
  179 {
  180     delete d->watch;
  181     delete d;
  182     m_instance = nullptr;
  183 }
  184 
  185 DImg* LoadingCache::retrieveImage(const QString& cacheKey) const
  186 {
  187     return d->imageCache[cacheKey];
  188 }
  189 
  190 bool LoadingCache::putImage(const QString& cacheKey, const DImg& img, const QString& filePath) const
  191 {
  192     int cost                 = img.numBytes();
  193     bool successfulyInserted = d->imageCache.insert(cacheKey, new DImg(img), cost);
  194 
  195     if (successfulyInserted && !filePath.isEmpty())
  196     {
  197         d->mapImageFilePath(filePath, cacheKey);
  198         d->fileWatch()->addedImage(filePath);
  199     }
  200 
  201     return successfulyInserted;
  202 }
  203 
  204 void LoadingCache::removeImage(const QString& cacheKey)
  205 {
  206     d->imageCache.remove(cacheKey);
  207 }
  208 
  209 void LoadingCache::removeImages()
  210 {
  211     d->imageCache.clear();
  212 }
  213 
  214 bool LoadingCache::isCacheable(const DImg& img) const
  215 {
  216     // return whether image fits in cache
  217     return (uint)d->imageCache.maxCost() >= img.numBytes();
  218 }
  219 
  220 void LoadingCache::addLoadingProcess(LoadingProcess* const process)
  221 {
  222     d->loadingDict[process->cacheKey()] = process;
  223 }
  224 
  225 LoadingProcess* LoadingCache::retrieveLoadingProcess(const QString& cacheKey) const
  226 {
  227     return d->loadingDict.value(cacheKey);
  228 }
  229 
  230 void LoadingCache::removeLoadingProcess(LoadingProcess* const process)
  231 {
  232     d->loadingDict.remove(process->cacheKey());
  233 }
  234 
  235 void LoadingCache::notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description)
  236 {
  237     for (QMap<QString, LoadingProcess*>::const_iterator it = d->loadingDict.constBegin() ;
  238          it != d->loadingDict.constEnd() ; ++it)
  239     {
  240         it.value()->notifyNewLoadingProcess(process, description);
  241     }
  242 }
  243 
  244 void LoadingCache::setCacheSize(int megabytes)
  245 {
  246     qCDebug(DIGIKAM_GENERAL_LOG) << "Allowing a cache size of" << megabytes << "MB";
  247     d->imageCache.setMaxCost(megabytes * 1024 * 1024);
  248 }
  249 
  250 // --- Thumbnails ----
  251 
  252 const QImage* LoadingCache::retrieveThumbnail(const QString& cacheKey) const
  253 {
  254     return d->thumbnailImageCache[cacheKey];
  255 }
  256 
  257 const QPixmap* LoadingCache::retrieveThumbnailPixmap(const QString& cacheKey) const
  258 {
  259     return d->thumbnailPixmapCache[cacheKey];
  260 }
  261 
  262 bool LoadingCache::hasThumbnailPixmap(const QString& cacheKey) const
  263 {
  264     return d->thumbnailPixmapCache.contains(cacheKey);
  265 }
  266 
  267 void LoadingCache::putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath)
  268 {
  269 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
  270     int cost = thumb.sizeInBytes();
  271 #else
  272     int cost = thumb.byteCount();
  273 #endif
  274 
  275     if (d->thumbnailImageCache.insert(cacheKey, new QImage(thumb), cost))
  276     {
  277         d->mapThumbnailFilePath(filePath, cacheKey);
  278         d->fileWatch()->addedThumbnail(filePath);
  279     }
  280 }
  281 
  282 void LoadingCache::putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath)
  283 {
  284     int cost = thumb.width() * thumb.height() * thumb.depth() / 8;
  285 
  286     if (d->thumbnailPixmapCache.insert(cacheKey, new QPixmap(thumb), cost))
  287     {
  288         d->mapThumbnailFilePath(filePath, cacheKey);
  289         d->fileWatch()->addedThumbnail(filePath);
  290     }
  291 }
  292 
  293 void LoadingCache::removeThumbnail(const QString& cacheKey)
  294 {
  295     d->thumbnailImageCache.remove(cacheKey);
  296     d->thumbnailPixmapCache.remove(cacheKey);
  297 }
  298 
  299 void LoadingCache::removeThumbnails()
  300 {
  301     d->thumbnailImageCache.clear();
  302     d->thumbnailPixmapCache.clear();
  303 }
  304 
  305 void LoadingCache::setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps)
  306 {
  307     d->thumbnailImageCache.setMaxCost(numberOfQImages *
  308                                       ThumbnailSize::maxThumbsSize() *
  309                                       ThumbnailSize::maxThumbsSize() * 4);
  310 
  311     d->thumbnailPixmapCache.setMaxCost(numberOfQPixmaps *
  312                                        ThumbnailSize::maxThumbsSize() *
  313                                        ThumbnailSize::maxThumbsSize() * QPixmap::defaultDepth() / 8);
  314 }
  315 
  316 void LoadingCache::setFileWatch(LoadingCacheFileWatch* const watch)
  317 {
  318     delete d->watch;
  319     d->watch          = watch;
  320     d->watch->m_cache = this;
  321 }
  322 
  323 QStringList LoadingCache::imageFilePathsInCache() const
  324 {
  325     d->cleanUpImageFilePathHash();
  326     return d->imageFilePathHash.uniqueKeys();
  327 }
  328 
  329 QStringList LoadingCache::thumbnailFilePathsInCache() const
  330 {
  331     d->cleanUpThumbnailFilePathHash();
  332     return d->thumbnailFilePathHash.uniqueKeys();
  333 }
  334 
  335 void LoadingCache::notifyFileChanged(const QString& filePath, bool notify)
  336 {
  337     QList<QString> keys = d->imageFilePathHash.values(filePath);
  338 
  339     foreach (const QString& cacheKey, keys)
  340     {
  341         if (d->imageCache.remove(cacheKey) && notify)
  342         {
  343             emit fileChanged(filePath, cacheKey);
  344         }
  345     }
  346 
  347     keys = d->thumbnailFilePathHash.values(filePath);
  348 
  349     foreach (const QString& cacheKey, keys)
  350     {
  351         bool removedImage  = d->thumbnailImageCache.remove(cacheKey);
  352         bool removedPixmap = d->thumbnailPixmapCache.remove(cacheKey);
  353 
  354         if ((removedImage || removedPixmap) && notify)
  355         {
  356             emit fileChanged(filePath, cacheKey);
  357         }
  358     }
  359 
  360     if (notify)
  361     {
  362         emit fileChanged(filePath);
  363     }
  364 }
  365 
  366 void LoadingCache::iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous)
  367 {
  368     if (current.enableCM           != previous.enableCM           ||
  369         current.useManagedPreviews != previous.useManagedPreviews ||
  370         current.monitorProfile     != previous.monitorProfile)
  371     {
  372         LoadingCache::CacheLock lock(this);
  373         removeImages();
  374         removeThumbnails();
  375     }
  376 }
  377 
  378 //---------------------------------------------------------------------------------------------------
  379 
  380 LoadingCacheFileWatch::~LoadingCacheFileWatch()
  381 {
  382     if (m_cache)
  383     {
  384         LoadingCache::CacheLock lock(m_cache);
  385 
  386         if (m_cache->d->watch == this)
  387         {
  388             m_cache->d->watch = nullptr;
  389         }
  390     }
  391 }
  392 
  393 void LoadingCacheFileWatch::notifyFileChanged(const QString& filePath)
  394 {
  395     if (m_cache)
  396     {
  397         LoadingCache::CacheLock lock(m_cache);
  398         m_cache->notifyFileChanged(filePath);
  399     }
  400 }
  401 
  402 void LoadingCacheFileWatch::addedImage(const QString&)
  403 {
  404     // default: do nothing
  405 }
  406 
  407 void LoadingCacheFileWatch::addedThumbnail(const QString&)
  408 {
  409     // default: do nothing
  410 }
  411 
  412 //---------------------------------------------------------------------------------------------------
  413 
  414 ClassicLoadingCacheFileWatch::ClassicLoadingCacheFileWatch()
  415 {
  416     if (thread() != QCoreApplication::instance()->thread())
  417     {
  418         moveToThread(QCoreApplication::instance()->thread());
  419     }
  420 
  421     m_watch = new QFileSystemWatcher;
  422 
  423     connect(m_watch, SIGNAL(fileChanged(QString)),
  424             this, SLOT(slotFileDirty(QString)));
  425 
  426     // Make sure the signal gets here directly from the event loop.
  427     // If putImage is called from the main thread, with CacheLock,
  428     // a deadlock would result (mutex is not recursive)
  429     connect(this, SIGNAL(signalUpdateDirWatch()),
  430             this, SLOT(slotUpdateDirWatch()),
  431             Qt::QueuedConnection);
  432 
  433 }
  434 
  435 ClassicLoadingCacheFileWatch::~ClassicLoadingCacheFileWatch()
  436 {
  437     delete m_watch;
  438 }
  439 
  440 void ClassicLoadingCacheFileWatch::addedImage(const QString& filePath)
  441 {
  442     Q_UNUSED(filePath)
  443     // schedule update of file watch
  444     // QFileSystemWatch can only be accessed from main thread!
  445     emit signalUpdateDirWatch();
  446 }
  447 
  448 void ClassicLoadingCacheFileWatch::addedThumbnail(const QString& filePath)
  449 {
  450     Q_UNUSED(filePath);
  451     // ignore, we do not watch thumbnails
  452 }
  453 
  454 void ClassicLoadingCacheFileWatch::slotFileDirty(const QString& path)
  455 {
  456     // Signal comes from main thread
  457     qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingCache slotFileDirty " << path;
  458     // This method acquires a lock itself
  459     notifyFileChanged(path);
  460     // No need for locking here, we are in main thread
  461     m_watch->removePath(path);
  462     m_watchedFiles.remove(path);
  463 }
  464 
  465 void ClassicLoadingCacheFileWatch::slotUpdateDirWatch()
  466 {
  467     // Event comes from main thread, we need to lock ourselves.
  468     LoadingCache::CacheLock lock(m_cache);
  469 
  470     // get a list of files in cache that need watch
  471     QSet<QString> toBeAdded;
  472     QSet<QString> toBeRemoved = m_watchedFiles;
  473     QList<QString> filePaths  = m_cache->imageFilePathsInCache();
  474 
  475     foreach (const QString& m_watchPath, filePaths)
  476     {
  477         if (!m_watchPath.isEmpty())
  478         {
  479             if (!m_watchedFiles.contains(m_watchPath))
  480             {
  481                 toBeAdded.insert(m_watchPath);
  482             }
  483 
  484             toBeRemoved.remove(m_watchPath);
  485         }
  486     }
  487 
  488     foreach (const QString& watchedItem, toBeRemoved)
  489     {
  490         //qCDebug(DIGIKAM_GENERAL_LOG) << "removing watch for " << *it;
  491         m_watch->removePath(watchedItem);
  492         m_watchedFiles.remove(watchedItem);
  493     }
  494 
  495     foreach (const QString& watchedItem, toBeAdded)
  496     {
  497         //qCDebug(DIGIKAM_GENERAL_LOG) << "adding watch for " << *it;
  498         m_watch->addPath(watchedItem);
  499         m_watchedFiles.insert(watchedItem);
  500     }
  501 }
  502 
  503 //---------------------------------------------------------------------------------------------------
  504 
  505 LoadingCache::CacheLock::CacheLock(LoadingCache* const cache)
  506     : m_cache(cache)
  507 {
  508     m_cache->d->mutex.lock();
  509 }
  510 
  511 LoadingCache::CacheLock::~CacheLock()
  512 {
  513     m_cache->d->mutex.unlock();
  514 }
  515 
  516 void LoadingCache::CacheLock::wakeAll()
  517 {
  518     // obviously the mutex is locked when this function is called
  519     m_cache->d->condVar.wakeAll();
  520 }
  521 
  522 void LoadingCache::CacheLock::timedWait()
  523 {
  524     // same as above, the mutex is certainly locked
  525     m_cache->d->condVar.wait(&m_cache->d->mutex, 1000);
  526 }
  527 
  528 } // namespace Digikam