summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErik Verbruggen <erik.verbruggen@me.com>2013-11-29 12:55:59 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2014-02-14 10:51:50 +0100
commit4273c14e57d26cf2dde90ed9db1f463a25b327bd (patch)
tree9a23f10aa5b44b25418ddaa221b18558fa12a5db
parent97c187da3c1381bc55dd16976bf9fb3c773d0047 (diff)
Mac: FSEvents-based QFileSystemWatcherEngine.
Use FSEvents to monitor changes in the filesystem instead of the kqueue implementation. This removes the limit of wathed files: kqueue uses a file descriptor for each file monitored, for which the ulimit was set by default to 256. Now the OSX implementation on par with the other major desktop platforms. Change-Id: I2d46cca811978621989fd35201138df88a37c0fb Reviewed-by: Morten Johan Sørvig <morten.sorvig@digia.com>
-rw-r--r--src/corelib/io/io.pri4
-rw-r--r--src/corelib/io/qfilesystemwatcher.cpp8
-rw-r--r--src/corelib/io/qfilesystemwatcher_fsevents.mm507
-rw-r--r--src/corelib/io/qfilesystemwatcher_fsevents_p.h142
4 files changed, 659 insertions, 2 deletions
diff --git a/src/corelib/io/io.pri b/src/corelib/io/io.pri
index 3f2f025aeb..989e4644c7 100644
--- a/src/corelib/io/io.pri
+++ b/src/corelib/io/io.pri
@@ -135,6 +135,10 @@ win32 {
OBJECTIVE_SOURCES += io/qurl_mac.mm
}
mac {
+ osx {
+ OBJECTIVE_SOURCES += io/qfilesystemwatcher_fsevents.mm
+ HEADERS += io/qfilesystemwatcher_fsevents_p.h
+ }
macx {
SOURCES += io/qstandardpaths_mac.cpp
} else:ios {
diff --git a/src/corelib/io/qfilesystemwatcher.cpp b/src/corelib/io/qfilesystemwatcher.cpp
index d1deae2d7b..8cc6ad0552 100644
--- a/src/corelib/io/qfilesystemwatcher.cpp
+++ b/src/corelib/io/qfilesystemwatcher.cpp
@@ -60,8 +60,10 @@
# include "qfilesystemwatcher_win_p.h"
#elif defined(USE_INOTIFY)
# include "qfilesystemwatcher_inotify_p.h"
-#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_IOS) || (defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7)
# include "qfilesystemwatcher_kqueue_p.h"
+#elif defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_6
+# include "qfilesystemwatcher_fsevents_p.h"
#endif
QT_BEGIN_NAMESPACE
@@ -74,8 +76,10 @@ QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine(QObject
// there is a chance that inotify may fail on Linux pre-2.6.13 (August
// 2005), so we can't just new inotify directly.
return QInotifyFileSystemWatcherEngine::create(parent);
-#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_IOS) || (defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7)
return QKqueueFileSystemWatcherEngine::create(parent);
+#elif defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_6
+ return QFseventsFileSystemWatcherEngine::create(parent);
#else
Q_UNUSED(parent);
return 0;
diff --git a/src/corelib/io/qfilesystemwatcher_fsevents.mm b/src/corelib/io/qfilesystemwatcher_fsevents.mm
new file mode 100644
index 0000000000..cb6ddd913e
--- /dev/null
+++ b/src/corelib/io/qfilesystemwatcher_fsevents.mm
@@ -0,0 +1,507 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qplatformdefs.h>
+
+#include "qdiriterator.h"
+#include "qfilesystemwatcher.h"
+#include "qfilesystemwatcher_fsevents_p.h"
+#include "private/qcore_unix_p.h"
+#include "kernel/qcore_mac_p.h"
+
+#ifndef QT_NO_FILESYSTEMWATCHER
+
+#include <qdebug.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qvarlengtharray.h>
+
+//#define FSEVENT_DEBUG
+#ifdef FSEVENT_DEBUG
+# define DEBUG if (true) qDebug
+#else
+# define DEBUG if (false) qDebug
+#endif
+
+QT_BEGIN_NAMESPACE
+
+static void callBackFunction(ConstFSEventStreamRef streamRef,
+ void *clientCallBackInfo,
+ size_t numEvents,
+ void *eventPaths,
+ const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId eventIds[])
+{
+ char **paths = static_cast<char **>(eventPaths);
+ QFseventsFileSystemWatcherEngine *engine = static_cast<QFseventsFileSystemWatcherEngine *>(clientCallBackInfo);
+ engine->processEvent(streamRef, numEvents, paths, eventFlags, eventIds);
+}
+
+void QFseventsFileSystemWatcherEngine::checkDir(DirsByName::iterator &it)
+{
+ QT_STATBUF st;
+ const QString &name = it.key();
+ Info &info = it->dirInfo;
+ const int res = QT_STAT(QFile::encodeName(name), &st);
+ if (res == -1) {
+ derefPath(info.watchedPath);
+ emit emitDirectoryChanged(info.origPath, true);
+ it = watchedDirectories.erase(it);
+ } else if (st.st_ctimespec != info.ctime || st.st_mode != info.mode) {
+ info.ctime = st.st_ctimespec;
+ info.mode = st.st_mode;
+ emit emitDirectoryChanged(info.origPath, false);
+ ++it;
+ } else {
+ bool dirChanged = false;
+ InfoByName &entries = it->entries;
+ // check known entries:
+ for (InfoByName::iterator i = entries.begin(); i != entries.end(); ) {
+ if (QT_STAT(QFile::encodeName(i.key()), &st) == -1) {
+ // entry disappeared
+ dirChanged = true;
+ i = entries.erase(i);
+ } else {
+ if (i->ctime != st.st_ctimespec || i->mode != st.st_mode) {
+ // entry changed
+ dirChanged = true;
+ i->ctime = st.st_ctimespec;
+ i->mode = st.st_mode;
+ }
+ ++i;
+ }
+ }
+ // check for new entries:
+ QDirIterator dirIt(name);
+ while (dirIt.hasNext()) {
+ dirIt.next();
+ QString entryName = dirIt.filePath();
+ if (!entries.contains(entryName)) {
+ dirChanged = true;
+ QT_STATBUF st;
+ if (QT_STAT(QFile::encodeName(entryName), &st) == -1)
+ continue;
+ entries.insert(entryName, Info(QString(), st.st_ctimespec, st.st_mode, QString()));
+
+ }
+ }
+ if (dirChanged)
+ emit emitDirectoryChanged(info.origPath, false);
+ }
+}
+
+void QFseventsFileSystemWatcherEngine::rescanDirs(const QString &path)
+{
+ for (DirsByName::iterator it = watchedDirectories.begin(); it != watchedDirectories.end(); ) {
+ if (it.key().startsWith(path))
+ checkDir(it);
+ else
+ ++it;
+ }
+}
+
+void QFseventsFileSystemWatcherEngine::rescanFiles(InfoByName &filesInPath)
+{
+ for (InfoByName::iterator it = filesInPath.begin(); it != filesInPath.end(); ) {
+ QT_STATBUF st;
+ QString name = it.key();
+ const int res = QT_STAT(QFile::encodeName(name), &st);
+ if (res == -1) {
+ derefPath(it->watchedPath);
+ emit emitFileChanged(it.value().origPath, true);
+ it = filesInPath.erase(it);
+ continue;
+ } else if (st.st_ctimespec != it->ctime || st.st_mode != it->mode) {
+ it->ctime = st.st_ctimespec;
+ it->mode = st.st_mode;
+ emit emitFileChanged(it.value().origPath, false);
+ }
+
+ ++it;
+ }
+}
+
+void QFseventsFileSystemWatcherEngine::rescanFiles(const QString &path)
+{
+ for (FilesByPath::iterator i = watchedFiles.begin(); i != watchedFiles.end(); ) {
+ if (i.key().startsWith(path)) {
+ rescanFiles(i.value());
+ if (i.value().isEmpty()) {
+ i = watchedFiles.erase(i);
+ continue;
+ }
+ }
+
+ ++i;
+ }
+}
+
+void QFseventsFileSystemWatcherEngine::processEvent(ConstFSEventStreamRef streamRef,
+ size_t numEvents,
+ char **eventPaths,
+ const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId eventIds[])
+{
+#if defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_6
+ Q_UNUSED(streamRef);
+
+ QMutexLocker locker(&lock);
+
+ for (size_t i = 0; i < numEvents; ++i) {
+ FSEventStreamEventFlags eFlags = eventFlags[i];
+ DEBUG("Change %llu in %s, flags %x", eventIds[i], eventPaths[i], (unsigned int)eFlags);
+ QString path = QFile::decodeName(eventPaths[i]);
+ if (path.endsWith(QDir::separator()))
+ path = path.mid(0, path.size() - 1);
+
+ if (eFlags & kFSEventStreamEventFlagMustScanSubDirs) {
+ DEBUG("\tmust rescan directory because of coalesced events");
+ if (eFlags & kFSEventStreamEventFlagUserDropped)
+ DEBUG("\t\t... user dropped.");
+ if (eFlags & kFSEventStreamEventFlagKernelDropped)
+ DEBUG("\t\t... kernel dropped.");
+ rescanDirs(path);
+ rescanFiles(path);
+ continue;
+ }
+
+ if (eFlags & kFSEventStreamEventFlagEventIdsWrapped) {
+ DEBUG("\tthe event ids wrapped");
+ // TODO: verify if we need to do something
+ }
+
+ if (eFlags & kFSEventStreamEventFlagRootChanged) {
+ // re-check everything:
+ DirsByName::iterator dirIt = watchedDirectories.find(path);
+ if (dirIt != watchedDirectories.end())
+ checkDir(dirIt);
+ rescanFiles(path);
+ continue;
+ }
+
+ if ((eFlags & kFSEventStreamEventFlagItemIsDir) && (eFlags & kFSEventStreamEventFlagItemRemoved))
+ rescanDirs(path);
+
+ // check watched directories:
+ DirsByName::iterator dirIt = watchedDirectories.find(path);
+ if (dirIt != watchedDirectories.end())
+ checkDir(dirIt);
+
+ // check watched files:
+ FilesByPath::iterator pIt = watchedFiles.find(path);
+ if (pIt != watchedFiles.end())
+ rescanFiles(pIt.value());
+ }
+#else
+ // This is a work-around for moc: when we put the version check at the top of the header file,
+ // moc will still see the Q_OBJECT macro and generate a meta-object when compiling for 10.6,
+ // which obviously won't link.
+ //
+ // So the trick is to still compile this class on 10.6, but never instantiate it.
+
+ Q_UNUSED(streamRef);
+ Q_UNUSED(numEvents);
+ Q_UNUSED(eventPaths);
+ Q_UNUSED(eventFlags);
+ Q_UNUSED(eventIds);
+#endif
+}
+
+void QFseventsFileSystemWatcherEngine::doEmitFileChanged(const QString path, bool removed)
+{
+ emit fileChanged(path, removed);
+}
+
+void QFseventsFileSystemWatcherEngine::doEmitDirectoryChanged(const QString path, bool removed)
+{
+ emit directoryChanged(path, removed);
+}
+
+QFseventsFileSystemWatcherEngine *QFseventsFileSystemWatcherEngine::create(QObject *parent)
+{
+ return new QFseventsFileSystemWatcherEngine(parent);
+}
+
+QFseventsFileSystemWatcherEngine::QFseventsFileSystemWatcherEngine(QObject *parent)
+ : QFileSystemWatcherEngine(parent)
+ , stream(0)
+{
+
+ // We cannot use signal-to-signal queued connections, because the
+ // QSignalSpy cannot spot signals fired from other/alien threads.
+ connect(this, SIGNAL(emitDirectoryChanged(const QString, bool)),
+ this, SLOT(doEmitDirectoryChanged(const QString, bool)), Qt::QueuedConnection);
+ connect(this, SIGNAL(emitFileChanged(const QString, bool)),
+ this, SLOT(doEmitFileChanged(const QString, bool)), Qt::QueuedConnection);
+
+ queue = dispatch_queue_create("org.qt-project.QFseventsFileSystemWatcherEngine", NULL);
+}
+
+QFseventsFileSystemWatcherEngine::~QFseventsFileSystemWatcherEngine()
+{
+ if (stream)
+ FSEventStreamStop(stream);
+
+ // The assumption with the locking strategy is that this class cannot and will not be subclassed!
+ QMutexLocker locker(&lock);
+
+ stopStream();
+ dispatch_release(queue);
+}
+
+QStringList QFseventsFileSystemWatcherEngine::addPaths(const QStringList &paths,
+ QStringList *files,
+ QStringList *directories)
+{
+ if (stream)
+ FSEventStreamFlushSync(stream);
+
+ QMutexLocker locker(&lock);
+
+ bool newWatchPathsFound = false;
+
+ QStringList p = paths;
+ QMutableListIterator<QString> it(p);
+ while (it.hasNext()) {
+ QString origPath = it.next();
+ QString realPath = origPath;
+ if (realPath.endsWith(QDir::separator()))
+ realPath = realPath.mid(0, realPath.size() - 1);
+ QString watchedPath, parentPath;
+
+ realPath = QFileInfo(realPath).canonicalFilePath();
+ QFileInfo fi(realPath);
+ if (realPath.isEmpty())
+ continue;
+
+ QT_STATBUF st;
+ if (QT_STAT(QFile::encodeName(realPath), &st) == -1)
+ continue;
+
+ const bool isDir = S_ISDIR(st.st_mode);
+ if (isDir) {
+ if (watchedDirectories.contains(realPath))
+ continue;
+ directories->append(origPath);
+ watchedPath = realPath;
+ it.remove();
+ } else {
+ if (files->contains(origPath))
+ continue;
+ files->append(origPath);
+ it.remove();
+
+ watchedPath = fi.path();
+ parentPath = watchedPath;
+ }
+
+ for (PathRefCounts::const_iterator i = watchedPaths.begin(), ei = watchedPaths.end(); i != ei; ++i) {
+ if (watchedPath.startsWith(i.key())) {
+ watchedPath = i.key();
+ break;
+ }
+ }
+
+ PathRefCounts::iterator it = watchedPaths.find(watchedPath);
+ if (it == watchedPaths.end()) {
+ newWatchPathsFound = true;
+ watchedPaths.insert(watchedPath, 1);
+ } else {
+ ++it.value();
+ }
+
+ Info info(origPath, st.st_ctimespec, st.st_mode, watchedPath);
+ if (isDir) {
+ DirInfo dirInfo;
+ dirInfo.dirInfo = info;
+ dirInfo.entries = scanForDirEntries(realPath);
+ watchedDirectories.insert(realPath, dirInfo);
+ } else {
+ watchedFiles[parentPath].insert(realPath, info);
+ }
+ }
+
+ if (newWatchPathsFound) {
+ stopStream();
+ if (!startStream())
+ p = paths;
+ }
+
+ return p;
+}
+
+QStringList QFseventsFileSystemWatcherEngine::removePaths(const QStringList &paths,
+ QStringList *files,
+ QStringList *directories)
+{
+ QMutexLocker locker(&lock);
+
+ QStringList p = paths;
+ QMutableListIterator<QString> it(p);
+ while (it.hasNext()) {
+ QString origPath = it.next();
+ QString realPath = origPath;
+ if (realPath.endsWith(QDir::separator()))
+ realPath = realPath.mid(0, realPath.size() - 1);
+
+ QFileInfo fi(realPath);
+ realPath = fi.canonicalFilePath();
+
+ if (fi.isDir()) {
+ DirsByName::iterator dirIt = watchedDirectories.find(realPath);
+ if (dirIt != watchedDirectories.end()) {
+ derefPath(dirIt->dirInfo.watchedPath);
+ watchedDirectories.erase(dirIt);
+ directories->removeAll(origPath);
+ it.remove();
+ }
+ } else {
+ QFileInfo fi(realPath);
+ QString parentPath = fi.path();
+ FilesByPath::iterator pIt = watchedFiles.find(parentPath);
+ if (pIt != watchedFiles.end()) {
+ InfoByName &filesInDir = pIt.value();
+ InfoByName::iterator fIt = filesInDir.find(realPath);
+ if (fIt != filesInDir.end()) {
+ derefPath(fIt->watchedPath);
+ filesInDir.erase(fIt);
+ if (filesInDir.isEmpty())
+ watchedFiles.erase(pIt);
+ files->removeAll(origPath);
+ it.remove();
+ }
+ }
+ }
+ }
+
+ return p;
+}
+
+bool QFseventsFileSystemWatcherEngine::startStream()
+{
+ if (watchedPaths.isEmpty())
+ return false;
+
+ DEBUG() << "Starting stream with paths" << watchedPaths.keys();
+
+ NSMutableArray *pathsToWatch = [NSMutableArray arrayWithCapacity:watchedPaths.size()];
+ for (PathRefCounts::const_iterator i = watchedPaths.begin(), ei = watchedPaths.end(); i != ei; ++i)
+ [pathsToWatch addObject:reinterpret_cast<const NSString *>(QCFString::toCFStringRef(i.key()))];
+
+ struct FSEventStreamContext callBackInfo = {
+ 0,
+ this,
+ NULL,
+ NULL,
+ NULL
+ };
+ const CFAbsoluteTime latency = .5; // in seconds
+ FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagWatchRoot;
+
+ stream = FSEventStreamCreate(NULL,
+ &callBackFunction,
+ &callBackInfo,
+ reinterpret_cast<CFArrayRef>(pathsToWatch),
+ kFSEventStreamEventIdSinceNow,
+ latency,
+ flags);
+
+ if (!stream) {
+ DEBUG() << "Failed to create stream!";
+ return false;
+ }
+
+ FSEventStreamSetDispatchQueue(stream, queue);
+
+ if (FSEventStreamStart(stream)) {
+ DEBUG() << "Stream started successfully.";
+ return true;
+ } else {
+ DEBUG() << "Stream failed to start!";
+ return false;
+ }
+}
+
+void QFseventsFileSystemWatcherEngine::stopStream(bool isStopped)
+{
+ if (stream) {
+ if (!isStopped)
+ FSEventStreamStop(stream);
+ FSEventStreamInvalidate(stream);
+ FSEventStreamRelease(stream);
+ stream = 0;
+ DEBUG() << "Stream stopped.";
+ }
+}
+
+QFseventsFileSystemWatcherEngine::InfoByName QFseventsFileSystemWatcherEngine::scanForDirEntries(const QString &path)
+{
+ InfoByName entries;
+
+ QDirIterator it(path);
+ while (it.hasNext()) {
+ it.next();
+ QString entryName = it.filePath();
+ QT_STATBUF st;
+ if (QT_STAT(QFile::encodeName(entryName), &st) == -1)
+ continue;
+ entries.insert(entryName, Info(QString(), st.st_ctimespec, st.st_mode, QString()));
+ }
+
+ return entries;
+}
+
+void QFseventsFileSystemWatcherEngine::derefPath(const QString &watchedPath)
+{
+ PathRefCounts::iterator it = watchedPaths.find(watchedPath);
+ if (it == watchedPaths.end())
+ return;
+ if (--it.value() < 1) {
+ watchedPaths.erase(it);
+ stopStream();
+ startStream();
+ }
+}
+
+#endif //QT_NO_FILESYSTEMWATCHER
+
+QT_END_NAMESPACE
diff --git a/src/corelib/io/qfilesystemwatcher_fsevents_p.h b/src/corelib/io/qfilesystemwatcher_fsevents_p.h
new file mode 100644
index 0000000000..c899c556c8
--- /dev/null
+++ b/src/corelib/io/qfilesystemwatcher_fsevents_p.h
@@ -0,0 +1,142 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QFILESYSTEMWATCHER_FSEVENTS_P_H
+#define QFILESYSTEMWATCHER_FSEVENTS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the QLibrary class. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qfilesystemwatcher_p.h"
+
+#include <QtCore/qmutex.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qvector.h>
+#include <QtCore/qsocketnotifier.h>
+
+#include <dispatch/dispatch.h>
+#include <CoreServices/CoreServices.h>
+
+#ifndef QT_NO_FILESYSTEMWATCHER
+
+QT_BEGIN_NAMESPACE
+
+class QFseventsFileSystemWatcherEngine : public QFileSystemWatcherEngine
+{
+ Q_OBJECT
+public:
+ ~QFseventsFileSystemWatcherEngine();
+
+ static QFseventsFileSystemWatcherEngine *create(QObject *parent);
+
+ QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories);
+ QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories);
+
+ void processEvent(ConstFSEventStreamRef streamRef, size_t numEvents, char **eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);
+
+Q_SIGNALS:
+ void emitFileChanged(const QString path, bool removed);
+ void emitDirectoryChanged(const QString path, bool removed);
+
+private slots:
+ void doEmitFileChanged(const QString path, bool removed);
+ void doEmitDirectoryChanged(const QString path, bool removed);
+
+private:
+ struct Info {
+ QString origPath;
+ timespec ctime;
+ mode_t mode;
+ QString watchedPath;
+
+ Info(): mode(0)
+ {
+ ctime.tv_sec = 0;
+ ctime.tv_nsec = 0;
+ }
+
+ Info(const QString &origPath, const timespec &ctime, mode_t mode, const QString &watchedPath)
+ : origPath(origPath)
+ , ctime(ctime)
+ , mode(mode)
+ , watchedPath(watchedPath)
+ {}
+ };
+ typedef QHash<QString, Info> InfoByName;
+ typedef QHash<QString, InfoByName> FilesByPath;
+ struct DirInfo {
+ Info dirInfo;
+ InfoByName entries;
+ };
+ typedef QHash<QString, DirInfo> DirsByName;
+ typedef QHash<QString, qint64> PathRefCounts;
+
+ QFseventsFileSystemWatcherEngine(QObject *parent);
+ bool startStream();
+ void stopStream(bool isStopped = false);
+ InfoByName scanForDirEntries(const QString &path);
+ void derefPath(const QString &watchedPath);
+ void checkDir(DirsByName::iterator &it);
+ void rescanDirs(const QString &path);
+ void rescanFiles(InfoByName &filesInPath);
+ void rescanFiles(const QString &path);
+
+ QMutex lock;
+ dispatch_queue_t queue;
+ FSEventStreamRef stream;
+ FilesByPath watchedFiles;
+ DirsByName watchedDirectories;
+ PathRefCounts watchedPaths;
+};
+
+QT_END_NAMESPACE
+
+#endif //QT_NO_FILESYSTEMWATCHER
+#endif // QFILESYSTEMWATCHER_FSEVENTS_P_H