diff --git a/libs/database/utils/scancontroller.cpp b/libs/database/utils/scancontroller.cpp index 65bec8327c..74c07095fb 100644 --- a/libs/database/utils/scancontroller.cpp +++ b/libs/database/utils/scancontroller.cpp @@ -1,1104 +1,1108 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-01-01 * Description : scan pictures interface. * * Copyright (C) 2005-2006 by Tom Albers * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2007-2013 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 "scancontroller.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "collectionscanner.h" #include "collectionscannerhints.h" #include "coredbaccess.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "filereadwritelock.h" #include "coredbwatch.h" #include "dprogressdlg.h" #include "dmetadata.h" #include "coredb.h" #include "albummanager.h" #include "album.h" #include "coredbschemaupdater.h" namespace Digikam { class SimpleCollectionScannerObserver : public CollectionScannerObserver { public: explicit SimpleCollectionScannerObserver(bool* const var) : m_continue(var) { *m_continue = true; } bool continueQuery() { return *m_continue; } public: bool* m_continue; }; // ------------------------------------------------------------------------------ class ScanController::Private { public: Private() : running(false), needsInitialization(false), needsCompleteScan(false), needsUpdateUniqueHash(false), idle(false), scanSuspended(0), deferFileScanning(false), finishScanAllowed(true), continueInitialization(false), continueScan(false), continuePartialScan(false), fileWatchInstalled(false), eventLoop(0), showTimer(0), relaxedTimer(0), hints(CollectionScanner::createHintContainer()), progressDialog(0), advice(ScanController::Success), needTotalFiles(false), totalFilesToScan(0) { } bool running; bool needsInitialization; bool needsCompleteScan; bool needsUpdateUniqueHash; bool idle; int scanSuspended; QStringList scanTasks; QStringList completeScanDeferredAlbums; bool deferFileScanning; bool finishScanAllowed; QMutex mutex; QWaitCondition condVar; bool continueInitialization; bool continueScan; bool continuePartialScan; bool fileWatchInstalled; QEventLoop* eventLoop; QTimer* showTimer; QTimer* relaxedTimer; QPixmap albumPix; QPixmap rootPix; QPixmap actionPix; QPixmap errorPix; CollectionScannerHintContainer* hints; QDateTime lastHintAdded; DProgressDlg* progressDialog; ScanController::Advice advice; bool needTotalFiles; int totalFilesToScan; public: QPixmap albumPixmap() { if (albumPix.isNull()) { albumPix = QIcon::fromTheme(QLatin1String("folder-pictures")).pixmap(32); } return albumPix; } QPixmap rootPixmap() { if (rootPix.isNull()) { rootPix = QIcon::fromTheme(QLatin1String("folder-open")).pixmap(32); } return rootPix; } QPixmap actionPixmap() { if (actionPix.isNull()) { actionPix = QIcon::fromTheme(QLatin1String("system-run")).pixmap(32); } return actionPix; } QPixmap errorPixmap() { if (errorPix.isNull()) { errorPix = QIcon::fromTheme(QLatin1String("dialog-error")).pixmap(32); } return errorPix; } QPixmap restartPixmap() { if (errorPix.isNull()) { errorPix = QIcon::fromTheme(QLatin1String("view-refresh")).pixmap(32); } return errorPix; } void garbageCollectHints(bool setAccessTime) { QDateTime current = QDateTime::currentDateTime(); if (idle && lastHintAdded.isValid() && lastHintAdded.secsTo(current) > (5*60)) { hints->clear(); } if (setAccessTime) { lastHintAdded = current; } } }; // ------------------------------------------------------------------------------ class ScanControllerCreator { public: ScanController object; }; Q_GLOBAL_STATIC(ScanControllerCreator, creator) // ------------------------------------------------------------------------------ ScanController* ScanController::instance() { return &creator->object; } ScanController::ScanController() : d(new Private) { // create event loop d->eventLoop = new QEventLoop(this); connect(this, SIGNAL(databaseInitialized(bool)), d->eventLoop, SLOT(quit())); connect(this, SIGNAL(completeScanDone()), d->eventLoop, SLOT(quit())); + connect(this, SIGNAL(completeScanCanceled()), + d->eventLoop, SLOT(quit())); + // create timer to show progress dialog d->showTimer = new QTimer(this); d->showTimer->setSingleShot(true); connect(d->showTimer, &QTimer::timeout, this, &ScanController::slotShowProgressDialog); connect(this, &ScanController::triggerShowProgressDialog, this, &ScanController::slotTriggerShowProgressDialog); // create timer for relaxed scheduling d->relaxedTimer = new QTimer(this); d->relaxedTimer->setSingleShot(true); d->relaxedTimer->setInterval(500); connect(d->relaxedTimer, &QTimer::timeout, this, &ScanController::slotRelaxedScanning); // interthread connections connect(this, &ScanController::errorFromInitialization, this, &ScanController::slotErrorFromInitialization); connect(this, &ScanController::progressFromInitialization, this, &ScanController::slotProgressFromInitialization); // start thread d->running = true; start(); } ScanController::~ScanController() { shutDown(); delete d->progressDialog; delete d->hints; delete d; } void ScanController::shutDown() { if (!isRunning()) { return; } d->running = false; d->continueInitialization = false; d->continueScan = false; d->continuePartialScan = false; { QMutexLocker lock(&d->mutex); d->condVar.wakeAll(); } wait(); } void ScanController::createProgressDialog() { if (d->progressDialog) { return; } d->progressDialog = new DProgressDlg(0); d->progressDialog->setLabel(i18n("Scanning collections, please wait...")); d->progressDialog->setWhatsThis(i18n("This shows the progress of the scan. " "During the scan, all files on disk " "are registered in a database.")); d->progressDialog->setMaximum(1); d->progressDialog->setValue(0); connect(this, SIGNAL(incrementProgressDialog(int)), d->progressDialog, SLOT(incrementMaximum(int))); connect(d->progressDialog, SIGNAL(signalCancelPressed()), this, SLOT(slotCancelPressed())); } void ScanController::slotCancelPressed() { abortInitialization(); cancelCompleteScan(); } void ScanController::slotTriggerShowProgressDialog() { if (d->progressDialog && !d->showTimer->isActive() && !d->progressDialog->isVisible()) { d->showTimer->start(300); } } void ScanController::slotShowProgressDialog() { if (d->progressDialog) { //if (!CollectionScanner::databaseInitialScanDone()) { d->progressDialog->show(); } } } ScanController::Advice ScanController::databaseInitialization() { d->advice = Success; createProgressDialog(); setInitializationMessage(); { QMutexLocker lock(&d->mutex); d->needsInitialization = true; d->condVar.wakeAll(); } // loop is quit by signal d->eventLoop->exec(); // setup file watch service for LoadingCache - now that we are sure we have a CoreDbWatch if (!d->fileWatchInstalled) { d->fileWatchInstalled = true; // once per application lifetime only LoadingCache* const cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->setFileWatch(new ScanControllerLoadingCacheFileWatch); } delete d->progressDialog; d->progressDialog = 0; return d->advice; } void ScanController::completeCollectionScanDeferFiles() { completeCollectionScan(true); } void ScanController::allowToScanDeferredFiles() { QMutexLocker lock(&d->mutex); d->finishScanAllowed = true; d->condVar.wakeAll(); } void ScanController::completeCollectionScan(bool defer) { createProgressDialog(); // we only need to count the files in advance // if we show a progress percentage in progress dialog completeCollectionScanCore(!CollectionScanner::databaseInitialScanDone(), defer); delete d->progressDialog; d->progressDialog = 0; } void ScanController::completeCollectionScanInBackground(bool defer) { completeCollectionScanCore(true, defer); } void ScanController::completeCollectionScanCore(bool needTotalFiles, bool defer) { d->needTotalFiles = needTotalFiles; { QMutexLocker lock(&d->mutex); d->needsCompleteScan = true; d->deferFileScanning = defer; d->condVar.wakeAll(); } // loop is quit by signal d->eventLoop->exec(); d->needTotalFiles = false; } void ScanController::updateUniqueHash() { createProgressDialog(); // we only need to count the files in advance //if we show a progress percentage in progress dialog d->needTotalFiles = true; { QMutexLocker lock(&d->mutex); d->needsUpdateUniqueHash = true; d->condVar.wakeAll(); } // loop is quit by signal d->eventLoop->exec(); delete d->progressDialog; d->progressDialog = 0; d->needTotalFiles = false; } void ScanController::scheduleCollectionScan(const QString& path) { QMutexLocker lock(&d->mutex); if (!d->scanTasks.contains(path)) { d->scanTasks << path; } d->condVar.wakeAll(); } void ScanController::scheduleCollectionScanRelaxed(const QString& path) { d->relaxedTimer->start(); QMutexLocker lock(&d->mutex); if (!d->scanTasks.contains(path)) { d->scanTasks << path; } } void ScanController::slotRelaxedScanning() { qCDebug(DIGIKAM_DATABASE_LOG) << "Starting scan!"; QMutexLocker lock(&d->mutex); d->condVar.wakeAll(); } ImageInfo ScanController::scannedInfo(const QString& filePath) { CollectionScanner scanner; scanner.setHintContainer(d->hints); ImageInfo info = ImageInfo::fromLocalFile(filePath); if (info.isNull()) { qlonglong id = scanner.scanFile(filePath, CollectionScanner::NormalScan); return ImageInfo(id); } else { scanner.scanFile(info, CollectionScanner::NormalScan); return info; } } ScanController::FileMetadataWrite::FileMetadataWrite(const ImageInfo& info) : m_info(info), m_changed(false) { ScanController::instance()->beginFileMetadataWrite(info); } void ScanController::FileMetadataWrite::changed(bool wasChanged) { m_changed = m_changed || wasChanged; } ScanController::FileMetadataWrite::~FileMetadataWrite() { ScanController::instance()->finishFileMetadataWrite(m_info, m_changed); } void ScanController::scanFileDirectly(const QString& filePath) { suspendCollectionScan(); CollectionScanner scanner; scanner.setHintContainer(d->hints); scanner.scanFile(filePath); resumeCollectionScan(); } void ScanController::scanFileDirectlyNormal(const ImageInfo& info) { CollectionScanner scanner; scanner.setHintContainer(d->hints); scanner.scanFile(info, CollectionScanner::NormalScan); } /* /// This variant shall be used when a new file is created which is a version /// of another image, and all relevant attributes shall be copied. void scanFileDirectlyCopyAttributes(const QString& filePath, qlonglong parentVersion); void ScanController::scanFileDirectlyCopyAttributes(const QString& filePath, qlonglong parentVersion) { suspendCollectionScan(); CollectionScanner scanner; scanner.recordHints(d->itemHints); scanner.recordHints(d->itemChangeHints); qlonglong id = scanner.scanFile(filePath); ImageInfo dest(id), source(parentVersion); scanner.copyFileProperties(source, dest); resumeCollectionScan(); } */ void ScanController::abortInitialization() { QMutexLocker lock(&d->mutex); d->needsInitialization = false; d->continueInitialization = false; } void ScanController::cancelCompleteScan() { QMutexLocker lock(&d->mutex); d->needsCompleteScan = false; d->continueScan = false; + emit completeScanCanceled(); } void ScanController::cancelAllAndSuspendCollectionScan() { QMutexLocker lock(&d->mutex); d->needsInitialization = false; d->continueInitialization = false; d->needsCompleteScan = false; d->continueScan = false; d->scanTasks.clear(); d->continuePartialScan = false; d->relaxedTimer->stop(); // like suspendCollectionScan d->scanSuspended++; while (!d->idle) { d->condVar.wait(&d->mutex, 20); } } void ScanController::suspendCollectionScan() { QMutexLocker lock(&d->mutex); d->scanSuspended++; } void ScanController::resumeCollectionScan() { QMutexLocker lock(&d->mutex); if (d->scanSuspended) { d->scanSuspended--; } if (!d->scanSuspended) { d->condVar.wakeAll(); } } void ScanController::run() { while (d->running) { bool doInit = false; bool doScan = false; bool doScanDeferred = false; bool doFinishScan = false; bool doPartialScan = false; bool doUpdateUniqueHash = false; QString task; { QMutexLocker lock(&d->mutex); if (d->needsInitialization) { d->needsInitialization = false; doInit = true; } else if (d->needsCompleteScan) { d->needsCompleteScan = false; doScan = true; doScanDeferred = d->deferFileScanning; } else if (d->needsUpdateUniqueHash) { d->needsUpdateUniqueHash = false; doUpdateUniqueHash = true; } else if (!d->completeScanDeferredAlbums.isEmpty() && d->finishScanAllowed && !d->scanSuspended) { // d->completeScanDeferredAlbums is only accessed from the thread, no need to copy doFinishScan = true; } else if (!d->scanTasks.isEmpty() && !d->scanSuspended) { doPartialScan = true; task = d->scanTasks.takeFirst(); } else { d->idle = true; d->condVar.wait(&d->mutex); d->idle = false; } } if (doInit) { d->continueInitialization = true; // pass "this" as InitializationObserver bool success = CoreDbAccess::checkReadyForUse(this); // If d->advice has not been adjusted to a value indicating failure, do this here if (!success && d->advice == Success) { d->advice = ContinueWithoutDatabase; } emit databaseInitialized(success); } else if (doScan) { CollectionScanner scanner; connectCollectionScanner(&scanner); scanner.setNeedFileCount(d->needTotalFiles); scanner.setDeferredFileScanning(doScanDeferred); scanner.setHintContainer(d->hints); SimpleCollectionScannerObserver observer(&d->continueScan); scanner.setObserver(&observer); scanner.completeScan(); emit completeScanDone(); if (doScanDeferred) { d->completeScanDeferredAlbums = scanner.deferredAlbumPaths(); d->finishScanAllowed = false; } } else if (doFinishScan) { if (d->completeScanDeferredAlbums.isEmpty()) { continue; } CollectionScanner scanner; connectCollectionScanner(&scanner); emit collectionScanStarted(i18nc("@info:status", "Scanning collection")); //TODO: reconsider performance scanner.setNeedFileCount(true);//d->needTotalFiles); scanner.setHintContainer(d->hints); SimpleCollectionScannerObserver observer(&d->continueScan); scanner.setObserver(&observer); scanner.finishCompleteScan(d->completeScanDeferredAlbums); d->completeScanDeferredAlbums.clear(); emit completeScanDone(); emit collectionScanFinished(); } else if (doPartialScan) { CollectionScanner scanner; scanner.setHintContainer(d->hints); //connectCollectionScanner(&scanner); SimpleCollectionScannerObserver observer(&d->continuePartialScan); scanner.setObserver(&observer); scanner.partialScan(task); emit partialScanDone(task); } else if (doUpdateUniqueHash) { CoreDbAccess access; CoreDbSchemaUpdater updater(access.db(), access.backend(), access.parameters()); updater.setCoreDbAccess(&access); updater.setObserver(this); updater.updateUniqueHash(); emit completeScanDone(); } } } // (also implementing InitializationObserver) void ScanController::connectCollectionScanner(CollectionScanner* const scanner) { scanner->setSignalsEnabled(true); connect(scanner, SIGNAL(startCompleteScan()), this, SLOT(slotStartCompleteScan())); connect(scanner, SIGNAL(totalFilesToScan(int)), this, SLOT(slotTotalFilesToScan(int))); connect(scanner, SIGNAL(startScanningAlbum(QString,QString)), this, SLOT(slotStartScanningAlbum(QString,QString))); connect(scanner, SIGNAL(scannedFiles(int)), this, SLOT(slotScannedFiles(int))); connect(scanner, SIGNAL(startScanningAlbumRoot(QString)), this, SLOT(slotStartScanningAlbumRoot(QString))); connect(scanner, SIGNAL(startScanningForStaleAlbums()), this, SLOT(slotStartScanningForStaleAlbums())); connect(scanner, SIGNAL(startScanningAlbumRoots()), this, SLOT(slotStartScanningAlbumRoots())); } void ScanController::slotTotalFilesToScan(int count) { if (d->progressDialog) { d->progressDialog->incrementMaximum(count); } d->totalFilesToScan = count; emit totalFilesToScan(d->totalFilesToScan); } void ScanController::slotStartCompleteScan() { d->totalFilesToScan = 0; slotTriggerShowProgressDialog(); QString message = i18n("Preparing collection scan..."); if (d->progressDialog) { d->progressDialog->addedAction(d->restartPixmap(), message); } } void ScanController::slotStartScanningAlbum(const QString& albumRoot, const QString& album) { Q_UNUSED(albumRoot); if (d->progressDialog) { d->progressDialog->addedAction(d->albumPixmap(), QLatin1Char(' ') + album); } } void ScanController::slotScannedFiles(int scanned) { if (d->progressDialog) { d->progressDialog->advance(scanned); } if (d->totalFilesToScan) { emit filesScanned(scanned); emit scanningProgress(double(scanned) / double(d->totalFilesToScan)); } } void ScanController::slotStartScanningAlbumRoot(const QString& albumRoot) { if (d->progressDialog) { d->progressDialog->addedAction(d->rootPixmap(), albumRoot); } } void ScanController::slotStartScanningForStaleAlbums() { QString message = i18n("Scanning for removed albums..."); if (d->progressDialog) { d->progressDialog->addedAction(d->actionPixmap(), message); } } void ScanController::slotStartScanningAlbumRoots() { QString message = i18n("Scanning images in individual albums..."); if (d->progressDialog) { d->progressDialog->addedAction(d->actionPixmap(), message); } } // implementing InitializationObserver void ScanController::moreSchemaUpdateSteps(int numberOfSteps) { // not from main thread emit triggerShowProgressDialog(); emit incrementProgressDialog(numberOfSteps); } // implementing InitializationObserver void ScanController::schemaUpdateProgress(const QString& message, int numberOfSteps) { // not from main thread emit progressFromInitialization(message, numberOfSteps); } void ScanController::slotProgressFromInitialization(const QString& message, int numberOfSteps) { // main thread if (d->progressDialog) { d->progressDialog->addedAction(d->actionPixmap(), message); d->progressDialog->advance(numberOfSteps); } } // implementing InitializationObserver void ScanController::finishedSchemaUpdate(UpdateResult result) { // not from main thread switch (result) { case InitializationObserver::UpdateSuccess: d->advice = Success; break; case InitializationObserver::UpdateError: d->advice = ContinueWithoutDatabase; break; case InitializationObserver::UpdateErrorMustAbort: d->advice = AbortImmediately; break; } } // implementing InitializationObserver void ScanController::error(const QString& errorMessage) { // not from main thread emit errorFromInitialization(errorMessage); } // implementing InitializationObserver bool ScanController::continueQuery() { // not from main thread return d->continueInitialization; } void ScanController::slotErrorFromInitialization(const QString& errorMessage) { // main thread QString message = i18n("Error"); if (d->progressDialog) { d->progressDialog->addedAction(d->errorPixmap(), message); } QMessageBox::critical(d->progressDialog, qApp->applicationName(), errorMessage); } void ScanController::setInitializationMessage() { QString message = i18n("Initializing database..."); if (d->progressDialog) { d->progressDialog->addedAction(d->restartPixmap(), message); } } static AlbumCopyMoveHint hintForAlbum(const PAlbum* const album, int dstAlbumRootId, const QString& relativeDstPath, const QString& albumName) { QString dstAlbumPath; if (relativeDstPath == QLatin1String("/")) { dstAlbumPath = relativeDstPath + albumName; } else { dstAlbumPath = relativeDstPath + QLatin1Char('/') + albumName; } return AlbumCopyMoveHint(album->albumRootId(), album->id(), dstAlbumRootId, dstAlbumPath); } static QList hintsForAlbum(const PAlbum* const album, int dstAlbumRootId, QString relativeDstPath, const QString& albumName) { QList newHints; newHints << hintForAlbum(album, dstAlbumRootId, relativeDstPath, albumName); QString parentAlbumPath = album->albumPath(); if (parentAlbumPath == QLatin1String("/")) { parentAlbumPath.clear(); // do not cut away a "/" in mid() below } for (AlbumIterator it(const_cast(album)); *it; ++it) { PAlbum* const a = (PAlbum*)*it; QString childAlbumPath = a->albumPath(); newHints << hintForAlbum(a, dstAlbumRootId, relativeDstPath, albumName + childAlbumPath.mid(parentAlbumPath.length())); } return newHints; } void ScanController::hintAtMoveOrCopyOfAlbum(const PAlbum* const album, const QString& dstPath, const QString& newAlbumName) { // get album root and album from dst path CollectionLocation location = CollectionManager::instance()->locationForPath(dstPath); if (location.isNull()) { qCWarning(DIGIKAM_DATABASE_LOG) << "hintAtMoveOrCopyOfAlbum: Destination path" << dstPath << "does not point to an available location."; return; } QString relativeDstPath = CollectionManager::instance()->album(location, dstPath); QList newHints = hintsForAlbum(album, location.id(), relativeDstPath, newAlbumName.isNull() ? album->title() : newAlbumName); //QMutexLocker lock(&d->mutex); //d->albumHints << newHints; d->hints->recordHints(newHints); } void ScanController::hintAtMoveOrCopyOfAlbum(const PAlbum* const album, const PAlbum* const dstAlbum, const QString& newAlbumName) { QList newHints = hintsForAlbum(album, dstAlbum->albumRootId(), dstAlbum->albumPath(), newAlbumName.isNull() ? album->title() : newAlbumName); //QMutexLocker lock(&d->mutex); //d->albumHints << newHints; d->hints->recordHints(newHints); } void ScanController::hintAtMoveOrCopyOfItems(const QList ids, const PAlbum* const dstAlbum, const QStringList& itemNames) { ItemCopyMoveHint hint(ids, dstAlbum->albumRootId(), dstAlbum->id(), itemNames); d->garbageCollectHints(true); //d->itemHints << hint; d->hints->recordHints(QList() << hint); } void ScanController::hintAtMoveOrCopyOfItem(qlonglong id, const PAlbum* const dstAlbum, const QString& itemName) { ItemCopyMoveHint hint(QList() << id, dstAlbum->albumRootId(), dstAlbum->id(), QStringList() << itemName); d->garbageCollectHints(true); //d->itemHints << hint; d->hints->recordHints(QList() << hint); } void ScanController::hintAtModificationOfItems(const QList ids) { ItemChangeHint hint(ids, ItemChangeHint::ItemModified); d->garbageCollectHints(true); //d->itemHints << hint; d->hints->recordHints(QList() << hint); } void ScanController::hintAtModificationOfItem(qlonglong id) { ItemChangeHint hint(QList() << id, ItemChangeHint::ItemModified); d->garbageCollectHints(true); //d->itemHints << hint; d->hints->recordHints(QList() << hint); } void ScanController::beginFileMetadataWrite(const ImageInfo& info) { { // throw in a lock to synchronize with all parallel writing FileReadLocker locker(info.filePath()); } QFileInfo fi(info.filePath()); d->hints->recordHint(ItemMetadataAdjustmentHint(info.id(), ItemMetadataAdjustmentHint::AboutToEditMetadata, fi.lastModified(), fi.size())); } void ScanController::finishFileMetadataWrite(const ImageInfo& info, bool changed) { QFileInfo fi(info.filePath()); d->hints->recordHint(ItemMetadataAdjustmentHint(info.id(), changed ? ItemMetadataAdjustmentHint::MetadataEditingFinished : ItemMetadataAdjustmentHint::MetadataEditingAborted, fi.lastModified(), fi.size())); scanFileDirectlyNormal(info); } // -------------------------------------------------------------------------------------------- ScanControllerLoadingCacheFileWatch::ScanControllerLoadingCacheFileWatch() { CoreDbWatch* const dbwatch = CoreDbAccess::databaseWatch(); // we opt for a queued connection to make stuff a bit relaxed connect(dbwatch, SIGNAL(imageChange(ImageChangeset)), this, SLOT(slotImageChanged(ImageChangeset)), Qt::QueuedConnection); } void ScanControllerLoadingCacheFileWatch::slotImageChanged(const ImageChangeset& changeset) { foreach(const qlonglong& imageId, changeset.ids()) { DatabaseFields::Set changes = changeset.changes(); if (changes & DatabaseFields::ModificationDate || changes & DatabaseFields::Orientation) { ImageInfo info(imageId); //qCDebug(DIGIKAM_DATABASE_LOG) << imageId << info.filePath(); notifyFileChanged(info.filePath()); } } } } // namespace Digikam diff --git a/libs/database/utils/scancontroller.h b/libs/database/utils/scancontroller.h index f2275e27be..aa199ddb64 100644 --- a/libs/database/utils/scancontroller.h +++ b/libs/database/utils/scancontroller.h @@ -1,298 +1,299 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-10-28 * Description : scan pictures interface. * * Copyright (C) 2005-2006 by Tom Albers * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2007-2013 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 SCANCONTROLLER_H #define SCANCONTROLLER_H // Qt includes #include #include // Local includes #include "digikam_export.h" #include "collectionscannerobserver.h" #include "imageinfo.h" #include "loadingcache.h" #include "coredbchangesets.h" namespace Digikam { class CollectionScanner; class PAlbum; class DIGIKAM_EXPORT ScanController : public QThread, public InitializationObserver { Q_OBJECT public: enum Advice { Success, ContinueWithoutDatabase, AbortImmediately }; public: static ScanController* instance(); /** Wait for the thread to finish. Returns after all tasks are done. */ void shutDown(); /** * Calls CoreDbAccess::checkReadyForUse(), providing progress * feedback if schema updating occurs. * Synchronous, returns when ready. */ Advice databaseInitialization(); /** * Carries out a complete collection scan, providing progress feedback. * Synchronous, returns when ready. * The database will be locked while the scan is running. * With the DeferFiles variant, deep files scanning (new files), the part * which can take long, will be done during the time after the method returns, * shortening the synchronous wait. After completeCollectionScanDeferFiles, you * need to call allowToScanDeferredFiles() once to enable scanning the deferred files. */ void completeCollectionScan(bool defer = false); void completeCollectionScanDeferFiles(); void allowToScanDeferredFiles(); /** * Scan Whole collection without to display a progress dialog or to manage splashscreen, as for NewItemsFinder tool. */ void completeCollectionScanInBackground(bool defer); /** * Carries out a complete collection scan, at the same time updating * the unique hash in the database and thumbnail database. * Synchronous, returns when ready. * The database will be locked while the scan is running. */ void updateUniqueHash(); /** * Schedules a scan of the specified part of the collection. * Asynchronous, returns immediately. */ void scheduleCollectionScan(const QString& path); /** * Schedules a scan of the specified part of the collection. * Asynchronous, returns immediately. * A small delay may be introduced before the actual scanning starts, * so that you can call this often without checking for duplicates. * This method must only be used from the main thread. */ void scheduleCollectionScanRelaxed(const QString& path); /** * If necessary (modified or newly created, scans the file directly * Returns the up-to-date ImageInfo. */ ImageInfo scannedInfo(const QString& filePath); /** * When writing metadata to the file, the file content on disk changes, * but the information is taken from the database; therefore, * the resulting scanning process can be optimized. * * Thus, if you write metadata of an ImageInfo from the database to disk, * do this in the scope of a FileMetadataWrite object. */ class FileMetadataWrite { public: explicit FileMetadataWrite(const ImageInfo& info); ~FileMetadataWrite(); void changed(bool wasChanged); protected: ImageInfo m_info; bool m_changed; }; /** If the controller is currently processing a database update * (typically after first run), * cancel this hard and as soon as possible. Any progress may be lost. */ void abortInitialization(); /** If the controller is currently doing a complete scan * (typically at startup), stop this operation. * It can be resumed later. */ void cancelCompleteScan(); /** Cancels all running or scheduled operations and suspends scanning. * This method returns when all scanning has stopped. * This includes a call to suspendCollectionScan(). * Restart with resumeCollectionScan. */ void cancelAllAndSuspendCollectionScan(); /** Temporarily suspend collection scanning. * All scheduled scanning tasks are queued * and will be done when resumeCollectionScan() * has been called. * Calling these methods is recursive, you must resume * as often as you called suspend. */ void suspendCollectionScan(); void resumeCollectionScan(); /** Hint at the imminent copy, move or rename of an album, so that the * collection scanner is informed about this. * If the album is renamed, give the new name in newAlbumName. * DstAlbum is the new parent album / * dstPath is the new parent directory of the album, so * do not include the album name to dstPath. */ void hintAtMoveOrCopyOfAlbum(const PAlbum* const album, const PAlbum* const dstAlbum, const QString& newAlbumName = QString()); void hintAtMoveOrCopyOfAlbum(const PAlbum* const album, const QString& dstPath, const QString& newAlbumName = QString()); /** Hint at the imminent copy, move or rename of items, so that the * collection scanner is informed about this. * Give the list of existing items, specify the destination with dstAlbum, * and give the names at destination in itemNames (Unless for rename, names wont usually change. * Give them nevertheless.) */ void hintAtMoveOrCopyOfItems(const QList ids, const PAlbum* const dstAlbum, const QStringList& itemNames); void hintAtMoveOrCopyOfItem(qlonglong id, const PAlbum* const dstAlbum, const QString& itemName); /** Hint at the fact that an item may have changed, although its modification date may not have changed. * Note that a scan of the containing directory will need to be triggered nonetheless for the hints to take effect. */ void hintAtModificationOfItems(const QList ids); void hintAtModificationOfItem(qlonglong id); /** * Implementation of FileMetadataWrite, see there. Calling these methods is equivalent. */ void beginFileMetadataWrite(const ImageInfo& info); void finishFileMetadataWrite(const ImageInfo& info, bool changed); Q_SIGNALS: void databaseInitialized(bool success); void completeScanDone(); + void completeScanCanceled(); void triggerShowProgressDialog(); void incrementProgressDialog(int); void errorFromInitialization(const QString&); void progressFromInitialization(const QString&, int); void totalFilesToScan(int); void filesScanned(int); void collectionScanStarted(const QString& message); void scanningProgress(float progress); void collectionScanFinished(); void partialScanDone(const QString& path); private Q_SLOTS: void slotStartCompleteScan(); void slotTotalFilesToScan(int count); void slotStartScanningAlbum(const QString& albumRoot, const QString& album); void slotScannedFiles(int scanned); void slotStartScanningAlbumRoot(const QString& albumRoot); void slotStartScanningForStaleAlbums(); void slotStartScanningAlbumRoots(); void slotShowProgressDialog(); void slotTriggerShowProgressDialog(); void slotCancelPressed(); void slotProgressFromInitialization(const QString& message, int numberOfSteps); void slotErrorFromInitialization(const QString& errorMessage); void slotRelaxedScanning(); protected: virtual void run(); private: /** * The file pointed to by file path will be scanned. * The scan is finished when returning from the method. */ void scanFileDirectly(const QString& filePath); void scanFileDirectlyNormal(const ImageInfo& info); void createProgressDialog(); void setInitializationMessage(); void completeCollectionScanCore(bool needTotalFiles, bool defer); virtual void moreSchemaUpdateSteps(int numberOfSteps); virtual void schemaUpdateProgress(const QString& message, int numberOfSteps); virtual void finishedSchemaUpdate(UpdateResult result); virtual void connectCollectionScanner(CollectionScanner* const scanner); virtual void error(const QString& errorMessage); virtual bool continueQuery(); private: ScanController(); ~ScanController(); friend class ScanControllerCreator; private: class Private; Private* const d; }; // ------------------------------------------------------------------------------ class ScanControllerLoadingCacheFileWatch : public ClassicLoadingCacheFileWatch { Q_OBJECT /* This class is derived from the ClassicLoadingCacheFileWatch, which means it has the full functionality of the class and only extends it by listening to CollectionScanner information */ public: ScanControllerLoadingCacheFileWatch(); private Q_SLOTS: void slotImageChanged(const ImageChangeset& changeset); }; } // namespace Digikam #endif // SCANCONTROLLER_H diff --git a/utilities/maintenance/newitemsfinder.cpp b/utilities/maintenance/newitemsfinder.cpp index acc8ec25ba..888c88b4f9 100644 --- a/utilities/maintenance/newitemsfinder.cpp +++ b/utilities/maintenance/newitemsfinder.cpp @@ -1,187 +1,205 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-01-20 * Description : new items finder. * * Copyright (C) 2012-2018 by Gilles Caulier * Copyright (C) 2012 by Andi Clemens * * 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 "newitemsfinder.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "scancontroller.h" namespace Digikam { class NewItemsFinder::Private { public: - Private() : - mode(CompleteCollectionScan) + Private() + : mode(CompleteCollectionScan), + cancel(false) { } FinderMode mode; + + bool cancel; + QStringList foldersToScan; QStringList foldersScanned; }; NewItemsFinder::NewItemsFinder(const FinderMode mode, const QStringList& foldersToScan, ProgressItem* const parent) : MaintenanceTool(QLatin1String("NewItemsFinder"), parent), d(new Private) { setLabel(i18n("Find new items")); setThumbnail(QIcon::fromTheme(QLatin1String("view-refresh")).pixmap(22)); setShowAtStart(true); ProgressManager::addProgressItem(this); d->mode = mode; // Common conections to ScanController connect(ScanController::instance(), SIGNAL(collectionScanStarted(QString)), this, SLOT(slotScanStarted(QString))); connect(ScanController::instance(), SIGNAL(totalFilesToScan(int)), this, SLOT(slotTotalFilesToScan(int))); connect(ScanController::instance(), SIGNAL(filesScanned(int)), this, SLOT(slotFilesScanned(int))); // Connection and rules for ScheduleCollectionScan mode. connect(ScanController::instance(), SIGNAL(partialScanDone(QString)), this, SLOT(slotPartialScanDone(QString))); // If we are scanning for newly imported files, we need to have the folders for scanning... if (mode == ScheduleCollectionScan && foldersToScan.isEmpty()) { qCWarning(DIGIKAM_GENERAL_LOG) << "NewItemsFinder called without any folders. Wrong call."; } d->foldersToScan = foldersToScan; d->foldersToScan.sort(); } NewItemsFinder::~NewItemsFinder() { delete d; } void NewItemsFinder::slotStart() { MaintenanceTool::slotStart(); switch (d->mode) { case ScanDeferredFiles: { qCDebug(DIGIKAM_GENERAL_LOG) << "scan mode: ScanDeferredFiles"; connect(ScanController::instance(), SIGNAL(completeScanDone()), this, SLOT(slotDone())); ScanController::instance()->completeCollectionScanInBackground(false); ScanController::instance()->allowToScanDeferredFiles(); break; } case CompleteCollectionScan: { qCDebug(DIGIKAM_GENERAL_LOG) << "scan mode: CompleteCollectionScan"; ScanController::instance()->completeCollectionScanInBackground(false); + if (d->cancel) + { + break; + } + connect(ScanController::instance(), SIGNAL(completeScanDone()), this, SLOT(slotDone())); ScanController::instance()->allowToScanDeferredFiles(); ScanController::instance()->completeCollectionScanInBackground(true); break; } case ScheduleCollectionScan: { qCDebug(DIGIKAM_GENERAL_LOG) << "scan mode: ScheduleCollectionScan :: " << d->foldersToScan; d->foldersScanned.clear(); foreach(const QString& folder, d->foldersToScan) + { + if (d->cancel) + { + break; + } + ScanController::instance()->scheduleCollectionScan(folder); + } break; } } } void NewItemsFinder::slotScanStarted(const QString& info) { qCDebug(DIGIKAM_GENERAL_LOG) << info; setStatus(info); } void NewItemsFinder::slotTotalFilesToScan(int t) { qCDebug(DIGIKAM_GENERAL_LOG) << "total scan value : " << t; setTotalItems(t); } void NewItemsFinder::slotFilesScanned(int s) { //qCDebug(DIGIKAM_GENERAL_LOG) << "file scanned : " << s; advance(s); } void NewItemsFinder::slotCancel() { + d->cancel = true; + ScanController::instance()->cancelCompleteScan(); MaintenanceTool::slotCancel(); } void NewItemsFinder::slotPartialScanDone(const QString& path) { // Check if path scanned is included in planed list. if (d->foldersToScan.contains(path) && !d->foldersScanned.contains(path)) { d->foldersScanned.append(path); d->foldersScanned.sort(); // Check if all planed scanning is done if (d->foldersScanned == d->foldersToScan) { slotDone(); } } } } // namespace Digikam