comparison src/fswatcher.cpp @ 584:09b9849b9800 fswatcher

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.
author Chris Cannam
date Mon, 12 Mar 2012 17:25:41 +0000
parents 0a094020c2d4
children fa242885a233
comparison
equal deleted inserted replaced
583:e40e3ebe9f2e 584:09b9849b9800
13 published by the Free Software Foundation; either version 2 of the 13 published by the Free Software Foundation; either version 2 of the
14 License, or (at your option) any later version. See the file 14 License, or (at your option) any later version. See the file
15 COPYING included with this distribution for more information. 15 COPYING included with this distribution for more information.
16 */ 16 */
17 17
18 #include <QMutexLocker>
19 #include <QDir>
20
21 #ifdef Q_OS_MAC
22 // Must include this before debug.h
23 #include <CoreServices/CoreServices.h>
24 #endif
25
18 #include "fswatcher.h" 26 #include "fswatcher.h"
19 #include "debug.h" 27 #include "debug.h"
20
21 #include <QMutexLocker>
22 #include <QDir>
23 28
24 #include <deque> 29 #include <deque>
25 30
26 //#define DEBUG_FSWATCHER 1 31 //#define DEBUG_FSWATCHER 1
27 32
43 * 48 *
44 * This means that, when a directory changes, we need to rescan the 49 * This means that, when a directory changes, we need to rescan the
45 * directory to learn whether the set of files in it _excluding_ files 50 * directory to learn whether the set of files in it _excluding_ files
46 * matching our ignore patterns differs from the previous scan, and 51 * matching our ignore patterns differs from the previous scan, and
47 * ignore the change if it doesn't. 52 * ignore the change if it doesn't.
53 *
54 */
55
56 /*
57 * 20120312 -- Another complication. The documentation for
58 * QFileSystemWatcher says:
59 *
60 * On Mac OS X 10.4 [...] an open file descriptor is required for
61 * each monitored file. [...] This means that addPath() and
62 * addPaths() will fail if your process tries to add more than 256
63 * files or directories to the file system monitor [...] Mac OS X
64 * 10.5 and up use a different backend and do not suffer from this
65 * issue.
66 *
67 * Unfortunately, the last sentence above is not true:
68 * http://qt.gitorious.org/qt/qt/commit/6d1baf9979346d6f15da81a535becb4046278962
69 * ("Removing the usage of FSEvents-based backend for now as it has a
70 * few bugs..."). It can't be restored without hacking the Qt source,
71 * which we don't want to do in this context. The commit log doesn't
72 * make clear how serious the bugs were -- an example is given but it
73 * doesn't indicate whether it's an edge case or a common case and
74 * whether the result was a crash or failure to notify.
75 *
76 * This means the Qt class uses kqueue instead on OS/X, but that
77 * doesn't really work for us -- it can only monitor 256 files (or
78 * whatever the fd ulimit is set to, but that's the default) and it
79 * doesn't notify if a file within a directory is modified unless the
80 * metadata changes. The main limitation of FSEvents is that it only
81 * notifies with directory granularity, but that's OK for us so long
82 * as notifications are actually provoked by file changes as well.
83 *
84 * One other problem with FSEvents is that the API only exists on OS/X
85 * 10.5 or newer -- on older versions we would have no option but to
86 * use kqueue via QFileSystemWatcher. But we can't ship a binary
87 * linked with the FSEvents API to run on 10.4 without some fiddling,
88 * and I'm not really keen to do that either. That may be our cue to
89 * drop 10.4 support for EasyMercurial.
48 */ 90 */
49 91
50 FsWatcher::FsWatcher() : 92 FsWatcher::FsWatcher() :
51 m_lastToken(0), 93 m_lastToken(0),
52 m_lastCounter(0) 94 m_lastCounter(0)
53 { 95 {
96 #ifdef Q_OS_MAC
97 m_stream = 0; // create when we have a path
98 #else
54 connect(&m_watcher, SIGNAL(directoryChanged(QString)), 99 connect(&m_watcher, SIGNAL(directoryChanged(QString)),
55 this, SLOT(fsDirectoryChanged(QString))); 100 this, SLOT(fsDirectoryChanged(QString)));
56 connect(&m_watcher, SIGNAL(fileChanged(QString)), 101 connect(&m_watcher, SIGNAL(fileChanged(QString)),
57 this, SLOT(fsFileChanged(QString))); 102 this, SLOT(fsFileChanged(QString)));
103 #endif
58 } 104 }
59 105
60 FsWatcher::~FsWatcher() 106 FsWatcher::~FsWatcher()
61 { 107 {
62 } 108 }
64 void 110 void
65 FsWatcher::setWorkDirPath(QString path) 111 FsWatcher::setWorkDirPath(QString path)
66 { 112 {
67 QMutexLocker locker(&m_mutex); 113 QMutexLocker locker(&m_mutex);
68 if (m_workDirPath == path) return; 114 if (m_workDirPath == path) return;
115 clearWatchedPaths();
116 m_workDirPath = path;
117 addWorkDirectory(path);
118 debugPrint();
119 }
120
121 void
122 FsWatcher::clearWatchedPaths()
123 {
124 #ifdef Q_OS_MAC
125 FSEventStreamRef stream = (FSEventStreamRef)m_stream;
126 if (stream) {
127 FSEventStreamStop(stream);
128 FSEventStreamInvalidate(stream);
129 FSEventStreamRelease(stream);
130 }
131 m_stream = 0;
132 #else
69 // annoyingly, removePaths prints a warning if given an empty list 133 // annoyingly, removePaths prints a warning if given an empty list
70 if (!m_watcher.directories().empty()) { 134 if (!m_watcher.directories().empty()) {
71 m_watcher.removePaths(m_watcher.directories()); 135 m_watcher.removePaths(m_watcher.directories());
72 } 136 }
73 if (!m_watcher.files().empty()) { 137 if (!m_watcher.files().empty()) {
74 m_watcher.removePaths(m_watcher.files()); 138 m_watcher.removePaths(m_watcher.files());
75 } 139 }
76 m_workDirPath = path; 140 #endif
77 addWorkDirectory(path); 141 }
78 debugPrint(); 142
79 } 143 #ifdef Q_OS_MAC
80 144 static void
81 void 145 fsEventsCallback(FSEventStreamRef streamRef,
82 FsWatcher::setTrackedFilePaths(QStringList paths) 146 void *clientCallBackInfo,
83 { 147 int numEvents,
84 QMutexLocker locker(&m_mutex); 148 const char *const eventPaths[],
85 149 const FSEventStreamEventFlags *eventFlags,
86 QSet<QString> alreadyWatched = 150 const uint64_t *eventIDs)
87 QSet<QString>::fromList(m_watcher.files()); 151 {
88 152 }
89 foreach (QString path, paths) { 153 #endif
90 path = m_workDirPath + QDir::separator() + path;
91 if (!alreadyWatched.contains(path)) {
92 m_watcher.addPath(path);
93 } else {
94 alreadyWatched.remove(path);
95 }
96 }
97
98 // Remove the remaining paths, those that were being watched
99 // before but that are not in the list we were given
100 foreach (QString path, alreadyWatched) {
101 m_watcher.removePath(path);
102 }
103
104 debugPrint();
105 }
106 154
107 void 155 void
108 FsWatcher::addWorkDirectory(QString path) 156 FsWatcher::addWorkDirectory(QString path)
109 { 157 {
158 #ifdef Q_OS_MAC
159 FSEventStreamRef stream =
160 FSEventStreamCreate(kCFAllocatorDefault,
161 (FSEventStreamCallback)&fsEventsCallback,
162 this,
163 cfPaths,
164 kFSEventStreamEventIdSinceNow,
165 1.0, // latency, seconds
166 kFSEventStreamCreateFlagNone);
167
168 m_stream = stream;
169
170 FSEventStreamScheduleWithRunLoop(stream,
171 CFRunLoopGetCurrent(),
172 kCFRunLoopDefaultMode);
173
174 if (!FSEventStreamStart(stream)) {
175 std::cerr << "ERROR: FsWatcher::addWorkDirectory: Failed to start FSEvent stream" << std::endl;
176 }
177 #else
110 // QFileSystemWatcher will refuse to add a file or directory to 178 // QFileSystemWatcher will refuse to add a file or directory to
111 // its watch list that it is already watching -- fine -- but it 179 // its watch list that it is already watching -- fine -- but it
112 // prints a warning when this happens, which we wouldn't want. So 180 // prints a warning when this happens, which we wouldn't want. So
113 // we'll check for duplicates ourselves. 181 // we'll check for duplicates ourselves.
114 QSet<QString> alreadyWatched = 182 QSet<QString> alreadyWatched =
135 QString entryPath = d.absoluteFilePath(entry); 203 QString entryPath = d.absoluteFilePath(entry);
136 pending.push_back(entryPath); 204 pending.push_back(entryPath);
137 } 205 }
138 } 206 }
139 } 207 }
208 #endif
209 }
210
211 void
212 FsWatcher::setTrackedFilePaths(QStringList paths)
213 {
214 #ifdef Q_OS_MAC
215 // We don't need to do anything here, so long as addWorkDirectory
216 // has been called -- FSEvents monitors files within directories
217 // as well as the directories themselves (even though it only
218 // notifies with directory granularity)
219 #else
220 QMutexLocker locker(&m_mutex);
221
222 QSet<QString> alreadyWatched =
223 QSet<QString>::fromList(m_watcher.files());
224
225 foreach (QString path, paths) {
226 path = m_workDirPath + QDir::separator() + path;
227 if (!alreadyWatched.contains(path)) {
228 m_watcher.addPath(path);
229 } else {
230 alreadyWatched.remove(path);
231 }
232 }
233
234 // Remove the remaining paths, those that were being watched
235 // before but that are not in the list we were given
236 foreach (QString path, alreadyWatched) {
237 m_watcher.removePath(path);
238 }
239
240 debugPrint();
241 #endif
140 } 242 }
141 243
142 void 244 void
143 FsWatcher::setIgnoredFilePrefixes(QStringList prefixes) 245 FsWatcher::setIgnoredFilePrefixes(QStringList prefixes)
144 { 246 {