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

Last change on this file since 595 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

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