summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qfilesystemwatcher_fsevents.cpp
diff options
context:
space:
mode:
authorQt by Nokia <qt-info@nokia.com>2011-04-27 12:05:43 +0200
committeraxis <qt-info@nokia.com>2011-04-27 12:05:43 +0200
commit38be0d13830efd2d98281c645c3a60afe05ffece (patch)
tree6ea73f3ec77f7d153333779883e8120f82820abe /src/corelib/io/qfilesystemwatcher_fsevents.cpp
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'src/corelib/io/qfilesystemwatcher_fsevents.cpp')
-rw-r--r--src/corelib/io/qfilesystemwatcher_fsevents.cpp492
1 files changed, 492 insertions, 0 deletions
diff --git a/src/corelib/io/qfilesystemwatcher_fsevents.cpp b/src/corelib/io/qfilesystemwatcher_fsevents.cpp
new file mode 100644
index 0000000000..19b720c8b3
--- /dev/null
+++ b/src/corelib/io/qfilesystemwatcher_fsevents.cpp
@@ -0,0 +1,492 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <qplatformdefs.h>
+
+#include "qfilesystemwatcher.h"
+#include "qfilesystemwatcher_fsevents_p.h"
+
+#ifndef QT_NO_FILESYSTEMWATCHER
+
+#include <qdebug.h>
+#include <qfile.h>
+#include <qdatetime.h>
+#include <qfileinfo.h>
+#include <qvarlengtharray.h>
+
+#include <mach/mach.h>
+#include <sys/types.h>
+#include <CoreFoundation/CFRunLoop.h>
+#include <CoreFoundation/CFUUID.h>
+#include <CoreServices/CoreServices.h>
+#include <AvailabilityMacros.h>
+#include <private/qcore_mac_p.h>
+
+QT_BEGIN_NAMESPACE
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+// Static operator overloading so for the sake of some convieniece.
+// They only live in this compilation unit to avoid polluting Qt in general.
+static bool operator==(const struct ::timespec &left, const struct ::timespec &right)
+{
+ return left.tv_sec == right.tv_sec
+ && left.tv_nsec == right.tv_nsec;
+}
+
+static bool operator==(const struct ::stat64 &left, const struct ::stat64 &right)
+{
+ return left.st_dev == right.st_dev
+ && left.st_mode == right.st_mode
+ && left.st_size == right.st_size
+ && left.st_ino == right.st_ino
+ && left.st_uid == right.st_uid
+ && left.st_gid == right.st_gid
+ && left.st_mtimespec == right.st_mtimespec
+ && left.st_ctimespec == right.st_ctimespec
+ && left.st_flags == right.st_flags;
+}
+
+static bool operator!=(const struct ::stat64 &left, const struct ::stat64 &right)
+{
+ return !(operator==(left, right));
+}
+
+
+static void addPathToHash(PathHash &pathHash, const QString &key, const QFileInfo &fileInfo,
+ const QString &path)
+{
+ PathInfoList &list = pathHash[key];
+ list.push_back(PathInfo(path,
+ fileInfo.canonicalFilePath().normalized(QString::NormalizationForm_D).toUtf8()));
+ pathHash.insert(key, list);
+}
+
+static void removePathFromHash(PathHash &pathHash, const QString &key, const QString &path)
+{
+ PathInfoList &list = pathHash[key];
+ // We make the assumption that the list contains unique paths
+ PathInfoList::iterator End = list.end();
+ PathInfoList::iterator it = list.begin();
+ while (it != End) {
+ if (it->originalPath == path) {
+ list.erase(it);
+ break;
+ }
+ ++it;
+ }
+ if (list.isEmpty())
+ pathHash.remove(key);
+}
+
+static void stopFSStream(FSEventStreamRef stream)
+{
+ if (stream) {
+ FSEventStreamStop(stream);
+ FSEventStreamInvalidate(stream);
+ }
+}
+
+static QString createFSStreamPath(const QString &absolutePath)
+{
+ // The path returned has a trailing slash, so ensure that here.
+ QString string = absolutePath;
+ string.reserve(string.size() + 1);
+ string.append(QLatin1Char('/'));
+ return string;
+}
+
+static void cleanupFSStream(FSEventStreamRef stream)
+{
+ if (stream)
+ FSEventStreamRelease(stream);
+}
+
+const FSEventStreamCreateFlags QtFSEventFlags = (kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagNoDefer /* | kFSEventStreamCreateFlagWatchRoot*/);
+
+const CFTimeInterval Latency = 0.033; // This will do updates 30 times a second which is probably more than you need.
+#endif
+
+QFSEventsFileSystemWatcherEngine::QFSEventsFileSystemWatcherEngine()
+ : fsStream(0), pathsToWatch(0), threadsRunLoop(0)
+{
+}
+
+QFSEventsFileSystemWatcherEngine::~QFSEventsFileSystemWatcherEngine()
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ // I assume that at this point, QFileSystemWatcher has already called stop
+ // on me, so I don't need to invalidate or stop my stream, simply
+ // release it.
+ cleanupFSStream(fsStream);
+ if (pathsToWatch)
+ CFRelease(pathsToWatch);
+#endif
+}
+
+QFSEventsFileSystemWatcherEngine *QFSEventsFileSystemWatcherEngine::create()
+{
+ return new QFSEventsFileSystemWatcherEngine();
+}
+
+QStringList QFSEventsFileSystemWatcherEngine::addPaths(const QStringList &paths,
+ QStringList *files,
+ QStringList *directories)
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ stop();
+ wait();
+ QMutexLocker locker(&mutex);
+ QStringList failedToAdd;
+ // if we have a running FSStreamEvent, we have to kill it, we'll re-add the stream soon.
+ FSEventStreamEventId idToCheck;
+ if (fsStream) {
+ idToCheck = FSEventStreamGetLatestEventId(fsStream);
+ cleanupFSStream(fsStream);
+ } else {
+ idToCheck = kFSEventStreamEventIdSinceNow;
+ }
+
+ // Brain-dead approach, but works. FSEvents actually can already read sub-trees, but since it's
+ // work to figure out if we are doing a double register, we just register it twice as FSEvents
+ // seems smart enough to only deliver one event. We also duplicate directory entries in here
+ // (e.g., if you watch five files in the same directory, you get that directory included in the
+ // array 5 times). This stupidity also makes remove work correctly though. I'll freely admit
+ // that we could make this a bit smarter. If you do, check the auto-tests, they should catch at
+ // least a couple of the issues.
+ QCFType<CFMutableArrayRef> tmpArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ for (int i = 0; i < paths.size(); ++i) {
+ const QString &path = paths.at(i);
+
+ QFileInfo fileInfo(path);
+ if (!fileInfo.exists()) {
+ failedToAdd.append(path);
+ continue;
+ }
+
+ if (fileInfo.isDir()) {
+ if (directories->contains(path)) {
+ failedToAdd.append(path);
+ continue;
+ } else {
+ directories->append(path);
+ // Full file path for dirs.
+ QCFString cfpath(createFSStreamPath(fileInfo.canonicalFilePath()));
+ addPathToHash(dirPathInfoHash, cfpath, fileInfo, path);
+ CFArrayAppendValue(tmpArray, cfpath);
+ }
+ } else {
+ if (files->contains(path)) {
+ failedToAdd.append(path);
+ continue;
+ } else {
+ // Just the absolute path (minus it's filename) for files.
+ QCFString cfpath(createFSStreamPath(fileInfo.canonicalPath()));
+ files->append(path);
+ addPathToHash(filePathInfoHash, cfpath, fileInfo, path);
+ CFArrayAppendValue(tmpArray, cfpath);
+ }
+ }
+ }
+
+ if (!pathsToWatch && failedToAdd.size() == paths.size()) {
+ return failedToAdd;
+ }
+
+ if (CFArrayGetCount(tmpArray) > 0) {
+ if (pathsToWatch) {
+ CFArrayAppendArray(tmpArray, pathsToWatch, CFRangeMake(0, CFArrayGetCount(pathsToWatch)));
+ CFRelease(pathsToWatch);
+ }
+ pathsToWatch = CFArrayCreateCopy(kCFAllocatorDefault, tmpArray);
+ }
+
+ FSEventStreamContext context = { 0, this, 0, 0, 0 };
+ fsStream = FSEventStreamCreate(kCFAllocatorDefault,
+ QFSEventsFileSystemWatcherEngine::fseventsCallback,
+ &context, pathsToWatch,
+ idToCheck, Latency, QtFSEventFlags);
+ warmUpFSEvents();
+
+ return failedToAdd;
+#else
+ Q_UNUSED(paths);
+ Q_UNUSED(files);
+ Q_UNUSED(directories);
+ return QStringList();
+#endif
+}
+
+void QFSEventsFileSystemWatcherEngine::warmUpFSEvents()
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ // This function assumes that the mutex has already been grabbed before calling it.
+ // It exits with the mutex still locked (Q_ASSERT(mutex.isLocked()) ;-).
+ start();
+ waitCondition.wait(&mutex);
+#endif
+}
+
+QStringList QFSEventsFileSystemWatcherEngine::removePaths(const QStringList &paths,
+ QStringList *files,
+ QStringList *directories)
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ stop();
+ wait();
+ QMutexLocker locker(&mutex);
+ // short circuit for smarties that call remove before add and we have nothing.
+ if (pathsToWatch == 0)
+ return paths;
+ QStringList failedToRemove;
+ // if we have a running FSStreamEvent, we have to stop it, we'll re-add the stream soon.
+ FSEventStreamEventId idToCheck;
+ if (fsStream) {
+ idToCheck = FSEventStreamGetLatestEventId(fsStream);
+ cleanupFSStream(fsStream);
+ fsStream = 0;
+ } else {
+ idToCheck = kFSEventStreamEventIdSinceNow;
+ }
+
+ CFIndex itemCount = CFArrayGetCount(pathsToWatch);
+ QCFType<CFMutableArrayRef> tmpArray = CFArrayCreateMutableCopy(kCFAllocatorDefault, itemCount,
+ pathsToWatch);
+ CFRelease(pathsToWatch);
+ pathsToWatch = 0;
+ for (int i = 0; i < paths.size(); ++i) {
+ // Get the itemCount at the beginning to avoid any overruns during the iteration.
+ itemCount = CFArrayGetCount(tmpArray);
+ const QString &path = paths.at(i);
+ QFileInfo fi(path);
+ QCFString cfpath(createFSStreamPath(fi.canonicalPath()));
+
+ CFIndex index = CFArrayGetFirstIndexOfValue(tmpArray, CFRangeMake(0, itemCount), cfpath);
+ if (index != -1) {
+ CFArrayRemoveValueAtIndex(tmpArray, index);
+ files->removeAll(path);
+ removePathFromHash(filePathInfoHash, cfpath, path);
+ } else {
+ // Could be a directory we are watching instead.
+ QCFString cfdirpath(createFSStreamPath(fi.canonicalFilePath()));
+ index = CFArrayGetFirstIndexOfValue(tmpArray, CFRangeMake(0, itemCount), cfdirpath);
+ if (index != -1) {
+ CFArrayRemoveValueAtIndex(tmpArray, index);
+ directories->removeAll(path);
+ removePathFromHash(dirPathInfoHash, cfpath, path);
+ } else {
+ failedToRemove.append(path);
+ }
+ }
+ }
+ itemCount = CFArrayGetCount(tmpArray);
+ if (itemCount != 0) {
+ pathsToWatch = CFArrayCreateCopy(kCFAllocatorDefault, tmpArray);
+
+ FSEventStreamContext context = { 0, this, 0, 0, 0 };
+ fsStream = FSEventStreamCreate(kCFAllocatorDefault,
+ QFSEventsFileSystemWatcherEngine::fseventsCallback,
+ &context, pathsToWatch, idToCheck, Latency, QtFSEventFlags);
+ warmUpFSEvents();
+ }
+ return failedToRemove;
+#else
+ Q_UNUSED(paths);
+ Q_UNUSED(files);
+ Q_UNUSED(directories);
+ return QStringList();
+#endif
+}
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+void QFSEventsFileSystemWatcherEngine::updateList(PathInfoList &list, bool directory, bool emitSignals)
+{
+ PathInfoList::iterator End = list.end();
+ PathInfoList::iterator it = list.begin();
+ while (it != End) {
+ struct ::stat64 newInfo;
+ if (::stat64(it->absolutePath, &newInfo) == 0) {
+ if (emitSignals) {
+ if (newInfo != it->savedInfo) {
+ it->savedInfo = newInfo;
+ if (directory)
+ emit directoryChanged(it->originalPath, false);
+ else
+ emit fileChanged(it->originalPath, false);
+ }
+ } else {
+ it->savedInfo = newInfo;
+ }
+ } else {
+ if (errno == ENOENT) {
+ if (emitSignals) {
+ if (directory)
+ emit directoryChanged(it->originalPath, true);
+ else
+ emit fileChanged(it->originalPath, true);
+ }
+ it = list.erase(it);
+ continue;
+ } else {
+ qWarning("%s:%d:QFSEventsFileSystemWatcherEngine: stat error on %s:%s",
+ __FILE__, __LINE__, qPrintable(it->originalPath), strerror(errno));
+
+ }
+ }
+ ++it;
+ }
+}
+
+void QFSEventsFileSystemWatcherEngine::updateHash(PathHash &pathHash)
+{
+ PathHash::iterator HashEnd = pathHash.end();
+ PathHash::iterator it = pathHash.begin();
+ const bool IsDirectory = (&pathHash == &dirPathInfoHash);
+ while (it != HashEnd) {
+ updateList(it.value(), IsDirectory, false);
+ if (it.value().isEmpty())
+ it = pathHash.erase(it);
+ else
+ ++it;
+ }
+}
+#endif
+
+void QFSEventsFileSystemWatcherEngine::fseventsCallback(ConstFSEventStreamRef ,
+ void *clientCallBackInfo, size_t numEvents,
+ void *eventPaths,
+ const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId [])
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ QFSEventsFileSystemWatcherEngine *watcher = static_cast<QFSEventsFileSystemWatcherEngine *>(clientCallBackInfo);
+ QMutexLocker locker(&watcher->mutex);
+ CFArrayRef paths = static_cast<CFArrayRef>(eventPaths);
+ for (size_t i = 0; i < numEvents; ++i) {
+ const QString path = QCFString::toQString(
+ static_cast<CFStringRef>(CFArrayGetValueAtIndex(paths, i)));
+ const FSEventStreamEventFlags pathFlags = eventFlags[i];
+ // There are several flags that may be passed, but we really don't care about them ATM.
+ // Here they are and why we don't care.
+ // kFSEventStreamEventFlagHistoryDone--(very unlikely to be gotten, but even then, not much changes).
+ // kFSEventStreamEventFlagMustScanSubDirs--Likely means the data is very much out of date, we
+ // aren't coalescing our directories, so again not so much of an issue
+ // kFSEventStreamEventFlagRootChanged | kFSEventStreamEventFlagMount | kFSEventStreamEventFlagUnmount--
+ // These three flags indicate something has changed, but the stat will likely show this, so
+ // there's not really much to worry about.
+ // (btw, FSEvents is not the correct way of checking for mounts/unmounts,
+ // there are real CarbonCore events for that.)
+ Q_UNUSED(pathFlags);
+ if (watcher->filePathInfoHash.contains(path))
+ watcher->updateList(watcher->filePathInfoHash[path], false, true);
+
+ if (watcher->dirPathInfoHash.contains(path))
+ watcher->updateList(watcher->dirPathInfoHash[path], true, true);
+ }
+#else
+ Q_UNUSED(clientCallBackInfo);
+ Q_UNUSED(numEvents);
+ Q_UNUSED(eventPaths);
+ Q_UNUSED(eventFlags);
+#endif
+}
+
+void QFSEventsFileSystemWatcherEngine::stop()
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ QMutexLocker locker(&mutex);
+ stopFSStream(fsStream);
+ if (threadsRunLoop) {
+ CFRunLoopStop(threadsRunLoop);
+ waitForStop.wait(&mutex);
+ }
+#endif
+}
+
+void QFSEventsFileSystemWatcherEngine::updateFiles()
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ QMutexLocker locker(&mutex);
+ updateHash(filePathInfoHash);
+ updateHash(dirPathInfoHash);
+ if (filePathInfoHash.isEmpty() && dirPathInfoHash.isEmpty()) {
+ // Everything disappeared before we got to start, don't bother.
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ // Code duplicated from stop(), with the exception that we
+ // don't wait on waitForStop here. Doing this will lead to
+ // a deadlock since this function is called from the worker
+ // thread. (waitForStop.wakeAll() is only called from the
+ // end of run()).
+ stopFSStream(fsStream);
+ if (threadsRunLoop)
+ CFRunLoopStop(threadsRunLoop);
+#endif
+ cleanupFSStream(fsStream);
+ }
+ waitCondition.wakeAll();
+#endif
+}
+
+void QFSEventsFileSystemWatcherEngine::run()
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ threadsRunLoop = CFRunLoopGetCurrent();
+ FSEventStreamScheduleWithRunLoop(fsStream, threadsRunLoop, kCFRunLoopDefaultMode);
+ bool startedOK = FSEventStreamStart(fsStream);
+ // It's recommended by Apple that you only update the files after you've started
+ // the stream, because otherwise you might miss an update in between starting it.
+ updateFiles();
+#ifdef QT_NO_DEBUG
+ Q_UNUSED(startedOK);
+#else
+ Q_ASSERT(startedOK);
+#endif
+ // If for some reason we called stop up above (and invalidated our stream), this call will return
+ // immediately.
+ CFRunLoopRun();
+ threadsRunLoop = 0;
+ QMutexLocker locker(&mutex);
+ waitForStop.wakeAll();
+#endif
+}
+
+QT_END_NAMESPACE
+#endif //QT_NO_FILESYSTEMWATCHER