Mercurial > hg > easyhg
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 { |