1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2011-11-07
7 * Description : Directory watch interface
8 *
9 * Copyright (C) 2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10 * Copyright (C) 2015-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11 *
12 * This program is free software; you can redistribute it
13 * and/or modify it under the terms of the GNU General
14 * Public License as published by the Free Software Foundation;
15 * either version 2, or (at your option)
16 * any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * ============================================================ */
24
25 #include "albumwatch.h"
26
27 // Qt includes
28
29 #include <QFileSystemWatcher>
30 #include <QDateTime>
31 #include <QFileInfo>
32 #include <QDir>
33
34 // Local includes
35
36 #include "digikam_debug.h"
37 #include "album.h"
38 #include "albummanager.h"
39 #include "collectionlocation.h"
40 #include "collectionmanager.h"
41 #include "dbengineparameters.h"
42 #include "applicationsettings.h"
43 #include "scancontroller.h"
44 #include "dio.h"
45
46 namespace Digikam
47 {
48
49 class Q_DECL_HIDDEN AlbumWatch::Private
50 {
51 public:
52
Private()53 explicit Private()
54 : dirWatch(nullptr)
55 {
56 }
57
58 bool inBlackList(const QString& path) const;
59 bool inDirWatchParametersBlackList(const QFileInfo& info, const QString& path);
60 QList<QDateTime> buildDirectoryModList(const QFileInfo& dbFile) const;
61
62 public:
63
64 QFileSystemWatcher* dirWatch;
65
66 DbEngineParameters params;
67 QStringList fileNameBlackList;
68 QList<QDateTime> dbPathModificationDateList;
69 };
70
inBlackList(const QString & path) const71 bool AlbumWatch::Private::inBlackList(const QString& path) const
72 {
73 // Filter out dirty signals triggered by changes on the database file
74
75 foreach (const QString& bannedFile, fileNameBlackList)
76 {
77 if (path.endsWith(bannedFile))
78 {
79 return true;
80 }
81 }
82
83 return false;
84 }
85
inDirWatchParametersBlackList(const QFileInfo & info,const QString & path)86 bool AlbumWatch::Private::inDirWatchParametersBlackList(const QFileInfo& info, const QString& path)
87 {
88 if (params.isSQLite())
89 {
90 QDir dir;
91
92 if (info.isDir())
93 {
94 dir = QDir(path);
95 }
96 else
97 {
98 dir = info.dir();
99 }
100
101 QFileInfo dbFile(params.SQLiteDatabaseFile());
102
103 // is the signal for the directory containing the database file?
104
105 if (dbFile.dir() == dir)
106 {
107 // retrieve modification dates
108
109 QList<QDateTime> modList = buildDirectoryModList(dbFile);
110
111 // check for equality
112
113 if (modList == dbPathModificationDateList)
114 {
115 //qCDebug(DIGIKAM_GENERAL_LOG) << "Filtering out db-file-triggered dir watch signal";
116
117 // we can skip the signal
118
119 return true;
120 }
121
122 // set new list
123
124 dbPathModificationDateList = modList;
125 }
126 }
127
128 return false;
129 }
130
buildDirectoryModList(const QFileInfo & dbFile) const131 QList<QDateTime> AlbumWatch::Private::buildDirectoryModList(const QFileInfo& dbFile) const
132 {
133 // Retrieve modification dates
134
135 QList<QDateTime> modList;
136 QFileInfoList fileInfoList = dbFile.dir().entryInfoList(QDir::Dirs |
137 QDir::Files |
138 QDir::NoDotAndDotDot);
139
140 // Build the list
141
142 foreach (const QFileInfo& info, fileInfoList)
143 {
144 // Ignore digikam4.db and journal and other temporary files
145
146 if (!fileNameBlackList.contains(info.fileName()))
147 {
148 modList << info.lastModified();
149 }
150 }
151
152 return modList;
153 }
154
155 // -------------------------------------------------------------------------------------
156
AlbumWatch(AlbumManager * const parent)157 AlbumWatch::AlbumWatch(AlbumManager* const parent)
158 : QObject(parent),
159 d(new Private)
160 {
161 d->dirWatch = new QFileSystemWatcher(this);
162
163 if (ApplicationSettings::instance()->getAlbumMonitoring())
164 {
165 qCDebug(DIGIKAM_GENERAL_LOG) << "AlbumWatch use QFileSystemWatcher";
166
167 connect(d->dirWatch, SIGNAL(directoryChanged(QString)),
168 this, SLOT(slotQFSWatcherDirty(QString)));
169
170 connect(d->dirWatch, SIGNAL(fileChanged(QString)),
171 this, SLOT(slotQFSWatcherDirty(QString)));
172
173 connect(parent, SIGNAL(signalAlbumAdded(Album*)),
174 this, SLOT(slotAlbumAdded(Album*)));
175
176 connect(parent, SIGNAL(signalAlbumRenamed(Album*)),
177 this, SLOT(slotAlbumAdded(Album*)));
178
179 connect(parent, SIGNAL(signalAlbumNewPath(Album*)),
180 this, SLOT(slotAlbumAdded(Album*)));
181
182 connect(parent, SIGNAL(signalAlbumAboutToBeDeleted(Album*)),
183 this, SLOT(slotAlbumAboutToBeDeleted(Album*)));
184 }
185 else
186 {
187 qCDebug(DIGIKAM_GENERAL_LOG) << "AlbumWatch is disabled";
188 }
189 }
190
~AlbumWatch()191 AlbumWatch::~AlbumWatch()
192 {
193 delete d;
194 }
195
clear()196 void AlbumWatch::clear()
197 {
198 if (d->dirWatch && !d->dirWatch->directories().isEmpty())
199 {
200 d->dirWatch->removePaths(d->dirWatch->directories());
201 }
202 }
203
removeWatchedPAlbums(const PAlbum * const album)204 void AlbumWatch::removeWatchedPAlbums(const PAlbum* const album)
205 {
206 if (!album || d->dirWatch->directories().isEmpty())
207 {
208 return;
209 }
210
211 foreach (const QString& dir, d->dirWatch->directories())
212 {
213 if (dir.startsWith(album->folderPath()))
214 {
215 d->dirWatch->removePath(dir);
216 }
217 }
218 }
219
setDbEngineParameters(const DbEngineParameters & params)220 void AlbumWatch::setDbEngineParameters(const DbEngineParameters& params)
221 {
222 d->params = params;
223
224 d->fileNameBlackList.clear();
225
226 // filter out notifications caused by database operations
227
228 if (params.isSQLite())
229 {
230 d->fileNameBlackList << QLatin1String("thumbnails-digikam.db")
231 << QLatin1String("thumbnails-digikam.db-journal");
232 d->fileNameBlackList << QLatin1String("recognition.db")
233 << QLatin1String("recognition.db-journal");
234
235 QFileInfo dbFile(params.SQLiteDatabaseFile());
236 d->fileNameBlackList << dbFile.fileName()
237 << dbFile.fileName() + QLatin1String("-journal");
238
239 // ensure this is done after setting up the black list
240
241 d->dbPathModificationDateList = d->buildDirectoryModList(dbFile);
242 }
243 }
244
slotAlbumAdded(Album * a)245 void AlbumWatch::slotAlbumAdded(Album* a)
246 {
247 if (a->isRoot() || a->isTrashAlbum() || (a->type() != Album::PHYSICAL))
248 {
249 return;
250 }
251
252 PAlbum* const album = static_cast<PAlbum*>(a);
253 CollectionLocation location = CollectionManager::instance()->locationForAlbumRootId(album->albumRootId());
254
255 if (!location.isAvailable())
256 {
257 return;
258 }
259
260 QString dir = album->folderPath();
261
262 if (dir.isEmpty())
263 {
264 return;
265 }
266
267 d->dirWatch->addPath(dir);
268 }
269
slotAlbumAboutToBeDeleted(Album * a)270 void AlbumWatch::slotAlbumAboutToBeDeleted(Album* a)
271 {
272 if (a->isRoot() || a->isTrashAlbum() || (a->type() != Album::PHYSICAL))
273 {
274 return;
275 }
276
277 PAlbum* const album = static_cast<PAlbum*>(a);
278 QString dir = album->folderPath();
279
280 if (dir.isEmpty())
281 {
282 return;
283 }
284
285 d->dirWatch->removePath(dir);
286 }
287
rescanDirectory(const QString & dir)288 void AlbumWatch::rescanDirectory(const QString& dir)
289 {
290 if (DIO::itemsUnderProcessing())
291 {
292 return;
293 }
294
295 qCDebug(DIGIKAM_GENERAL_LOG) << "Detected change, triggering rescan of" << dir;
296
297 ScanController::instance()->scheduleCollectionScanExternal(dir);
298 }
299
slotQFSWatcherDirty(const QString & path)300 void AlbumWatch::slotQFSWatcherDirty(const QString& path)
301 {
302 if (d->inBlackList(path))
303 {
304 return;
305 }
306
307 QFileInfo info(path);
308
309 if (d->inDirWatchParametersBlackList(info, path))
310 {
311 return;
312 }
313
314 if (info.isDir())
315 {
316 rescanDirectory(path);
317 }
318 else
319 {
320 rescanDirectory(info.path());
321 }
322 }
323
324 } // namespace Digikam
325