File indexing completed on 2025-01-05 03:53:48
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2011-11-07 0007 * Description : Directory watch interface 0008 * 0009 * SPDX-FileCopyrightText: 2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2015-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "albumwatch.h" 0017 0018 // Qt includes 0019 0020 #include <QFileSystemWatcher> 0021 #include <QDateTime> 0022 #include <QFileInfo> 0023 #include <QDir> 0024 0025 // Local includes 0026 0027 #include "digikam_debug.h" 0028 #include "album.h" 0029 #include "albummanager.h" 0030 #include "collectionlocation.h" 0031 #include "collectionmanager.h" 0032 #include "dbengineparameters.h" 0033 #include "applicationsettings.h" 0034 #include "scancontroller.h" 0035 #include "dio.h" 0036 0037 namespace Digikam 0038 { 0039 0040 class Q_DECL_HIDDEN AlbumWatch::Private 0041 { 0042 public: 0043 0044 explicit Private() 0045 : dirWatch(nullptr) 0046 { 0047 } 0048 0049 bool inBlackList(const QString& path) const; 0050 bool inDirWatchParametersBlackList(const QFileInfo& info, const QString& path); 0051 QList<QDateTime> buildDirectoryModList(const QFileInfo& dbFile) const; 0052 0053 public: 0054 0055 QFileSystemWatcher* dirWatch; 0056 0057 DbEngineParameters params; 0058 QStringList fileNameBlackList; 0059 QList<QDateTime> dbPathModificationDateList; 0060 }; 0061 0062 bool AlbumWatch::Private::inBlackList(const QString& path) const 0063 { 0064 // Filter out dirty signals triggered by changes on the database file 0065 0066 Q_FOREACH (const QString& bannedFile, fileNameBlackList) 0067 { 0068 if (path.endsWith(bannedFile)) 0069 { 0070 return true; 0071 } 0072 } 0073 0074 return false; 0075 } 0076 0077 bool AlbumWatch::Private::inDirWatchParametersBlackList(const QFileInfo& info, const QString& path) 0078 { 0079 if (params.isSQLite()) 0080 { 0081 QDir dir; 0082 0083 if (info.isDir()) 0084 { 0085 dir = QDir(path); 0086 } 0087 else 0088 { 0089 dir = info.dir(); 0090 } 0091 0092 QFileInfo dbFile(params.SQLiteDatabaseFile()); 0093 0094 // is the signal for the directory containing the database file? 0095 0096 if (dbFile.dir() == dir) 0097 { 0098 // retrieve modification dates 0099 0100 QList<QDateTime> modList = buildDirectoryModList(dbFile); 0101 0102 // check for equality 0103 0104 if (modList == dbPathModificationDateList) 0105 { 0106 //qCDebug(DIGIKAM_GENERAL_LOG) << "Filtering out db-file-triggered dir watch signal"; 0107 0108 // we can skip the signal 0109 0110 return true; 0111 } 0112 0113 // set new list 0114 0115 dbPathModificationDateList = modList; 0116 } 0117 } 0118 0119 return false; 0120 } 0121 0122 QList<QDateTime> AlbumWatch::Private::buildDirectoryModList(const QFileInfo& dbFile) const 0123 { 0124 // Retrieve modification dates 0125 0126 QList<QDateTime> modList; 0127 QFileInfoList fileInfoList = dbFile.dir().entryInfoList(QDir::Dirs | 0128 QDir::Files | 0129 QDir::NoDotAndDotDot); 0130 0131 // Build the list 0132 0133 Q_FOREACH (const QFileInfo& info, fileInfoList) 0134 { 0135 // Ignore digikam4.db and journal and other temporary files 0136 0137 if (!fileNameBlackList.contains(info.fileName())) 0138 { 0139 modList << info.lastModified(); 0140 } 0141 } 0142 0143 return modList; 0144 } 0145 0146 // ------------------------------------------------------------------------------------- 0147 0148 AlbumWatch::AlbumWatch(AlbumManager* const parent) 0149 : QObject(parent), 0150 d(new Private) 0151 { 0152 d->dirWatch = new QFileSystemWatcher(this); 0153 0154 if (ApplicationSettings::instance()->getAlbumMonitoring()) 0155 { 0156 qCDebug(DIGIKAM_GENERAL_LOG) << "AlbumWatch use QFileSystemWatcher"; 0157 0158 connect(d->dirWatch, SIGNAL(directoryChanged(QString)), 0159 this, SLOT(slotQFSWatcherDirty(QString))); 0160 0161 connect(d->dirWatch, SIGNAL(fileChanged(QString)), 0162 this, SLOT(slotQFSWatcherDirty(QString))); 0163 0164 connect(parent, SIGNAL(signalAlbumAdded(Album*)), 0165 this, SLOT(slotAlbumAdded(Album*))); 0166 0167 connect(parent, SIGNAL(signalAlbumRenamed(Album*)), 0168 this, SLOT(slotAlbumAdded(Album*))); 0169 0170 connect(parent, SIGNAL(signalAlbumNewPath(Album*)), 0171 this, SLOT(slotAlbumAdded(Album*))); 0172 0173 connect(parent, SIGNAL(signalAlbumAboutToBeDeleted(Album*)), 0174 this, SLOT(slotAlbumAboutToBeDeleted(Album*))); 0175 } 0176 else 0177 { 0178 qCDebug(DIGIKAM_GENERAL_LOG) << "AlbumWatch is disabled"; 0179 } 0180 } 0181 0182 AlbumWatch::~AlbumWatch() 0183 { 0184 delete d; 0185 } 0186 0187 void AlbumWatch::clear() 0188 { 0189 if (d->dirWatch && !d->dirWatch->directories().isEmpty()) 0190 { 0191 d->dirWatch->removePaths(d->dirWatch->directories()); 0192 } 0193 } 0194 0195 void AlbumWatch::removeWatchedPAlbums(const PAlbum* const album) 0196 { 0197 if (!album || d->dirWatch->directories().isEmpty()) 0198 { 0199 return; 0200 } 0201 0202 Q_FOREACH (const QString& dir, d->dirWatch->directories()) 0203 { 0204 if (dir.startsWith(album->folderPath())) 0205 { 0206 d->dirWatch->removePath(dir); 0207 } 0208 } 0209 } 0210 0211 void AlbumWatch::setDbEngineParameters(const DbEngineParameters& params) 0212 { 0213 d->params = params; 0214 0215 d->fileNameBlackList.clear(); 0216 0217 // filter out notifications caused by database operations 0218 0219 if (params.isSQLite()) 0220 { 0221 d->fileNameBlackList << QLatin1String("thumbnails-digikam.db") 0222 << QLatin1String("thumbnails-digikam.db-journal"); 0223 d->fileNameBlackList << QLatin1String("recognition.db") 0224 << QLatin1String("recognition.db-journal"); 0225 0226 QFileInfo dbFile(params.SQLiteDatabaseFile()); 0227 d->fileNameBlackList << dbFile.fileName() 0228 << dbFile.fileName() + QLatin1String("-journal"); 0229 0230 // ensure this is done after setting up the black list 0231 0232 d->dbPathModificationDateList = d->buildDirectoryModList(dbFile); 0233 } 0234 } 0235 0236 void AlbumWatch::slotAlbumAdded(Album* a) 0237 { 0238 if (a->isRoot() || a->isTrashAlbum() || (a->type() != Album::PHYSICAL)) 0239 { 0240 return; 0241 } 0242 0243 PAlbum* const album = static_cast<PAlbum*>(a); 0244 CollectionLocation location = CollectionManager::instance()->locationForAlbumRootId(album->albumRootId()); 0245 0246 if (!location.isAvailable()) 0247 { 0248 return; 0249 } 0250 0251 QString dir = album->folderPath(); 0252 0253 if (dir.isEmpty()) 0254 { 0255 return; 0256 } 0257 0258 d->dirWatch->addPath(dir); 0259 } 0260 0261 void AlbumWatch::slotAlbumAboutToBeDeleted(Album* a) 0262 { 0263 if (a->isRoot() || a->isTrashAlbum() || (a->type() != Album::PHYSICAL)) 0264 { 0265 return; 0266 } 0267 0268 PAlbum* const album = static_cast<PAlbum*>(a); 0269 QString dir = album->folderPath(); 0270 0271 if (dir.isEmpty()) 0272 { 0273 return; 0274 } 0275 0276 d->dirWatch->removePath(dir); 0277 } 0278 0279 void AlbumWatch::rescanDirectory(const QString& dir) 0280 { 0281 if (DIO::itemsUnderProcessing()) 0282 { 0283 return; 0284 } 0285 0286 qCDebug(DIGIKAM_GENERAL_LOG) << "Detected change, triggering rescan of" << dir; 0287 0288 ScanController::instance()->scheduleCollectionScanExternal(dir); 0289 } 0290 0291 void AlbumWatch::slotQFSWatcherDirty(const QString& path) 0292 { 0293 if (d->inBlackList(path)) 0294 { 0295 return; 0296 } 0297 0298 QFileInfo info(path); 0299 0300 if (d->inDirWatchParametersBlackList(info, path)) 0301 { 0302 return; 0303 } 0304 0305 if (info.isDir()) 0306 { 0307 rescanDirectory(path); 0308 } 0309 else 0310 { 0311 rescanDirectory(info.path()); 0312 } 0313 } 0314 0315 } // namespace Digikam 0316 0317 #include "moc_albumwatch.cpp"