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"