diff --git a/core/libs/database/utils/dio.cpp b/core/libs/database/utils/dio.cpp index 372d54d37e..2bd73cbcab 100644 --- a/core/libs/database/utils/dio.cpp +++ b/core/libs/database/utils/dio.cpp @@ -1,638 +1,618 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-05-17 * Description : low level files management interface. * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2012-2013 by Marcel Wiesweg * Copyright (C) 2015 by Mohamed_Anwer * Copyright (C) 2018 by Maik Qualmann * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dio.h" // Qt includes #include // Local includes #include "digikam_debug.h" #include "imageinfo.h" #include "albummanager.h" #include "coredb.h" #include "coredbaccess.h" #include "album.h" #include "dmetadata.h" #include "metadatasettings.h" #include "scancontroller.h" #include "thumbsdb.h" #include "thumbsdbaccess.h" #include "iojobsmanager.h" #include "collectionmanager.h" #include "dnotificationwrapper.h" +#include "loadingcacheinterface.h" #include "progressmanager.h" #include "digikamapp.h" #include "iojobdata.h" namespace Digikam { SidecarFinder::SidecarFinder(const QList& files) { foreach(const QUrl& url, files) { if (DMetadata::hasSidecar(url.toLocalFile())) { localFiles << DMetadata::sidecarUrl(url); localFileSuffixes << QLatin1String(".xmp"); qCDebug(DIGIKAM_DATABASE_LOG) << "Detected a sidecar" << localFiles.last(); } foreach(QString suffix, MetadataSettings::instance()->settings().sidecarExtensions) { suffix = QLatin1String(".") + suffix; QString sidecarName = url.toLocalFile() + suffix; if (QFileInfo::exists(sidecarName) && !localFiles.contains(QUrl::fromLocalFile(sidecarName))) { localFiles << QUrl::fromLocalFile(sidecarName); localFileSuffixes << suffix; qCDebug(DIGIKAM_DATABASE_LOG) << "Detected a sidecar" << localFiles.last(); } } localFiles << url; localFileSuffixes << QString(); } } // ------------------------------------------------------------------------------------------------ // TODO // Groups should not be resolved in dio, it should be handled in views. // This is already done for most things except for drag&drop, which is hard :) GroupedImagesFinder::GroupedImagesFinder(const QList& source) { QSet ids; foreach(const ImageInfo& info, source) { ids << info.id(); } infos.reserve(source.size()); foreach(const ImageInfo& info, source) { infos << info; if (info.hasGroupedImages()) { foreach(const ImageInfo& groupedImage, info.groupedImages()) { if (ids.contains(groupedImage.id())) { continue; } infos << groupedImage; ids << groupedImage.id(); } } } } // ------------------------------------------------------------------------------------------------ class DIOCreator { public: DIO object; }; Q_GLOBAL_STATIC(DIOCreator, creator) // ------------------------------------------------------------------------------------------------ DIO* DIO::instance() { return &creator->object; } DIO::DIO() { } DIO::~DIO() { } void DIO::cleanUp() { } // Album -> Album ----------------------------------------------------- void DIO::copy(PAlbum* const src, PAlbum* const dest) { if (!src || !dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyAlbum, src, dest)); } void DIO::move(PAlbum* const src, PAlbum* const dest) { if (!src || !dest) { return; } #ifdef Q_OS_WIN AlbumManager::instance()->removeWatchedPAlbums(src); #endif instance()->processJob(new IOJobData(IOJobData::MoveAlbum, src, dest)); } // Images -> Album ---------------------------------------------------- void DIO::copy(const QList& infos, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyImage, infos, dest)); } void DIO::move(const QList& infos, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::MoveImage, infos, dest)); } // External files -> album -------------------------------------------- void DIO::copy(const QUrl& src, PAlbum* const dest) { copy(QList() << src, dest); } void DIO::copy(const QList& srcList, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyFiles, srcList, dest)); } void DIO::move(const QUrl& src, PAlbum* const dest) { move(QList() << src, dest); } void DIO::move(const QList& srcList, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::MoveFiles, srcList, dest)); } // Rename -------------------------------------------------------------- void DIO::rename(const ImageInfo& info, const QString& newName, bool overwrite) { instance()->processJob(new IOJobData(IOJobData::Rename, info, newName, overwrite)); } // Delete -------------------------------------------------------------- void DIO::del(const QList& infos, bool useTrash) { instance()->processJob(new IOJobData(useTrash ? IOJobData::Trash : IOJobData::Delete, infos)); } void DIO::del(const ImageInfo& info, bool useTrash) { del(QList() << info, useTrash); } void DIO::del(PAlbum* const album, bool useTrash) { if (!album) { return; } #ifdef Q_OS_WIN AlbumManager::instance()->removeWatchedPAlbums(album); #endif instance()->createJob(new IOJobData(useTrash ? IOJobData::Trash : IOJobData::Delete, album)); } // ------------------------------------------------------------------------------------------------ void DIO::processJob(IOJobData* const data) { const int operation = data->operation(); if (operation == IOJobData::CopyImage || operation == IOJobData::MoveImage) { // this is a fast db operation, do here GroupedImagesFinder finder(data->imageInfos()); data->setImageInfos(finder.infos); QStringList filenames; QList ids; foreach(const ImageInfo& info, data->imageInfos()) { filenames << info.name(); ids << info.id(); } ScanController::instance()->hintAtMoveOrCopyOfItems(ids, data->destAlbum(), filenames); } else if (operation == IOJobData::CopyAlbum || operation == IOJobData::MoveAlbum) { ScanController::instance()->hintAtMoveOrCopyOfAlbum(data->srcAlbum(), data->destAlbum()); createJob(data); return; } else if (operation == IOJobData::Delete || operation == IOJobData::Trash) { qCDebug(DIGIKAM_DATABASE_LOG) << "Number of files to be deleted:" << data->sourceUrls().count(); } SidecarFinder finder(data->sourceUrls()); data->setSourceUrls(finder.localFiles); if (operation == IOJobData::Rename) { if (!data->imageInfos().isEmpty()) { ImageInfo info = data->imageInfos().first(); PAlbum* const album = AlbumManager::instance()->findPAlbum(info.albumId()); if (album) { ScanController::instance()->hintAtMoveOrCopyOfItem(info.id(), album, data->destUrl().fileName()); } for (int i = 0 ; i < finder.localFiles.length() ; ++i) { data->setDestUrl(finder.localFiles.at(i), QUrl::fromLocalFile(data->destUrl().toLocalFile() + finder.localFileSuffixes.at(i))); } } } createJob(data); } void DIO::createJob(IOJobData* const data) { if (data->sourceUrls().isEmpty()) { delete data; return; } ProgressItem* item = 0; QString itemString = getItemString(data); if (!itemString.isEmpty()) { item = ProgressManager::instance()->createProgressItem(itemString, QString(), true, false); item->setTotalItems(data->sourceUrls().count()); data->setProgressId(item->id()); } IOJobsThread* const jobThread = IOJobsManager::instance()->startIOJobs(data); connect(jobThread, SIGNAL(signalOneProccessed(QUrl)), this, SLOT(slotOneProccessed(QUrl)), Qt::QueuedConnection); connect(jobThread, SIGNAL(finished()), this, SLOT(slotResult())); if (data->operation() == IOJobData::Rename) { connect(jobThread, SIGNAL(signalRenameFailed(QUrl)), this, SIGNAL(signalRenameFailed(QUrl))); } if (item) { connect(item, SIGNAL(progressItemCanceled(ProgressItem*)), jobThread, SLOT(slotCancel())); connect(item, SIGNAL(progressItemCanceled(ProgressItem*)), this, SLOT(slotCancel(ProgressItem*))); } } void DIO::slotResult() { IOJobsThread* const jobThread = dynamic_cast(sender()); if (!jobThread || !jobThread->jobData()) { return; } IOJobData* const data = jobThread->jobData(); if (jobThread->hasErrors() && data->operation() != IOJobData::Rename) { // Pop-up a message about the error. QString errors = jobThread->errorsList().join(QLatin1String("\n")); DNotificationWrapper(QString(), errors, DigikamApp::instance(), DigikamApp::instance()->windowTitle()); } slotCancel(getProgressItem(data)); } void DIO::slotOneProccessed(const QUrl& url) { IOJobsThread* const jobThread = dynamic_cast(sender()); if (!jobThread || !jobThread->jobData()) { return; } IOJobData* const data = jobThread->jobData(); const int operation = data->operation(); if (operation == IOJobData::MoveImage) { ImageInfo info = data->findImageInfo(url); if (!info.isNull() && data->destAlbum()) { CoreDbAccess().db()->moveItem(info.albumId(), info.name(), data->destAlbum()->id(), info.name()); } } else if (operation == IOJobData::Delete) { // Mark the images as obsolete and remove them // from their album and from the grouped PAlbum* const album = data->srcAlbum(); CoreDbAccess access; if (album && album->fileUrl() == url) { // get all deleted albums QList albumsToDelete; QList imagesToRemove; addAlbumChildrenToList(albumsToDelete, album); foreach(int albumId, albumsToDelete) { imagesToRemove << access.db()->getItemIDsInAlbum(albumId); } foreach(const qlonglong& imageId, imagesToRemove) { access.db()->removeAllImageRelationsFrom(imageId, DatabaseRelation::Grouped); } access.db()->removeItemsPermanently(imagesToRemove, albumsToDelete); } else { ImageInfo info = data->findImageInfo(url); if (!info.isNull()) { CoreDbAccess access; access.db()->removeAllImageRelationsFrom(info.id(), DatabaseRelation::Grouped); access.db()->removeItemsPermanently(QList() << info.id(), QList() << info.albumId()); } } } else if (operation == IOJobData::Trash) { ImageInfo info = data->findImageInfo(url); if (!info.isNull()) { CoreDbAccess().db()->removeItems(QList() << info.id(), QList() << info.albumId()); } } else if (operation == IOJobData::Rename) { ImageInfo info = data->findImageInfo(url); if (!info.isNull()) { QString oldPath = url.toLocalFile(); QString newName = data->destUrl(url).fileName(); QString newPath = data->destUrl(url).toLocalFile(); if (data->overwrite()) { ThumbsDbAccess().db()->removeByFilePath(newPath); CoreDbAccess().db()->deleteItem(info.albumId(), newName); } ThumbsDbAccess().db()->renameByFilePath(oldPath, newPath); info.setName(newName); // Remove old thumbnails and images from the cache - { - LoadingCache* const cache = LoadingCache::cache(); - LoadingCache::CacheLock lock(cache); - QStringList possibleKeys = LoadingDescription::possibleThumbnailCacheKeys(oldPath); - - if (data->overwrite()) - { - possibleKeys << LoadingDescription::possibleThumbnailCacheKeys(newPath); - } - - foreach(const QString& cacheKey, possibleKeys) - { - cache->removeThumbnail(cacheKey); - } - - possibleKeys = LoadingDescription::possibleCacheKeys(oldPath); + LoadingCacheInterface::fileChanged(oldPath, false); - if (data->overwrite()) - { - possibleKeys << LoadingDescription::possibleCacheKeys(newPath); - } - - foreach(const QString& cacheKey, possibleKeys) - { - cache->removeImage(cacheKey); - } + if (data->overwrite()) + { + LoadingCacheInterface::fileChanged(newPath, false); } } emit signalRenameSucceeded(url); } // Scan folders for changes QString scanPath; if (operation == IOJobData::CopyImage || operation == IOJobData::CopyAlbum || operation == IOJobData::CopyFiles || operation == IOJobData::MoveImage || operation == IOJobData::MoveAlbum || operation == IOJobData::MoveFiles) { scanPath = data->destUrl().toLocalFile(); } else if (operation == IOJobData::Delete || operation == IOJobData::Trash) { PAlbum* const album = data->srcAlbum(); if (album) { PAlbum* const parent = dynamic_cast(album->parent()); if (parent) { scanPath = parent->fileUrl().toLocalFile(); } } else { scanPath = url.adjusted(QUrl::RemoveFilename).toLocalFile(); } } if (!scanPath.isEmpty()) { ScanController::instance()->scheduleCollectionScanRelaxed(scanPath); } ProgressItem* const item = getProgressItem(data); if (item) { item->advance(1); } } QString DIO::getItemString(IOJobData* const data) const { switch (data->operation()) { case IOJobData::CopyAlbum: return i18n("Copy Album"); case IOJobData::CopyImage: return i18n("Copy Images"); case IOJobData::CopyFiles: return i18n("Copy Files"); case IOJobData::MoveAlbum: return i18n("Move Album"); case IOJobData::MoveImage: return i18n("Move Images"); case IOJobData::MoveFiles: return i18n("Move Files"); case IOJobData::Trash: return i18n("Trash"); case IOJobData::Delete: return i18n("Delete"); default: break; } return QString(); } ProgressItem* DIO::getProgressItem(IOJobData* const data) const { QString itemId = data->getProgressId(); if (itemId.isEmpty()) { return 0; } return ProgressManager::instance()->findItembyId(itemId); } void DIO::slotCancel(ProgressItem* item) { if (item) { item->setComplete(); } } void DIO::addAlbumChildrenToList(QList& list, Album* const album) { // simple recursive helper function if (album) { if (!list.contains(album->id())) { list.append(album->id()); } AlbumIterator it(album); while (it.current()) { addAlbumChildrenToList(list, *it); ++it; } } } } // namespace Digikam diff --git a/core/libs/threadimageio/loadingcache.cpp b/core/libs/threadimageio/loadingcache.cpp index 6cc14cd1c8..3fa60a025b 100644 --- a/core/libs/threadimageio/loadingcache.cpp +++ b/core/libs/threadimageio/loadingcache.cpp @@ -1,519 +1,522 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-01-11 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "loadingcache.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "iccsettings.h" #include "kmemoryinfo.h" #include "dmetadata.h" #include "thumbnailsize.h" namespace Digikam { class LoadingCache::Private { public: explicit Private(LoadingCache* const q) : q(q) { // Note: Don't make the mutex recursive, we need to use a wait condition on it watch = 0; } void mapImageFilePath(const QString& filePath, const QString& cacheKey); void mapThumbnailFilePath(const QString& filePath, const QString& cacheKey); void cleanUpImageFilePathHash(); void cleanUpThumbnailFilePathHash(); LoadingCacheFileWatch* fileWatch() const; public: QCache imageCache; QCache thumbnailImageCache; QCache thumbnailPixmapCache; QMultiMap imageFilePathHash; QMultiMap thumbnailFilePathHash; QMap loadingDict; QMutex mutex; QWaitCondition condVar; LoadingCacheFileWatch* watch; LoadingCache* q; }; LoadingCacheFileWatch* LoadingCache::Private::fileWatch() const { // install default watch if no watch is set yet if (!watch) { q->setFileWatch(new ClassicLoadingCacheFileWatch); } return watch; } void LoadingCache::Private::mapImageFilePath(const QString& filePath, const QString& cacheKey) { if (imageFilePathHash.size() > 5*imageCache.size()) { cleanUpImageFilePathHash(); } imageFilePathHash.insert(filePath, cacheKey); } void LoadingCache::Private::mapThumbnailFilePath(const QString& filePath, const QString& cacheKey) { if (thumbnailFilePathHash.size() > 5*(thumbnailImageCache.size() + thumbnailPixmapCache.size())) { cleanUpThumbnailFilePathHash(); } thumbnailFilePathHash.insert(filePath, cacheKey); } void LoadingCache::Private::cleanUpImageFilePathHash() { // Remove all entries from hash whose value is no longer a key in the cache QSet keys = imageCache.keys().toSet(); QMultiMap::iterator it; for (it = imageFilePathHash.begin(); it != imageFilePathHash.end(); ) { if (!keys.contains(it.value())) { it = imageFilePathHash.erase(it); } else { ++it; } } } void LoadingCache::Private::cleanUpThumbnailFilePathHash() { QSet keys; keys += thumbnailImageCache.keys().toSet(); keys += thumbnailPixmapCache.keys().toSet(); QMultiMap::iterator it; for (it = thumbnailFilePathHash.begin(); it != thumbnailFilePathHash.end(); ) { if (!keys.contains(it.value())) { it = thumbnailFilePathHash.erase(it); } else { ++it; } } } LoadingCache* LoadingCache::m_instance = 0; LoadingCache* LoadingCache::cache() { if (!m_instance) { m_instance = new LoadingCache; } return m_instance; } void LoadingCache::cleanUp() { delete m_instance; } LoadingCache::LoadingCache() : d(new Private(this)) { KMemoryInfo memory = KMemoryInfo::currentInfo(); setCacheSize(qBound(60, int(memory.megabytes(KMemoryInfo::TotalRam)*0.05), 200)); setThumbnailCacheSize(5, 100); // the pixmap number should not be based on system memory, it's graphics memory // good place to call it here as LoadingCache is a singleton qRegisterMetaType("LoadingDescription"); qRegisterMetaType("DImg"); qRegisterMetaType("DMetadata"); connect(IccSettings::instance(), SIGNAL(settingsChanged(ICCSettingsContainer,ICCSettingsContainer)), this, SLOT(iccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer))); } LoadingCache::~LoadingCache() { delete d->watch; delete d; m_instance = 0; } DImg* LoadingCache::retrieveImage(const QString& cacheKey) const { return d->imageCache[cacheKey]; } bool LoadingCache::putImage(const QString& cacheKey, DImg* img, const QString& filePath) const { bool successfulyInserted; int cost = img->numBytes(); successfulyInserted = d->imageCache.insert(cacheKey, img, cost); if (successfulyInserted && !filePath.isEmpty()) { d->mapImageFilePath(filePath, cacheKey); d->fileWatch()->addedImage(filePath); } return successfulyInserted; } void LoadingCache::removeImage(const QString& cacheKey) { d->imageCache.remove(cacheKey); } void LoadingCache::removeImages() { d->imageCache.clear(); } bool LoadingCache::isCacheable(const DImg* img) const { // return whether image fits in cache return (uint)d->imageCache.maxCost() >= img->numBytes(); } void LoadingCache::addLoadingProcess(LoadingProcess* process) { d->loadingDict[process->cacheKey()] = process; } LoadingProcess* LoadingCache::retrieveLoadingProcess(const QString& cacheKey) const { return d->loadingDict.value(cacheKey); } void LoadingCache::removeLoadingProcess(LoadingProcess* process) { d->loadingDict.remove(process->cacheKey()); } void LoadingCache::notifyNewLoadingProcess(LoadingProcess* process, const LoadingDescription& description) { for (QMap::const_iterator it = d->loadingDict.constBegin(); it != d->loadingDict.constEnd(); ++it) { it.value()->notifyNewLoadingProcess(process, description); } } void LoadingCache::setCacheSize(int megabytes) { qCDebug(DIGIKAM_GENERAL_LOG) << "Allowing a cache size of" << megabytes << "MB"; d->imageCache.setMaxCost(megabytes * 1024 * 1024); } // --- Thumbnails ---- const QImage* LoadingCache::retrieveThumbnail(const QString& cacheKey) const { return d->thumbnailImageCache[cacheKey]; } const QPixmap* LoadingCache::retrieveThumbnailPixmap(const QString& cacheKey) const { return d->thumbnailPixmapCache[cacheKey]; } bool LoadingCache::hasThumbnailPixmap(const QString& cacheKey) const { return d->thumbnailPixmapCache.contains(cacheKey); } void LoadingCache::putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath) { int cost = thumb.byteCount(); if (d->thumbnailImageCache.insert(cacheKey, new QImage(thumb), cost)) { d->mapThumbnailFilePath(filePath, cacheKey); d->fileWatch()->addedThumbnail(filePath); } } void LoadingCache::putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath) { int cost = thumb.width() * thumb.height() * thumb.depth() / 8; if (d->thumbnailPixmapCache.insert(cacheKey, new QPixmap(thumb), cost)) { d->mapThumbnailFilePath(filePath, cacheKey); d->fileWatch()->addedThumbnail(filePath); } } void LoadingCache::removeThumbnail(const QString& cacheKey) { d->thumbnailImageCache.remove(cacheKey); d->thumbnailPixmapCache.remove(cacheKey); } void LoadingCache::removeThumbnails() { d->thumbnailImageCache.clear(); d->thumbnailPixmapCache.clear(); } void LoadingCache::setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps) { d->thumbnailImageCache.setMaxCost(numberOfQImages * ThumbnailSize::maxThumbsSize() * ThumbnailSize::maxThumbsSize() * 4); d->thumbnailPixmapCache.setMaxCost(numberOfQPixmaps * ThumbnailSize::maxThumbsSize() * ThumbnailSize::maxThumbsSize() * QPixmap::defaultDepth() / 8); } void LoadingCache::setFileWatch(LoadingCacheFileWatch* watch) { delete d->watch; d->watch = watch; d->watch->m_cache = this; } QStringList LoadingCache::imageFilePathsInCache() const { d->cleanUpImageFilePathHash(); return d->imageFilePathHash.uniqueKeys(); } QStringList LoadingCache::thumbnailFilePathsInCache() const { d->cleanUpThumbnailFilePathHash(); return d->thumbnailFilePathHash.uniqueKeys(); } -void LoadingCache::notifyFileChanged(const QString& filePath) +void LoadingCache::notifyFileChanged(const QString& filePath, bool notify) { QList keys = d->imageFilePathHash.values(filePath); foreach(const QString& cacheKey, keys) { - if (d->imageCache.remove(cacheKey)) + if (d->imageCache.remove(cacheKey) && notify) { emit fileChanged(filePath, cacheKey); } } keys = d->thumbnailFilePathHash.values(filePath); foreach(const QString& cacheKey, keys) { bool removedImage = d->thumbnailImageCache.remove(cacheKey); bool removedPixmap = d->thumbnailPixmapCache.remove(cacheKey); - if (removedImage || removedPixmap) + if ((removedImage || removedPixmap) && notify) { emit fileChanged(filePath, cacheKey); } } - emit fileChanged(filePath); + if (notify) + { + emit fileChanged(filePath); + } } void LoadingCache::iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous) { if (current.enableCM != previous.enableCM || current.useManagedPreviews != previous.useManagedPreviews || current.monitorProfile != previous.monitorProfile) { LoadingCache::CacheLock lock(this); removeImages(); removeThumbnails(); } } //--------------------------------------------------------------------------------------------------- LoadingCacheFileWatch::~LoadingCacheFileWatch() { if (m_cache) { LoadingCache::CacheLock lock(m_cache); if (m_cache->d->watch == this) { m_cache->d->watch = 0; } } } void LoadingCacheFileWatch::notifyFileChanged(const QString& filePath) { if (m_cache) { LoadingCache::CacheLock lock(m_cache); m_cache->notifyFileChanged(filePath); } } void LoadingCacheFileWatch::addedImage(const QString&) { // default: do nothing } void LoadingCacheFileWatch::addedThumbnail(const QString&) { // default: do nothing } //--------------------------------------------------------------------------------------------------- ClassicLoadingCacheFileWatch::ClassicLoadingCacheFileWatch() { if (thread() != QCoreApplication::instance()->thread()) { moveToThread(QCoreApplication::instance()->thread()); } m_watch = new QFileSystemWatcher; connect(m_watch, SIGNAL(fileChanged(QString)), this, SLOT(slotFileDirty(QString))); // Make sure the signal gets here directly from the event loop. // If putImage is called from the main thread, with CacheLock, // a deadlock would result (mutex is not recursive) connect(this, SIGNAL(signalUpdateDirWatch()), this, SLOT(slotUpdateDirWatch()), Qt::QueuedConnection); } ClassicLoadingCacheFileWatch::~ClassicLoadingCacheFileWatch() { delete m_watch; } void ClassicLoadingCacheFileWatch::addedImage(const QString& filePath) { Q_UNUSED(filePath) // schedule update of file watch // QFileSystemWatch can only be accessed from main thread! emit signalUpdateDirWatch(); } void ClassicLoadingCacheFileWatch::addedThumbnail(const QString& filePath) { Q_UNUSED(filePath); // ignore, we do not watch thumbnails } void ClassicLoadingCacheFileWatch::slotFileDirty(const QString& path) { // Signal comes from main thread qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingCache slotFileDirty " << path; // This method acquires a lock itself notifyFileChanged(path); // No need for locking here, we are in main thread m_watch->removePath(path); m_watchedFiles.remove(path); } void ClassicLoadingCacheFileWatch::slotUpdateDirWatch() { // Event comes from main thread, we need to lock ourselves. LoadingCache::CacheLock lock(m_cache); // get a list of files in cache that need watch QSet toBeAdded; QSet toBeRemoved = m_watchedFiles; QList filePaths = m_cache->imageFilePathsInCache(); foreach(const QString& m_watchPath, filePaths) { if (!m_watchPath.isEmpty()) { if (!m_watchedFiles.contains(m_watchPath)) { toBeAdded.insert(m_watchPath); } toBeRemoved.remove(m_watchPath); } } foreach(const QString& watchedItem, toBeRemoved) { //qCDebug(DIGIKAM_GENERAL_LOG) << "removing watch for " << *it; m_watch->removePath(watchedItem); m_watchedFiles.remove(watchedItem); } foreach(const QString& watchedItem, toBeAdded) { //qCDebug(DIGIKAM_GENERAL_LOG) << "adding watch for " << *it; m_watch->addPath(watchedItem); m_watchedFiles.insert(watchedItem); } } //--------------------------------------------------------------------------------------------------- LoadingCache::CacheLock::CacheLock(LoadingCache* cache) : m_cache(cache) { m_cache->d->mutex.lock(); } LoadingCache::CacheLock::~CacheLock() { m_cache->d->mutex.unlock(); } void LoadingCache::CacheLock::wakeAll() { // obviously the mutex is locked when this function is called m_cache->d->condVar.wakeAll(); } void LoadingCache::CacheLock::timedWait() { // same as above, the mutex is certainly locked m_cache->d->condVar.wait(&m_cache->d->mutex, 1000); } } // namespace Digikam diff --git a/core/libs/threadimageio/loadingcache.h b/core/libs/threadimageio/loadingcache.h index 76ccaadbb6..ce3e4fe7fb 100644 --- a/core/libs/threadimageio/loadingcache.h +++ b/core/libs/threadimageio/loadingcache.h @@ -1,313 +1,313 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-01-11 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_LOADING_CACHE_H #define DIGIKAM_LOADING_CACHE_H // Qt includes #include #include #include // Local includes #include "dimg.h" #include "loadsavethread.h" #include "digikam_export.h" namespace Digikam { class ICCSettingsContainer; class LoadingProcessListener { public: virtual ~LoadingProcessListener() {}; virtual bool querySendNotifyEvent() = 0; virtual void setResult(const LoadingDescription& loadingDescription, const DImg& img) = 0; virtual LoadSaveNotifier* loadSaveNotifier() = 0; virtual LoadSaveThread::AccessMode accessMode() = 0; }; // -------------------------------------------------------------------------------------------------------------- class LoadingProcess { public: virtual ~LoadingProcess() {}; virtual bool completed() = 0; virtual QString filePath() = 0; virtual QString cacheKey() = 0; virtual void addListener(LoadingProcessListener* listener) = 0; virtual void removeListener(LoadingProcessListener* listener) = 0; virtual void notifyNewLoadingProcess(LoadingProcess* process, const LoadingDescription& description) = 0; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT LoadingCacheFileWatch { public: virtual ~LoadingCacheFileWatch(); /// Called by the thread when a new entry is added to the cache virtual void addedImage(const QString& filePath); virtual void addedThumbnail(const QString& filePath); protected: /** * Convenience method. * Call this to tell the cache to remove stored images for filePath from the cache. * Calling this method is fast, you do not need to check if the file is contained in the cache. * Do not hold the CacheLock when calling this method. */ void notifyFileChanged(const QString& filePath); protected: friend class LoadingCache; class LoadingCache* m_cache; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT ClassicLoadingCacheFileWatch : public QObject, public LoadingCacheFileWatch { Q_OBJECT /** Reference implementation */ public: ClassicLoadingCacheFileWatch(); ~ClassicLoadingCacheFileWatch(); virtual void addedImage(const QString& filePath); virtual void addedThumbnail(const QString& filePath); private Q_SLOTS: void slotFileDirty(const QString& path); void slotUpdateDirWatch(); Q_SIGNALS: void signalUpdateDirWatch(); protected: QFileSystemWatcher* m_watch; QSet m_watchedFiles; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT LoadingCache : public QObject { Q_OBJECT public: static LoadingCache* cache(); static void cleanUp(); virtual ~LoadingCache(); /// !! All methods of LoadingCache shall only be called when a CacheLock is held !! class DIGIKAM_EXPORT CacheLock { public: explicit CacheLock(LoadingCache* cache); ~CacheLock(); void wakeAll(); void timedWait(); private: LoadingCache* m_cache; }; /** * Retrieves an image for the given string from the cache, * or 0 if no image is found. */ DImg* retrieveImage(const QString& cacheKey) const; /// Returns whether the given DImg fits in the cache. bool isCacheable(const DImg* img) const; /** Put image into for given string into the cache. * Returns true if image has been put in the cache, false otherwise. * Ownership of the DImg instance is passed to the cache. * When it cannot be put in the cache it is deleted. * The third parameter specifies a file path that will be watched. * If this file changes, the object will be removed from the cache. */ bool putImage(const QString& cacheKey, DImg* img, const QString& filePath) const; /** * Remove entries for the given cacheKey from the cache */ void removeImage(const QString& cacheKey); /** * Remove all entries from the cache */ void removeImages(); // ------- Loading process management ----------------------------------- /** * Find the loading process for given cacheKey, or 0 if not found */ LoadingProcess* retrieveLoadingProcess(const QString& cacheKey) const; /** * Add a loading process to the list. Only one loading process * for the same cache key is registered at a time. */ void addLoadingProcess(LoadingProcess* process); /** * Remove loading process for given cache key */ void removeLoadingProcess(LoadingProcess* process); /** * Notify all currently registered loading processes */ void notifyNewLoadingProcess(LoadingProcess* process, const LoadingDescription& description); /** * Sets the cache size in megabytes. * The thumbnail cache is not affected and setThumbnailCacheSize takes the maximum number. */ void setCacheSize(int megabytes); // ------- Thumbnail cache ----------------------------------- /// The LoadingCache support both the caching of QImage and QPixmap objects. /// QPixmaps can only be accessed from the main thread, so the tasks cannot access this cache. /** * Retrieves a thumbnail for the given filePath from the thumbnail cache, * or a 0 if the thumbnail is not found. */ const QImage* retrieveThumbnail(const QString& cacheKey) const; const QPixmap* retrieveThumbnailPixmap(const QString& cacheKey) const; bool hasThumbnailPixmap(const QString& cacheKey) const; /** * Puts a thumbnail into the thumbnail cache. */ void putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath); void putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath); /** * Remove the thumbnail for the given file path from the thumbnail cache */ void removeThumbnail(const QString& cacheKey); /** * Remove all thumbnails */ void removeThumbnails(); /** * Sets the size of the thumbnail cache * @param numberOfQImages The maximum number of thumbnails of max possible size in QImage format that will be cached. If the size of the images is smaller, a larger number will be cached. * @param numberOfQPixmaps The maximum number of thumbnails of max possible size in QPixmap format that will be cached. If the size of the images is smaller, a larger number will be cached. * Note: The main cache is unaffected by this method, * and setCacheSize takes megabytes as parameter. * Note: A good caching strategy will be to set one of the numbers to 0 * Default values: (0, 100) */ void setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps); // ------- File Watch Management ----------------------------------- /** * Sets a LoadingCacheFileWatch to watch the files contained in this cache. * Ownership of this object is transferred to the cache. */ void setFileWatch(LoadingCacheFileWatch* watch); /** * Returns a list of all possible file paths in cache. */ QStringList imageFilePathsInCache() const; QStringList thumbnailFilePathsInCache() const; /** * Remove all entries from cache that were loaded from filePath. - * Emits relevant signals. + * Emits relevant signals if notify = true. */ - void notifyFileChanged(const QString& filePath); + void notifyFileChanged(const QString& filePath, bool notify = true); Q_SIGNALS: /** * This signal is emitted when the cache is notified that a file was changed. * There is no information in this signal if the file was ever contained in the cache. * The signal may be emitted under CacheLock. Strongly consider a queued connection. */ void fileChanged(const QString& filePath); /** * This signal is emitted when the cache is notified that a file was changed, * and the given cache key was removed from the cache. * The signal may be emitted under CacheLock. Strongly consider a queued connection. */ void fileChanged(const QString& filePath, const QString& cacheKey); private Q_SLOTS: void iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous); private: LoadingCache(); friend class LoadingCacheFileWatch; friend class CacheLock; private: static LoadingCache* m_instance; class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_LOADING_CACHE_H diff --git a/core/libs/threadimageio/loadingcacheinterface.cpp b/core/libs/threadimageio/loadingcacheinterface.cpp index 378d7f1646..94d36a30e1 100644 --- a/core/libs/threadimageio/loadingcacheinterface.cpp +++ b/core/libs/threadimageio/loadingcacheinterface.cpp @@ -1,101 +1,94 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-02-06 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "loadingcacheinterface.h" // Local includes #include "loadingcache.h" namespace Digikam { void LoadingCacheInterface::initialize() { LoadingCache::cache(); } void LoadingCacheInterface::cleanUp() { LoadingCache::cleanUp(); } -void LoadingCacheInterface::fileChanged(const QString& filePath) +void LoadingCacheInterface::fileChanged(const QString& filePath, bool notify) { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); - cache->notifyFileChanged(filePath); -/* NOTE: old implementation - QStringList possibleCacheKeys = LoadingDescription::possibleCacheKeys(filePath); - for (QStringList::iterator it = possibleCacheKeys.begin(); it != possibleCacheKeys.end(); ++it) - { - cache->removeImage(*it); - } -*/ + cache->notifyFileChanged(filePath, notify); } void LoadingCacheInterface::connectToSignalFileChanged(QObject* object, const char* slot) { LoadingCache* cache = LoadingCache::cache(); QObject::connect(cache, SIGNAL(fileChanged(QString)), object, slot, Qt::QueuedConnection); // make it a queued connection because the signal is emitted when the CacheLock is held! } void LoadingCacheInterface::cleanCache() { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->removeImages(); } void LoadingCacheInterface::cleanThumbnailCache() { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->removeThumbnails(); } void LoadingCacheInterface::putImage(const QString& filePath, const DImg& img) { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); if (cache->isCacheable(&img)) { DImg* copy = new DImg(img); copy->detach(); cache->putImage(filePath, copy, filePath); } } void LoadingCacheInterface::setCacheOptions(int cacheSize) { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->setCacheSize(cacheSize); } } // namespace Digikam diff --git a/core/libs/threadimageio/loadingcacheinterface.h b/core/libs/threadimageio/loadingcacheinterface.h index c464d5a4ac..a6b7d0d0dd 100644 --- a/core/libs/threadimageio/loadingcacheinterface.h +++ b/core/libs/threadimageio/loadingcacheinterface.h @@ -1,87 +1,87 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-02-06 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_LOADING_CACHE_INTERFACE_H #define DIGIKAM_LOADING_CACHE_INTERFACE_H // Qt includes #include // Local includes #include "digikam_export.h" #include "dimg.h" namespace Digikam { class DIGIKAM_EXPORT LoadingCacheInterface { public: static void initialize(); /** clean up cache at shutdown */ static void cleanUp(); /** * Remove an image from the cache * because it may have changed on disk */ - static void fileChanged(const QString& filePath); + static void fileChanged(const QString& filePath, bool notify = true); /** * Connect the given object/slot to the signal * void fileChanged(const QString& filePath); * which is emitted when the cache gains knowledge about a possible * change of this file on disk. */ static void connectToSignalFileChanged(QObject* object, const char* slot); /** * remove all images from the cache * (e.g. when loading settings changed) * Does not affect thumbnails. */ static void cleanCache(); /** * Remove all thumbnails from the thumbnail cache. * Does not affect main image cache. */ static void cleanThumbnailCache(); /** add a copy of the image to cache */ static void putImage(const QString& filePath, const DImg& img); /** * Set cache size in Megabytes. * Set to 0 to disable caching. */ static void setCacheOptions(int cacheSize); }; } // namespace Digikam #endif // DIGIKAM_LOADING_CACHE_INTERFACE_H