File indexing completed on 2025-01-05 03:54:17

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-10-28
0007  * Description : scan item controller.
0008  *
0009  * SPDX-FileCopyrightText: 2005-2006 by Tom Albers <tomalbers at kde dot nl>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2007-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "scancontroller_p.h"
0018 
0019 namespace Digikam
0020 {
0021 
0022 ScanController::FileMetadataWrite::FileMetadataWrite(const ItemInfo& info)
0023     : m_info   (info),
0024       m_changed(false)
0025 {
0026     ScanController::instance()->beginFileMetadataWrite(info);
0027 }
0028 
0029 void ScanController::FileMetadataWrite::changed(bool wasChanged)
0030 {
0031     m_changed = (m_changed || wasChanged);
0032 }
0033 
0034 ScanController::FileMetadataWrite::~FileMetadataWrite()
0035 {
0036     ScanController::instance()->finishFileMetadataWrite(m_info, m_changed);
0037 }
0038 
0039 // ----------------------------------------------------------------------------
0040 
0041 Q_GLOBAL_STATIC(ScanControllerCreator, creator)
0042 
0043 ScanController* ScanController::instance()
0044 {
0045     return &creator->object;
0046 }
0047 
0048 // ----------------------------------------------------------------------------
0049 
0050 ScanController::ScanController()
0051     : d(new Private)
0052 {
0053     // create event loop
0054 
0055     d->eventLoop = new QEventLoop(this);
0056 
0057     connect(this, SIGNAL(databaseInitialized(bool)),
0058             d->eventLoop, SLOT(quit()));
0059 
0060     connect(this, SIGNAL(completeScanDone()),
0061             d->eventLoop, SLOT(quit()));
0062 
0063     connect(this, SIGNAL(completeScanCanceled()),
0064             d->eventLoop, SLOT(quit()));
0065 
0066     // create timer to show progress dialog
0067 
0068     d->showTimer = new QTimer(this);
0069     d->showTimer->setSingleShot(true);
0070 
0071     connect(d->showTimer, &QTimer::timeout,
0072             this, &ScanController::slotShowProgressDialog);
0073 
0074     connect(this, &ScanController::triggerShowProgressDialog,
0075             this, &ScanController::slotTriggerShowProgressDialog);
0076 
0077     // create timer for relaxed scheduling
0078 
0079     d->relaxedTimer = new QTimer(this);
0080     d->relaxedTimer->setSingleShot(true);
0081     d->relaxedTimer->setInterval(250);
0082 
0083     connect(d->relaxedTimer, &QTimer::timeout,
0084             this, &ScanController::slotRelaxedScanning);
0085 
0086     // create timer for external scheduling
0087 
0088     d->externalTimer = new QTimer(this);
0089     d->externalTimer->setSingleShot(true);
0090     d->externalTimer->setInterval(1500);
0091 
0092     connect(d->externalTimer, &QTimer::timeout,
0093             this, &ScanController::slotRelaxedScanning);
0094 
0095     // inter-thread connections
0096 
0097     connect(this, &ScanController::errorFromInitialization,
0098             this, &ScanController::slotErrorFromInitialization);
0099 
0100     connect(this, &ScanController::progressFromInitialization,
0101             this, &ScanController::slotProgressFromInitialization);
0102 
0103     // start thread
0104 
0105     d->running = true;
0106     start();
0107 }
0108 
0109 ScanController::~ScanController()
0110 {
0111     shutDown();
0112 
0113     delete d->progressDialog;
0114     delete d->hints;
0115     delete d;
0116 }
0117 
0118 void ScanController::setInitializationMessage()
0119 {
0120     QString message = i18nc("@info", "Initializing database...");
0121 
0122     if (d->progressDialog)
0123     {
0124         d->progressDialog->addedAction(d->restartPixmap(), message);
0125     }
0126 }
0127 
0128 /// implementing InitializationObserver
0129 bool ScanController::continueQuery()
0130 {
0131     // not from main thread
0132 
0133     return d->continueInitialization;
0134 }
0135 
0136 void ScanController::createProgressDialog()
0137 {
0138     if (d->progressDialog)
0139     {
0140         return;
0141     }
0142 
0143     d->progressDialog = new DProgressDlg(nullptr);
0144     d->progressDialog->setLabel(i18nc("@label", "Scanning collections, please wait..."));
0145     d->progressDialog->setWhatsThis(i18nc("@info",
0146                                           "This shows the progress of the scan. "
0147                                           "During the scan, all files on disk "
0148                                           "are registered in a database. "
0149                                           "Note: this dialog can appear automatically "
0150                                           "if a previous scan of collections have not been fully completed."));
0151 
0152     d->progressDialog->setMaximum(1);
0153     d->progressDialog->setValue(0);
0154 
0155     connect(this, SIGNAL(incrementProgressDialog(int)),
0156             d->progressDialog, SLOT(incrementMaximum(int)));
0157 
0158     connect(d->progressDialog, SIGNAL(signalCancelPressed()),
0159             this, SLOT(slotCancelPressed()));
0160 }
0161 
0162 void ScanController::run()
0163 {
0164     while (d->running)
0165     {
0166         bool doInit             = false;
0167         bool doScan             = false;
0168         bool doScanDeferred     = false;
0169         bool doFinishScan       = false;
0170         bool doPartialScan      = false;
0171         bool doUpdateUniqueHash = false;
0172 
0173         QString task;
0174         {
0175             QMutexLocker lock(&d->mutex);
0176 
0177             if      (d->needsInitialization)
0178             {
0179                 d->needsInitialization = false;
0180                 doInit                 = true;
0181             }
0182             else if (d->needsCompleteScan && !d->scanSuspended)
0183             {
0184                 d->needsCompleteScan = false;
0185                 doScan               = true;
0186                 doScanDeferred       = d->deferFileScanning;
0187             }
0188             else if (d->needsUpdateUniqueHash && !d->scanSuspended)
0189             {
0190                 d->needsUpdateUniqueHash = false;
0191                 doUpdateUniqueHash       = true;
0192             }
0193             else if (!d->completeScanDeferredAlbums.isEmpty() && d->finishScanAllowed && !d->scanSuspended)
0194             {
0195                 // d->completeScanDeferredAlbums is only accessed from the thread, no need to copy
0196                 doFinishScan  = true;
0197             }
0198             else if (!d->scanTasks.isEmpty() && !d->scanSuspended)
0199             {
0200                 doPartialScan = true;
0201                 task          = d->scanTasks.takeFirst();
0202             }
0203             else
0204             {
0205                 d->idle = true;
0206                 d->condVar.wait(&d->mutex);
0207                 d->idle = false;
0208             }
0209         }
0210 
0211         if (doInit)
0212         {
0213             d->continueInitialization = true;
0214 
0215             // pass "this" as InitializationObserver
0216 
0217             bool success              = CoreDbAccess::checkReadyForUse(this);
0218 
0219             // If d->advice has not been adjusted to a value indicating failure, do this here
0220 
0221             if (!success && (d->advice == Success))
0222             {
0223                 d->advice = ContinueWithoutDatabase;
0224             }
0225 
0226             Q_EMIT databaseInitialized(success);
0227         }
0228         else if (doScan)
0229         {
0230             CollectionScanner scanner;
0231             connectCollectionScanner(&scanner);
0232 
0233             scanner.setNeedFileCount(d->needTotalFiles);
0234             scanner.setPerformFastScan(d->performFastScan);
0235             scanner.setDeferredFileScanning(doScanDeferred);
0236             scanner.setHintContainer(d->hints);
0237 
0238             SimpleCollectionScannerObserver observer(&d->continueScan);
0239             scanner.setObserver(&observer);
0240 
0241             scanner.completeScan();
0242 
0243             if (doScanDeferred)
0244             {
0245                 d->completeScanDeferredAlbums = scanner.deferredAlbumPaths();
0246                 d->finishScanAllowed          = false;
0247             }
0248 
0249             d->newIdsList = scanner.getNewIdsList();
0250 
0251             Q_EMIT completeScanDone();
0252 
0253         }
0254         else if (doFinishScan)
0255         {
0256             if (d->completeScanDeferredAlbums.isEmpty())
0257             {
0258                 continue;
0259             }
0260 
0261             CollectionScanner scanner;
0262             connectCollectionScanner(&scanner);
0263 
0264             Q_EMIT collectionScanStarted(i18nc("@info:status", "Scanning collection"));
0265 
0266             //TODO: reconsider performance
0267 
0268             scanner.setNeedFileCount(true);//d->needTotalFiles);
0269 
0270             scanner.setHintContainer(d->hints);
0271 
0272             SimpleCollectionScannerObserver observer(&d->continueScan);
0273             scanner.setObserver(&observer);
0274 
0275             scanner.finishCompleteScan(d->completeScanDeferredAlbums);
0276 
0277             d->completeScanDeferredAlbums.clear();
0278 
0279             Q_EMIT completeScanDone();
0280             Q_EMIT collectionScanFinished();
0281         }
0282         else if (doPartialScan)
0283         {
0284             CollectionScanner scanner;
0285             scanner.setHintContainer(d->hints);
0286 /*
0287             connectCollectionScanner(&scanner);
0288 */
0289             SimpleCollectionScannerObserver observer(&d->continuePartialScan);
0290             scanner.setObserver(&observer);
0291             scanner.partialScan(task);
0292 
0293             Q_EMIT partialScanDone(task);
0294         }
0295         else if (doUpdateUniqueHash)
0296         {
0297             CoreDbAccess access;
0298             CoreDbSchemaUpdater updater(access.db(), access.backend(), access.parameters());
0299             updater.setCoreDbAccess(&access);
0300             updater.setObserver(this);
0301             updater.updateUniqueHash();
0302 
0303             Q_EMIT completeScanDone();
0304         }
0305     }
0306 }
0307 
0308 /// (also implementing InitializationObserver)
0309 void ScanController::connectCollectionScanner(CollectionScanner* const scanner)
0310 {
0311     scanner->setSignalsEnabled(true);
0312 
0313     connect(scanner, SIGNAL(startCompleteScan()),
0314             this, SLOT(slotStartCompleteScan()));
0315 
0316     connect(scanner, SIGNAL(totalFilesToScan(int)),
0317             this, SLOT(slotTotalFilesToScan(int)));
0318 
0319     connect(scanner, SIGNAL(startScanningAlbum(QString,QString)),
0320             this, SLOT(slotStartScanningAlbum(QString,QString)));
0321 
0322     connect(scanner, SIGNAL(scannedFiles(int)),
0323             this, SLOT(slotScannedFiles(int)));
0324 
0325     connect(scanner, SIGNAL(startScanningAlbumRoot(QString)),
0326             this, SLOT(slotStartScanningAlbumRoot(QString)));
0327 
0328     connect(scanner, SIGNAL(startScanningForStaleAlbums()),
0329             this, SLOT(slotStartScanningForStaleAlbums()));
0330 
0331     connect(scanner, SIGNAL(startScanningAlbumRoots()),
0332             this, SLOT(slotStartScanningAlbumRoots()));
0333 }
0334 
0335 void ScanController::allowToScanDeferredFiles()
0336 {
0337     QMutexLocker lock(&d->mutex);
0338     d->finishScanAllowed = true;
0339     d->condVar.wakeAll();
0340 }
0341 
0342 void ScanController::updateUniqueHash()
0343 {
0344     createProgressDialog();
0345 
0346     // we only need to count the files in advance
0347     // if we show a progress percentage in progress dialog
0348 
0349     d->needTotalFiles = true;
0350 
0351     {
0352         QMutexLocker lock(&d->mutex);
0353         d->needsUpdateUniqueHash = true;
0354         d->condVar.wakeAll();
0355     }
0356 
0357     // NOTE: loop is quit by signal
0358 
0359     d->eventLoop->exec();
0360 
0361     delete d->progressDialog;
0362     d->progressDialog = nullptr;
0363     d->needTotalFiles = false;
0364 }
0365 
0366 ItemInfo ScanController::scannedInfo(const QString& filePath,
0367                                      CollectionScanner::FileScanMode mode)
0368 {
0369     CollectionScanner scanner;
0370     scanner.setHintContainer(d->hints);
0371 
0372     ItemInfo info = ItemInfo::fromLocalFile(filePath);
0373 
0374     if (info.isNull() || !info.isVisible())
0375     {
0376         qlonglong id = scanner.scanFile(filePath, CollectionScanner::NormalScan);
0377 
0378         return ItemInfo(id);
0379     }
0380     else
0381     {
0382         scanner.scanFile(info, mode);
0383 
0384         return info;
0385     }
0386 }
0387 
0388 QList<qlonglong> ScanController::getNewIdsList() const
0389 {
0390     return d->newIdsList;
0391 }
0392 
0393 } // namespace Digikam
0394 
0395 #include "moc_scancontroller.cpp"