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"