1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qfilesystemwatcher.h"
5#include "qfilesystemwatcher_p.h"
6
7#include <qdatetime.h>
8#include <qdir.h>
9#include <qfileinfo.h>
10#include <qloggingcategory.h>
11#include <qset.h>
12#include <qtimer.h>
13
14#if (defined(Q_OS_LINUX) || defined(Q_OS_QNX)) && QT_CONFIG(inotify)
15#define USE_INOTIFY
16#endif
17
18#include "qfilesystemwatcher_polling_p.h"
19#if defined(Q_OS_WIN)
20# include "qfilesystemwatcher_win_p.h"
21#elif defined(USE_INOTIFY)
22# include "qfilesystemwatcher_inotify_p.h"
23#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) || defined(QT_PLATFORM_UIKIT)
24# include "qfilesystemwatcher_kqueue_p.h"
25#elif defined(Q_OS_MACOS)
26# include "qfilesystemwatcher_fsevents_p.h"
27#endif
28
29#include <algorithm>
30#include <iterator>
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36Q_LOGGING_CATEGORY(lcWatcher, "qt.core.filesystemwatcher")
37
38QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine(QObject *parent)
39{
40#if defined(Q_OS_WIN)
41 return new QWindowsFileSystemWatcherEngine(parent);
42#elif defined(USE_INOTIFY)
43 // there is a chance that inotify may fail on Linux pre-2.6.13 (August
44 // 2005), so we can't just new inotify directly.
45 return QInotifyFileSystemWatcherEngine::create(parent);
46#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) || defined(QT_PLATFORM_UIKIT)
47 return QKqueueFileSystemWatcherEngine::create(parent);
48#elif defined(Q_OS_MACOS)
49 return QFseventsFileSystemWatcherEngine::create(parent);
50#else
51 Q_UNUSED(parent);
52 return 0;
53#endif
54}
55
56QFileSystemWatcherPrivate::QFileSystemWatcherPrivate()
57 : native(nullptr), poller(nullptr)
58{
59}
60
61void QFileSystemWatcherPrivate::connectEngine(QFileSystemWatcherEngine *engine)
62{
63 QObjectPrivate::connect(sender: engine, signal: &QFileSystemWatcherEngine::fileChanged,
64 receiverPrivate: this, slot: &QFileSystemWatcherPrivate::fileChanged);
65 QObjectPrivate::connect(sender: engine, signal: &QFileSystemWatcherEngine::directoryChanged,
66 receiverPrivate: this, slot: &QFileSystemWatcherPrivate::directoryChanged);
67}
68
69void QFileSystemWatcherPrivate::init()
70{
71 Q_Q(QFileSystemWatcher);
72 native = createNativeEngine(parent: q);
73 if (native) {
74 connectEngine(engine: native);
75#if defined(Q_OS_WIN)
76 auto *windowsWatcher = static_cast<QWindowsFileSystemWatcherEngine *>(native);
77 using WinE = QWindowsFileSystemWatcherEngine;
78 QObjectPrivate::connect(windowsWatcher, &WinE::driveLockForRemoval,
79 this, &QFileSystemWatcherPrivate::winDriveLockForRemoval);
80 QObjectPrivate::connect(windowsWatcher, &WinE::driveLockForRemovalFailed,
81 this, &QFileSystemWatcherPrivate::winDriveLockForRemovalFailed);
82 QObjectPrivate::connect(windowsWatcher, &WinE::driveRemoved,
83 this, &QFileSystemWatcherPrivate::winDriveRemoved);
84#endif // Q_OS_WIN
85 }
86}
87
88void QFileSystemWatcherPrivate::initPollerEngine()
89{
90 if (poller)
91 return;
92
93 Q_Q(QFileSystemWatcher);
94 poller = new QPollingFileSystemWatcherEngine(q); // that was a mouthful
95 connectEngine(engine: poller);
96}
97
98void QFileSystemWatcherPrivate::fileChanged(const QString &path, bool removed)
99{
100 Q_Q(QFileSystemWatcher);
101 qCDebug(lcWatcher) << "file changed" << path << "removed?" << removed << "watching?" << files.contains(str: path);
102 if (!files.contains(str: path)) {
103 // the path was removed after a change was detected, but before we delivered the signal
104 return;
105 }
106 if (removed)
107 files.removeAll(t: path);
108 emit q->fileChanged(path, QFileSystemWatcher::QPrivateSignal());
109}
110
111void QFileSystemWatcherPrivate::directoryChanged(const QString &path, bool removed)
112{
113 Q_Q(QFileSystemWatcher);
114 qCDebug(lcWatcher) << "directory changed" << path << "removed?" << removed << "watching?" << directories.contains(str: path);
115 if (!directories.contains(str: path)) {
116 // perhaps the path was removed after a change was detected, but before we delivered the signal
117 return;
118 }
119 if (removed)
120 directories.removeAll(t: path);
121 emit q->directoryChanged(path, QFileSystemWatcher::QPrivateSignal());
122}
123
124#if defined(Q_OS_WIN)
125
126void QFileSystemWatcherPrivate::winDriveLockForRemoval(const QString &path)
127{
128 // Windows: Request to lock a (removable/USB) drive for removal, release
129 // its paths under watch, temporarily storing them should the lock fail.
130 Q_Q(QFileSystemWatcher);
131 QStringList pathsToBeRemoved;
132 auto pred = [&path] (const QString &f) { return !f.startsWith(path, Qt::CaseInsensitive); };
133 std::remove_copy_if(files.cbegin(), files.cend(),
134 std::back_inserter(pathsToBeRemoved), pred);
135 std::remove_copy_if(directories.cbegin(), directories.cend(),
136 std::back_inserter(pathsToBeRemoved), pred);
137 if (!pathsToBeRemoved.isEmpty()) {
138 q->removePaths(pathsToBeRemoved);
139 temporarilyRemovedPaths.insert(path.at(0), pathsToBeRemoved);
140 }
141}
142
143void QFileSystemWatcherPrivate::winDriveLockForRemovalFailed(const QString &path)
144{
145 // Windows: Request to lock a (removable/USB) drive failed (blocked by other
146 // application), restore the watched paths.
147 Q_Q(QFileSystemWatcher);
148 if (!path.isEmpty()) {
149 const auto it = temporarilyRemovedPaths.find(path.at(0));
150 if (it != temporarilyRemovedPaths.end()) {
151 q->addPaths(it.value());
152 temporarilyRemovedPaths.erase(it);
153 }
154 }
155}
156
157void QFileSystemWatcherPrivate::winDriveRemoved(const QString &path)
158{
159 // Windows: Drive finally removed, clear out paths stored in lock request.
160 if (!path.isEmpty())
161 temporarilyRemovedPaths.remove(path.at(0));
162}
163#endif // Q_OS_WIN
164
165/*!
166 \class QFileSystemWatcher
167 \inmodule QtCore
168 \brief The QFileSystemWatcher class provides an interface for monitoring files and directories for modifications.
169 \ingroup io
170 \since 4.2
171 \reentrant
172
173 QFileSystemWatcher monitors the file system for changes to files
174 and directories by watching a list of specified paths.
175
176 Call addPath() to watch a particular file or directory. Multiple
177 paths can be added using the addPaths() function. Existing paths can
178 be removed by using the removePath() and removePaths() functions.
179
180 QFileSystemWatcher examines each path added to it. Files that have
181 been added to the QFileSystemWatcher can be accessed using the
182 files() function, and directories using the directories() function.
183
184 The fileChanged() signal is emitted when a file has been modified,
185 renamed or removed from disk. Similarly, the directoryChanged()
186 signal is emitted when a directory or its contents is modified or
187 removed. Note that QFileSystemWatcher stops monitoring files once
188 they have been renamed or removed from disk, and directories once
189 they have been removed from disk.
190
191 \list
192 \li \b Notes:
193 \list
194 \li On systems running a Linux kernel without inotify support,
195 file systems that contain watched paths cannot be unmounted.
196
197 \li The act of monitoring files and directories for
198 modifications consumes system resources. This implies there is a
199 limit to the number of files and directories your process can
200 monitor simultaneously. On all BSD variants, for
201 example, an open file descriptor is required for each monitored
202 file. Some system limits the number of open file descriptors to 256
203 by default. This means that addPath() and addPaths() will fail if
204 your process tries to add more than 256 files or directories to
205 the file system monitor. Also note that your process may have
206 other file descriptors open in addition to the ones for files
207 being monitored, and these other open descriptors also count in
208 the total. \macos uses a different backend and does not
209 suffer from this issue.
210 \endlist
211 \endlist
212
213 \sa QFile, QDir
214*/
215
216
217/*!
218 Constructs a new file system watcher object with the given \a parent.
219*/
220QFileSystemWatcher::QFileSystemWatcher(QObject *parent)
221 : QObject(*new QFileSystemWatcherPrivate, parent)
222{
223 d_func()->init();
224}
225
226/*!
227 Constructs a new file system watcher object with the given \a parent
228 which monitors the specified \a paths list.
229*/
230QFileSystemWatcher::QFileSystemWatcher(const QStringList &paths, QObject *parent)
231 : QObject(*new QFileSystemWatcherPrivate, parent)
232{
233 d_func()->init();
234 addPaths(files: paths);
235}
236
237/*!
238 Destroys the file system watcher.
239*/
240QFileSystemWatcher::~QFileSystemWatcher()
241{ }
242
243/*!
244 Adds \a path to the file system watcher if \a path exists. The
245 path is not added if it does not exist, or if it is already being
246 monitored by the file system watcher.
247
248 If \a path specifies a directory, the directoryChanged() signal
249 will be emitted when \a path is modified or removed from disk;
250 otherwise the fileChanged() signal is emitted when \a path is
251 modified, renamed or removed.
252
253 If the watch was successful, true is returned.
254
255 Reasons for a watch failure are generally system-dependent, but
256 may include the resource not existing, access failures, or the
257 total watch count limit, if the platform has one.
258
259 \note There may be a system dependent limit to the number of
260 files and directories that can be monitored simultaneously.
261 If this limit is been reached, \a path will not be monitored,
262 and false is returned.
263
264 \sa addPaths(), removePath()
265*/
266bool QFileSystemWatcher::addPath(const QString &path)
267{
268 if (path.isEmpty()) {
269 qWarning(msg: "QFileSystemWatcher::addPath: path is empty");
270 return true;
271 }
272
273 QStringList paths = addPaths(files: QStringList(path));
274 return paths.isEmpty();
275}
276
277static QStringList empty_paths_pruned(const QStringList &paths)
278{
279 QStringList p;
280 p.reserve(asize: paths.size());
281 const auto isEmpty = [](const QString &s) { return s.isEmpty(); };
282 std::remove_copy_if(first: paths.begin(), last: paths.end(),
283 result: std::back_inserter(x&: p),
284 pred: isEmpty);
285 return p;
286}
287
288/*!
289 Adds each path in \a paths to the file system watcher. Paths are
290 not added if they not exist, or if they are already being
291 monitored by the file system watcher.
292
293 If a path specifies a directory, the directoryChanged() signal
294 will be emitted when the path is modified or removed from disk;
295 otherwise the fileChanged() signal is emitted when the path is
296 modified, renamed, or removed.
297
298 The return value is a list of paths that could not be watched.
299
300 Reasons for a watch failure are generally system-dependent, but
301 may include the resource not existing, access failures, or the
302 total watch count limit, if the platform has one.
303
304 \note There may be a system dependent limit to the number of
305 files and directories that can be monitored simultaneously.
306 If this limit has been reached, the excess \a paths will not
307 be monitored, and they will be added to the returned QStringList.
308
309 \sa addPath(), removePaths()
310*/
311QStringList QFileSystemWatcher::addPaths(const QStringList &paths)
312{
313 Q_D(QFileSystemWatcher);
314
315 QStringList p = empty_paths_pruned(paths);
316
317 if (p.isEmpty()) {
318 qWarning(msg: "QFileSystemWatcher::addPaths: list is empty");
319 return p;
320 }
321 qCDebug(lcWatcher) << "adding" << paths;
322 const auto selectEngine = [this, d]() -> QFileSystemWatcherEngine* {
323#ifdef QT_BUILD_INTERNAL
324 const QString on = objectName();
325
326 if (Q_UNLIKELY(on.startsWith("_qt_autotest_force_engine_"_L1))) {
327 // Autotest override case - use the explicitly selected engine only
328 const auto forceName = QStringView{on}.mid(pos: 26);
329 if (forceName == "poller"_L1) {
330 qCDebug(lcWatcher, "QFileSystemWatcher: skipping native engine, using only polling engine");
331 d_func()->initPollerEngine();
332 return d->poller;
333 } else if (forceName == "native"_L1) {
334 qCDebug(lcWatcher, "QFileSystemWatcher: skipping polling engine, using only native engine");
335 return d->native;
336 }
337 return nullptr;
338 }
339#endif
340 // Normal runtime case - search intelligently for best engine
341 if (d->native) {
342 return d->native;
343 } else {
344 d_func()->initPollerEngine();
345 return d->poller;
346 }
347 };
348
349 if (auto engine = selectEngine())
350 p = engine->addPaths(paths: p, files: &d->files, directories: &d->directories);
351
352 return p;
353}
354
355/*!
356 Removes the specified \a path from the file system watcher.
357
358 If the watch is successfully removed, true is returned.
359
360 Reasons for watch removal failing are generally system-dependent,
361 but may be due to the path having already been deleted, for example.
362
363 \sa removePaths(), addPath()
364*/
365bool QFileSystemWatcher::removePath(const QString &path)
366{
367 if (path.isEmpty()) {
368 qWarning(msg: "QFileSystemWatcher::removePath: path is empty");
369 return true;
370 }
371
372 QStringList paths = removePaths(files: QStringList(path));
373 return paths.isEmpty();
374}
375
376/*!
377 Removes the specified \a paths from the file system watcher.
378
379 The return value is a list of paths which were not able to be
380 unwatched successfully.
381
382 Reasons for watch removal failing are generally system-dependent,
383 but may be due to the path having already been deleted, for example.
384
385 \sa removePath(), addPaths()
386*/
387QStringList QFileSystemWatcher::removePaths(const QStringList &paths)
388{
389 Q_D(QFileSystemWatcher);
390
391 QStringList p = empty_paths_pruned(paths);
392
393 if (p.isEmpty()) {
394 qWarning(msg: "QFileSystemWatcher::removePaths: list is empty");
395 return p;
396 }
397 qCDebug(lcWatcher) << "removing" << paths;
398
399 if (d->native)
400 p = d->native->removePaths(paths: p, files: &d->files, directories: &d->directories);
401 if (d->poller)
402 p = d->poller->removePaths(paths: p, files: &d->files, directories: &d->directories);
403
404 return p;
405}
406
407/*!
408 \fn void QFileSystemWatcher::fileChanged(const QString &path)
409
410 This signal is emitted when the file at the specified \a path is
411 modified, renamed or removed from disk.
412
413 \note As a safety measure, many applications save an open file by
414 writing a new file and then deleting the old one. In your slot
415 function, you can check \c watcher.files().contains(path).
416 If it returns \c false, check whether the file still exists
417 and then call \c addPath() to continue watching it.
418
419 \sa directoryChanged()
420*/
421
422/*!
423 \fn void QFileSystemWatcher::directoryChanged(const QString &path)
424
425 This signal is emitted when the directory at a specified \a path
426 is modified (e.g., when a file is added or deleted) or removed
427 from disk. Note that if there are several changes during a short
428 period of time, some of the changes might not emit this signal.
429 However, the last change in the sequence of changes will always
430 generate this signal.
431
432 \sa fileChanged()
433*/
434
435/*!
436 \fn QStringList QFileSystemWatcher::directories() const
437
438 Returns a list of paths to directories that are being watched.
439
440 \sa files()
441*/
442
443/*!
444 \fn QStringList QFileSystemWatcher::files() const
445
446 Returns a list of paths to files that are being watched.
447
448 \sa directories()
449*/
450
451QStringList QFileSystemWatcher::directories() const
452{
453 Q_D(const QFileSystemWatcher);
454 return d->directories;
455}
456
457QStringList QFileSystemWatcher::files() const
458{
459 Q_D(const QFileSystemWatcher);
460 return d->files;
461}
462
463QT_END_NAMESPACE
464
465#include "moc_qfilesystemwatcher.cpp"
466#include "moc_qfilesystemwatcher_p.cpp"
467
468

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/corelib/io/qfilesystemwatcher.cpp