source: trunk/src/corelib/io/qfilesystemwatcher.cpp@ 440

Last change on this file since 440 was 374, checked in by Dmitry A. Kuminov, 16 years ago

corelib/io: Improved the polling QFileSystemWatcher implementation so that it doesn't eat all CPU when there are more than 100 items to watch any more.

File size: 19.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qfilesystemwatcher.h"
43#include "qfilesystemwatcher_p.h"
44
45#ifndef QT_NO_FILESYSTEMWATCHER
46
47#include <qdatetime.h>
48#include <qdebug.h>
49#include <qdir.h>
50#include <qfileinfo.h>
51#include <qmutex.h>
52#include <qset.h>
53#include <qwaitcondition.h>
54
55#if defined(Q_OS_WIN)
56# include "qfilesystemwatcher_win_p.h"
57#elif defined(Q_OS_LINUX)
58# include "qfilesystemwatcher_inotify_p.h"
59# include "qfilesystemwatcher_dnotify_p.h"
60#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
61# include "qfilesystemwatcher_kqueue_p.h"
62#endif
63
64QT_BEGIN_NAMESPACE
65
66class QPollingFileSystemWatcherEngine : public QFileSystemWatcherEngine
67{
68 enum {
69 SmallPollingInterval = 50, // time to wait after checking each watched
70 // item, ms
71 BigPollingInterval = 1000, // time to wait after checking all watched
72 // items, ms
73 };
74
75 class FileInfo
76 {
77 uint ownerId;
78 uint groupId;
79 QFile::Permissions permissions;
80 QDateTime lastModified;
81 QStringList entries;
82
83 public:
84 FileInfo(const QFileInfo &fileInfo)
85 : ownerId(fileInfo.ownerId()),
86 groupId(fileInfo.groupId()),
87 permissions(fileInfo.permissions()),
88 lastModified(fileInfo.lastModified())
89 {
90 if (fileInfo.isDir()) {
91 entries = fileInfo.absoluteDir().entryList(QDir::AllEntries);
92 }
93 }
94 FileInfo &operator=(const QFileInfo &fileInfo)
95 {
96 *this = FileInfo(fileInfo);
97 return *this;
98 }
99
100 bool operator!=(const QFileInfo &fileInfo) const
101 {
102 if (fileInfo.isDir() && entries != fileInfo.absoluteDir().entryList(QDir::AllEntries))
103 return true;
104 return (ownerId != fileInfo.ownerId()
105 || groupId != fileInfo.groupId()
106 || permissions != fileInfo.permissions()
107 || lastModified != fileInfo.lastModified());
108 }
109 };
110
111 mutable QMutex mutex;
112 QHash<QString, FileInfo> files, directories;
113
114public:
115 QPollingFileSystemWatcherEngine();
116
117 void run();
118
119 QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories);
120 QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories);
121
122 void stop();
123
124private:
125 QWaitCondition waitCond;
126 bool askedToFinish;
127 bool startOver;
128};
129
130QPollingFileSystemWatcherEngine::QPollingFileSystemWatcherEngine()
131 : askedToFinish(false), startOver(false)
132{
133#ifndef QT_NO_THREAD
134 moveToThread(this);
135#endif
136}
137
138void QPollingFileSystemWatcherEngine::run()
139{
140 mutex.lock();
141
142 forever {
143 startOver = false;
144 QMutableHashIterator<QString, FileInfo> fit(files);
145 while (!startOver && fit.hasNext()) {
146 QHash<QString, FileInfo>::iterator x = fit.next();
147 QString path = x.key();
148 QFileInfo fi(path);
149 if (!fi.exists()) {
150 fit.remove();
151 emit fileChanged(path, true);
152 } else if (x.value() != fi) {
153 x.value() = fi;
154 emit fileChanged(path, false);
155 }
156 waitCond.wait(&mutex, SmallPollingInterval);
157 }
158 QMutableHashIterator<QString, FileInfo> dit(directories);
159 while (!startOver && dit.hasNext()) {
160 QHash<QString, FileInfo>::iterator x = dit.next();
161 QString path = x.key();
162 QFileInfo fi(path);
163 if (!path.endsWith(QLatin1Char('/')))
164 fi = QFileInfo(path + QLatin1Char('/'));
165 if (!fi.exists()) {
166 dit.remove();
167 emit directoryChanged(path, true);
168 } else if (x.value() != fi) {
169 x.value() = fi;
170 emit directoryChanged(path, false);
171 }
172 waitCond.wait(&mutex, SmallPollingInterval);
173 }
174 if (!startOver && !askedToFinish) {
175 waitCond.wait(&mutex, BigPollingInterval);
176 }
177 if (askedToFinish) {
178 askedToFinish = false;
179 break;
180 }
181 }
182
183 mutex.unlock();
184}
185
186QStringList QPollingFileSystemWatcherEngine::addPaths(const QStringList &paths,
187 QStringList *files,
188 QStringList *directories)
189{
190 QMutexLocker locker(&mutex);
191 QStringList p = paths;
192 QMutableListIterator<QString> it(p);
193 while (it.hasNext()) {
194 QString path = it.next();
195 QFileInfo fi(path);
196 if (!fi.exists())
197 continue;
198 if (fi.isDir()) {
199 if (!directories->contains(path))
200 directories->append(path);
201 if (!path.endsWith(QLatin1Char('/')))
202 fi = QFileInfo(path + QLatin1Char('/'));
203 this->directories.insert(path, fi);
204 startOver = true;
205 } else {
206 if (!files->contains(path))
207 files->append(path);
208 this->files.insert(path, fi);
209 startOver = true;
210 }
211 it.remove();
212 }
213 start(IdlePriority);
214 return p;
215}
216
217QStringList QPollingFileSystemWatcherEngine::removePaths(const QStringList &paths,
218 QStringList *files,
219 QStringList *directories)
220{
221 QMutexLocker locker(&mutex);
222 QStringList p = paths;
223 QMutableListIterator<QString> it(p);
224 while (it.hasNext()) {
225 QString path = it.next();
226 if (this->directories.remove(path)) {
227 directories->removeAll(path);
228 it.remove();
229 startOver = true;
230 } else if (this->files.remove(path)) {
231 files->removeAll(path);
232 it.remove();
233 startOver = true;
234 }
235 }
236 if (this->files.isEmpty() && this->directories.isEmpty()) {
237 askedToFinish = true;
238 waitCond.wakeAll();
239 locker.unlock();
240 wait();
241 }
242 return p;
243}
244
245void QPollingFileSystemWatcherEngine::stop()
246{
247 QMutexLocker locker(&mutex);
248 startOver = true;
249 askedToFinish = true;
250 waitCond.wakeAll();
251}
252
253
254QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine()
255{
256#if defined(Q_OS_WIN)
257 return new QWindowsFileSystemWatcherEngine;
258#elif defined(Q_OS_LINUX)
259 QFileSystemWatcherEngine *eng = QInotifyFileSystemWatcherEngine::create();
260 if(!eng)
261 eng = QDnotifyFileSystemWatcherEngine::create();
262 return eng;
263#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
264 return QKqueueFileSystemWatcherEngine::create();
265#else
266 return 0;
267#endif
268}
269
270QFileSystemWatcherPrivate::QFileSystemWatcherPrivate()
271 : native(0), poller(0), forced(0)
272{
273}
274
275void QFileSystemWatcherPrivate::init()
276{
277 Q_Q(QFileSystemWatcher);
278 native = createNativeEngine();
279 if (native) {
280 QObject::connect(native,
281 SIGNAL(fileChanged(QString,bool)),
282 q,
283 SLOT(_q_fileChanged(QString,bool)));
284 QObject::connect(native,
285 SIGNAL(directoryChanged(QString,bool)),
286 q,
287 SLOT(_q_directoryChanged(QString,bool)));
288 }
289}
290
291void QFileSystemWatcherPrivate::initForcedEngine(const QString &forceName)
292{
293 if(forced)
294 return;
295
296 Q_Q(QFileSystemWatcher);
297
298#if defined(Q_OS_LINUX)
299 if(forceName == QLatin1String("inotify")) {
300 forced = QInotifyFileSystemWatcherEngine::create();
301 } else if(forceName == QLatin1String("dnotify")) {
302 forced = QDnotifyFileSystemWatcherEngine::create();
303 }
304#else
305 Q_UNUSED(forceName);
306#endif
307
308 if(forced) {
309 QObject::connect(forced,
310 SIGNAL(fileChanged(QString,bool)),
311 q,
312 SLOT(_q_fileChanged(QString,bool)));
313 QObject::connect(forced,
314 SIGNAL(directoryChanged(QString,bool)),
315 q,
316 SLOT(_q_directoryChanged(QString,bool)));
317 }
318}
319
320void QFileSystemWatcherPrivate::initPollerEngine()
321{
322 if(poller)
323 return;
324
325 Q_Q(QFileSystemWatcher);
326 poller = new QPollingFileSystemWatcherEngine; // that was a mouthful
327 QObject::connect(poller,
328 SIGNAL(fileChanged(QString,bool)),
329 q,
330 SLOT(_q_fileChanged(QString,bool)));
331 QObject::connect(poller,
332 SIGNAL(directoryChanged(QString,bool)),
333 q,
334 SLOT(_q_directoryChanged(QString,bool)));
335}
336
337void QFileSystemWatcherPrivate::_q_fileChanged(const QString &path, bool removed)
338{
339 Q_Q(QFileSystemWatcher);
340 if (!files.contains(path)) {
341 // the path was removed after a change was detected, but before we delivered the signal
342 return;
343 }
344 if (removed)
345 files.removeAll(path);
346 emit q->fileChanged(path);
347}
348
349void QFileSystemWatcherPrivate::_q_directoryChanged(const QString &path, bool removed)
350{
351 Q_Q(QFileSystemWatcher);
352 if (!directories.contains(path)) {
353 // perhaps the path was removed after a change was detected, but before we delivered the signal
354 return;
355 }
356 if (removed)
357 directories.removeAll(path);
358 emit q->directoryChanged(path);
359}
360
361
362
363/*!
364 \class QFileSystemWatcher
365 \brief The QFileSystemWatcher class provides an interface for monitoring files and directories for modifications.
366 \ingroup io
367 \since 4.2
368 \reentrant
369
370 QFileSystemWatcher monitors the file system for changes to files
371 and directories by watching a list of specified paths.
372
373 Call addPath() to watch a particular file or directory. Multiple
374 paths can be added using the addPaths() function. Existing paths can
375 be removed by using the removePath() and removePaths() functions.
376
377 QFileSystemWatcher examines each path added to it. Files that have
378 been added to the QFileSystemWatcher can be accessed using the
379 files() function, and directories using the directories() function.
380
381 The fileChanged() signal is emitted when a file has been modified,
382 renamed or removed from disk. Similarly, the directoryChanged()
383 signal is emitted when a directory or its contents is modified or
384 removed. Note that QFileSystemWatcher stops monitoring files once
385 they have been renamed or removed from disk, and directories once
386 they have been removed from disk.
387
388 \note On systems running a Linux kernel without inotify support,
389 file systems that contain watched paths cannot be unmounted.
390
391 \note Windows CE does not support directory monitoring by
392 default as this depends on the file system driver installed.
393
394 \note The act of monitoring files and directories for
395 modifications consumes system resources. This implies there is a
396 limit to the number of files and directories your process can
397 monitor simultaneously. On Mac OS and all BSD variants, for
398 example, an open file descriptor is required for each monitored
399 file. The system limits the number of open file descriptors to 256
400 by default. This means that addPath() and addPaths() will fail if
401 your process tries to add more than 256 files or directories to
402 the file system monitor. Also note that your process may have
403 other file descriptors open in addition to the ones for files
404 being monitored, and these other open descriptors also count in
405 the total.
406
407 \sa QFile, QDir
408*/
409
410
411/*!
412 Constructs a new file system watcher object with the given \a parent.
413*/
414QFileSystemWatcher::QFileSystemWatcher(QObject *parent)
415 : QObject(*new QFileSystemWatcherPrivate, parent)
416{
417 d_func()->init();
418}
419
420/*!
421 Constructs a new file system watcher object with the given \a parent
422 which monitors the specified \a paths list.
423*/
424QFileSystemWatcher::QFileSystemWatcher(const QStringList &paths, QObject *parent)
425 : QObject(*new QFileSystemWatcherPrivate, parent)
426{
427 d_func()->init();
428 addPaths(paths);
429}
430
431/*!
432 Destroys the file system watcher.
433*/
434QFileSystemWatcher::~QFileSystemWatcher()
435{
436 Q_D(QFileSystemWatcher);
437 if (d->native) {
438 d->native->stop();
439 d->native->wait();
440 delete d->native;
441 d->native = 0;
442 }
443 if (d->poller) {
444 d->poller->stop();
445 d->poller->wait();
446 delete d->poller;
447 d->poller = 0;
448 }
449 if (d->forced) {
450 d->forced->stop();
451 d->forced->wait();
452 delete d->forced;
453 d->forced = 0;
454 }
455}
456
457/*!
458 Adds \a path to the file system watcher if \a path exists. The
459 path is not added if it does not exist, or if it is already being
460 monitored by the file system watcher.
461
462 If \a path specifies a directory, the directoryChanged() signal
463 will be emitted when \a path is modified or removed from disk;
464 otherwise the fileChanged() signal is emitted when \a path is
465 modified, renamed or removed.
466
467 \note There is a system dependent limit to the number of files and
468 directories that can be monitored simultaneously. If this limit
469 has been reached, \a path will not be added to the file system
470 watcher, and a warning message will be printed to \e{stderr}.
471
472 \sa addPaths(), removePath()
473*/
474void QFileSystemWatcher::addPath(const QString &path)
475{
476 if (path.isEmpty()) {
477 qWarning("QFileSystemWatcher::addPath: path is empty");
478 return;
479 }
480 addPaths(QStringList(path));
481}
482
483/*!
484 Adds each path in \a paths to the file system watcher. Paths are
485 not added if they not exist, or if they are already being
486 monitored by the file system watcher.
487
488 If a path specifies a directory, the directoryChanged() signal
489 will be emitted when the path is modified or removed from disk;
490 otherwise the fileChanged() signal is emitted when the path is
491 modified, renamed, or removed.
492
493 \note There is a system dependent limit to the number of files and
494 directories that can be monitored simultaneously. If this limit
495 has been reached, the excess \a paths will not be added to the
496 file system watcher, and a warning message will be printed to
497 \e{stderr} for each path that could not be added.
498
499 \sa addPath(), removePaths()
500*/
501void QFileSystemWatcher::addPaths(const QStringList &paths)
502{
503 Q_D(QFileSystemWatcher);
504 if (paths.isEmpty()) {
505 qWarning("QFileSystemWatcher::addPaths: list is empty");
506 return;
507 }
508
509 QStringList p = paths;
510 QFileSystemWatcherEngine *engine = 0;
511
512 if(!objectName().startsWith(QLatin1String("_qt_autotest_force_engine_"))) {
513 // Normal runtime case - search intelligently for best engine
514 if(d->native) {
515 engine = d->native;
516 } else {
517 d_func()->initPollerEngine();
518 engine = d->poller;
519 }
520
521 } else {
522 // Autotest override case - use the explicitly selected engine only
523 QString forceName = objectName().mid(26);
524 if(forceName == QLatin1String("poller")) {
525 qDebug() << "QFileSystemWatcher: skipping native engine, using only polling engine";
526 d_func()->initPollerEngine();
527 engine = d->poller;
528 } else if(forceName == QLatin1String("native")) {
529 qDebug() << "QFileSystemWatcher: skipping polling engine, using only native engine";
530 engine = d->native;
531 } else {
532 qDebug() << "QFileSystemWatcher: skipping polling and native engine, using only explicit" << forceName << "engine";
533 d_func()->initForcedEngine(forceName);
534 engine = d->forced;
535 }
536 }
537
538 if(engine)
539 p = engine->addPaths(p, &d->files, &d->directories);
540
541 if (!p.isEmpty())
542 qWarning("QFileSystemWatcher: failed to add paths: %s",
543 qPrintable(p.join(QLatin1String(", "))));
544}
545
546/*!
547 Removes the specified \a path from the file system watcher.
548
549 \sa removePaths(), addPath()
550*/
551void QFileSystemWatcher::removePath(const QString &path)
552{
553 if (path.isEmpty()) {
554 qWarning("QFileSystemWatcher::removePath: path is empty");
555 return;
556 }
557 removePaths(QStringList(path));
558}
559
560/*!
561 Removes the specified \a paths from the file system watcher.
562
563 \sa removePath(), addPaths()
564*/
565void QFileSystemWatcher::removePaths(const QStringList &paths)
566{
567 if (paths.isEmpty()) {
568 qWarning("QFileSystemWatcher::removePaths: list is empty");
569 return;
570 }
571 Q_D(QFileSystemWatcher);
572 QStringList p = paths;
573 if (d->native)
574 p = d->native->removePaths(p, &d->files, &d->directories);
575 if (d->poller)
576 p = d->poller->removePaths(p, &d->files, &d->directories);
577 if (d->forced)
578 p = d->forced->removePaths(p, &d->files, &d->directories);
579}
580
581/*!
582 \fn void QFileSystemWatcher::fileChanged(const QString &path)
583
584 This signal is emitted when the file at the specified \a path is
585 modified, renamed or removed from disk.
586
587 \sa directoryChanged()
588*/
589
590/*!
591 \fn void QFileSystemWatcher::directoryChanged(const QString &path)
592
593 This signal is emitted when the directory at a specified \a path,
594 is modified (e.g., when a file is added, modified or deleted) or
595 removed from disk. Note that if there are several changes during a
596 short period of time, some of the changes might not emit this
597 signal. However, the last change in the sequence of changes will
598 always generate this signal.
599
600 \sa fileChanged()
601*/
602
603/*!
604 \fn QStringList QFileSystemWatcher::directories() const
605
606 Returns a list of paths to directories that are being watched.
607
608 \sa files()
609*/
610
611/*!
612 \fn QStringList QFileSystemWatcher::files() const
613
614 Returns a list of paths to files that are being watched.
615
616 \sa directories()
617*/
618
619QStringList QFileSystemWatcher::directories() const
620{
621 Q_D(const QFileSystemWatcher);
622 return d->directories;
623}
624
625QStringList QFileSystemWatcher::files() const
626{
627 Q_D(const QFileSystemWatcher);
628 return d->files;
629}
630
631QT_END_NAMESPACE
632
633#include "moc_qfilesystemwatcher.cpp"
634
635#endif // QT_NO_FILESYSTEMWATCHER
636
Note: See TracBrowser for help on using the repository browser.