# HG changeset patch # User Chris Cannam # Date 1331573141 0 # Node ID 09b9849b98001e068e6a8f5b3618d1fe5477e867 # Parent e40e3ebe9f2e95fccb745aba74087c73619cfb07 Make a start with explicit FSEvents support on OS/X (see long comment added in this commit). Note that this branch doesn't currently build. diff -r e40e3ebe9f2e -r 09b9849b9800 easyhg.pro --- a/easyhg.pro Mon Mar 12 14:58:40 2012 +0000 +++ b/easyhg.pro Mon Mar 12 17:25:41 2012 +0000 @@ -4,13 +4,13 @@ TEMPLATE = app TARGET = EasyMercurial -# We use the 10.4 SDK and Carbon for all 32-bit OS/X, +# We use the 10.5 SDK and Carbon for all 32-bit OS/X, # and 10.6 with Cocoa for all 64-bit macx-g++40 { # Note, to use the 10.4 SDK on 10.6+ you need qmake -spec macx-g++40 - QMAKE_MAC_SDK = /Developer/SDKs/MacOSX10.4u.sdk - QMAKE_CFLAGS += -mmacosx-version-min=10.4 - QMAKE_CXXFLAGS += -mmacosx-version-min=10.4 + QMAKE_MAC_SDK = /Developer/SDKs/MacOSX10.5.sdk + QMAKE_CFLAGS += -mmacosx-version-min=10.5 + QMAKE_CXXFLAGS += -mmacosx-version-min=10.5 CONFIG += x86 ppc } macx-g++ { diff -r e40e3ebe9f2e -r 09b9849b9800 src/fswatcher.cpp --- a/src/fswatcher.cpp Mon Mar 12 14:58:40 2012 +0000 +++ b/src/fswatcher.cpp Mon Mar 12 17:25:41 2012 +0000 @@ -15,12 +15,17 @@ COPYING included with this distribution for more information. */ +#include +#include + +#ifdef Q_OS_MAC +// Must include this before debug.h +#include +#endif + #include "fswatcher.h" #include "debug.h" -#include -#include - #include //#define DEBUG_FSWATCHER 1 @@ -45,16 +50,57 @@ * directory to learn whether the set of files in it _excluding_ files * matching our ignore patterns differs from the previous scan, and * ignore the change if it doesn't. + * + */ + +/* + * 20120312 -- Another complication. The documentation for + * QFileSystemWatcher says: + * + * On Mac OS X 10.4 [...] an open file descriptor is required for + * each monitored file. [...] This means that addPath() and + * addPaths() will fail if your process tries to add more than 256 + * files or directories to the file system monitor [...] Mac OS X + * 10.5 and up use a different backend and do not suffer from this + * issue. + * + * Unfortunately, the last sentence above is not true: + * http://qt.gitorious.org/qt/qt/commit/6d1baf9979346d6f15da81a535becb4046278962 + * ("Removing the usage of FSEvents-based backend for now as it has a + * few bugs..."). It can't be restored without hacking the Qt source, + * which we don't want to do in this context. The commit log doesn't + * make clear how serious the bugs were -- an example is given but it + * doesn't indicate whether it's an edge case or a common case and + * whether the result was a crash or failure to notify. + * + * This means the Qt class uses kqueue instead on OS/X, but that + * doesn't really work for us -- it can only monitor 256 files (or + * whatever the fd ulimit is set to, but that's the default) and it + * doesn't notify if a file within a directory is modified unless the + * metadata changes. The main limitation of FSEvents is that it only + * notifies with directory granularity, but that's OK for us so long + * as notifications are actually provoked by file changes as well. + * + * One other problem with FSEvents is that the API only exists on OS/X + * 10.5 or newer -- on older versions we would have no option but to + * use kqueue via QFileSystemWatcher. But we can't ship a binary + * linked with the FSEvents API to run on 10.4 without some fiddling, + * and I'm not really keen to do that either. That may be our cue to + * drop 10.4 support for EasyMercurial. */ FsWatcher::FsWatcher() : m_lastToken(0), m_lastCounter(0) { +#ifdef Q_OS_MAC + m_stream = 0; // create when we have a path +#else connect(&m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(fsDirectoryChanged(QString))); connect(&m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(fsFileChanged(QString))); +#endif } FsWatcher::~FsWatcher() @@ -66,6 +112,24 @@ { QMutexLocker locker(&m_mutex); if (m_workDirPath == path) return; + clearWatchedPaths(); + m_workDirPath = path; + addWorkDirectory(path); + debugPrint(); +} + +void +FsWatcher::clearWatchedPaths() +{ +#ifdef Q_OS_MAC + FSEventStreamRef stream = (FSEventStreamRef)m_stream; + if (stream) { + FSEventStreamStop(stream); + FSEventStreamInvalidate(stream); + FSEventStreamRelease(stream); + } + m_stream = 0; +#else // annoyingly, removePaths prints a warning if given an empty list if (!m_watcher.directories().empty()) { m_watcher.removePaths(m_watcher.directories()); @@ -73,40 +137,44 @@ if (!m_watcher.files().empty()) { m_watcher.removePaths(m_watcher.files()); } - m_workDirPath = path; - addWorkDirectory(path); - debugPrint(); +#endif } -void -FsWatcher::setTrackedFilePaths(QStringList paths) +#ifdef Q_OS_MAC +static void +fsEventsCallback(FSEventStreamRef streamRef, + void *clientCallBackInfo, + int numEvents, + const char *const eventPaths[], + const FSEventStreamEventFlags *eventFlags, + const uint64_t *eventIDs) { - QMutexLocker locker(&m_mutex); - - QSet alreadyWatched = - QSet::fromList(m_watcher.files()); - - foreach (QString path, paths) { - path = m_workDirPath + QDir::separator() + path; - if (!alreadyWatched.contains(path)) { - m_watcher.addPath(path); - } else { - alreadyWatched.remove(path); - } - } - - // Remove the remaining paths, those that were being watched - // before but that are not in the list we were given - foreach (QString path, alreadyWatched) { - m_watcher.removePath(path); - } - - debugPrint(); } +#endif void FsWatcher::addWorkDirectory(QString path) { +#ifdef Q_OS_MAC + FSEventStreamRef stream = + FSEventStreamCreate(kCFAllocatorDefault, + (FSEventStreamCallback)&fsEventsCallback, + this, + cfPaths, + kFSEventStreamEventIdSinceNow, + 1.0, // latency, seconds + kFSEventStreamCreateFlagNone); + + m_stream = stream; + + FSEventStreamScheduleWithRunLoop(stream, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode); + + if (!FSEventStreamStart(stream)) { + std::cerr << "ERROR: FsWatcher::addWorkDirectory: Failed to start FSEvent stream" << std::endl; + } +#else // QFileSystemWatcher will refuse to add a file or directory to // its watch list that it is already watching -- fine -- but it // prints a warning when this happens, which we wouldn't want. So @@ -137,6 +205,40 @@ } } } +#endif +} + +void +FsWatcher::setTrackedFilePaths(QStringList paths) +{ +#ifdef Q_OS_MAC + // We don't need to do anything here, so long as addWorkDirectory + // has been called -- FSEvents monitors files within directories + // as well as the directories themselves (even though it only + // notifies with directory granularity) +#else + QMutexLocker locker(&m_mutex); + + QSet alreadyWatched = + QSet::fromList(m_watcher.files()); + + foreach (QString path, paths) { + path = m_workDirPath + QDir::separator() + path; + if (!alreadyWatched.contains(path)) { + m_watcher.addPath(path); + } else { + alreadyWatched.remove(path); + } + } + + // Remove the remaining paths, those that were being watched + // before but that are not in the list we were given + foreach (QString path, alreadyWatched) { + m_watcher.removePath(path); + } + + debugPrint(); +#endif } void diff -r e40e3ebe9f2e -r 09b9849b9800 src/fswatcher.h --- a/src/fswatcher.h Mon Mar 12 14:58:40 2012 +0000 +++ b/src/fswatcher.h Mon Mar 12 17:25:41 2012 +0000 @@ -25,7 +25,12 @@ #include #include #include + +#ifndef Q_OS_MAC +// We don't use QFileSystemWatcher on OS/X. +// See comments at top of fswatcher.cpp for an explanation. #include +#endif class FsWatcher : public QObject { @@ -93,6 +98,9 @@ private: // call with lock already held + void clearWatchedPaths(); + + // call with lock already held void addWorkDirectory(QString path); // call with lock already held @@ -142,7 +150,12 @@ QString m_workDirPath; int m_lastToken; size_t m_lastCounter; + +#ifdef Q_OS_MAC + void *m_stream; +#else QFileSystemWatcher m_watcher; +#endif }; #endif