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