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