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

Last change on this file was 869, checked in by Dmitry A. Kuminov, 14 years ago

core: Fixed QFileSystemWatcher regression after merging Qt 4.7 that could result in hangs when working with file dialogs etc.

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