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

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

corelib/gui: Add support for watching drive list changes (represented by the empty or null string) to the polling file system watcher and use this feature in QFileDialog to get instant drive change notifications in "My Computer".

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