comparison src/fswatcher.cpp @ 586:687d9e700e81 fswatcher

Check file timestamps for tracked files when a directory change is notified from FSEvents
author Chris Cannam
date Wed, 14 Mar 2012 11:40:58 +0000
parents fa242885a233
children 0b0444762a5d
comparison
equal deleted inserted replaced
585:fa242885a233 586:687d9e700e81
76 * This means the Qt class uses kqueue instead on OS/X, but that 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 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 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 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 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 81 * notifies with directory granularity, but that might be OK for us so
82 * as notifications are actually provoked by file changes as well. 82 * long as notifications are actually provoked by file changes as
83 * well. (In OS/X 10.7 there appear to be file-level notifications
84 * too, but that doesn't help us.)
83 * 85 *
84 * One other problem with FSEvents is that the API only exists on OS/X 86 * 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 87 * 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 88 * 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, 89 * linked with the FSEvents API to run on 10.4 without some fiddling,
150 const FSEventStreamEventId eventIDs[]) 152 const FSEventStreamEventId eventIDs[])
151 { 153 {
152 FsWatcher *watcher = reinterpret_cast<FsWatcher *>(clientCallBackInfo); 154 FsWatcher *watcher = reinterpret_cast<FsWatcher *>(clientCallBackInfo);
153 const char *const *cpaths = reinterpret_cast<const char *const *>(paths); 155 const char *const *cpaths = reinterpret_cast<const char *const *>(paths);
154 for (size_t i = 0; i < numEvents; ++i) { 156 for (size_t i = 0; i < numEvents; ++i) {
157 std::cerr << "path " << i << " = " << cpaths[i] << std::endl;
155 watcher->fsDirectoryChanged(QString::fromLocal8Bit(cpaths[i])); 158 watcher->fsDirectoryChanged(QString::fromLocal8Bit(cpaths[i]));
156 } 159 }
157 } 160 }
158 #endif 161 #endif
159 162
225 228
226 void 229 void
227 FsWatcher::setTrackedFilePaths(QStringList paths) 230 FsWatcher::setTrackedFilePaths(QStringList paths)
228 { 231 {
229 #ifdef Q_OS_MAC 232 #ifdef Q_OS_MAC
230 // We don't need to do anything here, so long as addWorkDirectory 233
231 // has been called -- FSEvents monitors files within directories 234 // FSEvents will notify when any file in the directory changes,
232 // as well as the directories themselves (even though it only 235 // but we need to be able to check whether the file change was
233 // notifies with directory granularity) 236 // meaningful to us if it didn't result in any files being added
237 // or removed -- and we have to do that by examining timestamps on
238 // the files we care about
239 foreach (QString p, paths) {
240 m_trackedFileUpdates[p] = QDateTime::currentDateTime();
241 }
242
234 #else 243 #else
244
235 QMutexLocker locker(&m_mutex); 245 QMutexLocker locker(&m_mutex);
236 246
237 QSet<QString> alreadyWatched = 247 QSet<QString> alreadyWatched =
238 QSet<QString>::fromList(m_watcher.files()); 248 QSet<QString>::fromList(m_watcher.files());
239 249
251 foreach (QString path, alreadyWatched) { 261 foreach (QString path, alreadyWatched) {
252 m_watcher.removePath(path); 262 m_watcher.removePath(path);
253 } 263 }
254 264
255 debugPrint(); 265 debugPrint();
266
256 #endif 267 #endif
257 } 268 }
258 269
259 void 270 void
260 FsWatcher::setIgnoredFilePrefixes(QStringList prefixes) 271 FsWatcher::setIgnoredFilePrefixes(QStringList prefixes)
296 } 307 }
297 308
298 void 309 void
299 FsWatcher::fsDirectoryChanged(QString path) 310 FsWatcher::fsDirectoryChanged(QString path)
300 { 311 {
312 bool haveChanges = false;
313
301 { 314 {
302 QMutexLocker locker(&m_mutex); 315 QMutexLocker locker(&m_mutex);
303 316
304 if (shouldIgnore(path)) return; 317 if (shouldIgnore(path)) return;
305 318
306 QSet<QString> files = scanDirectory(path); 319 QSet<QString> files = scanDirectory(path);
320
307 if (files == m_dirContents[path]) { 321 if (files == m_dirContents[path]) {
322
308 #ifdef DEBUG_FSWATCHER 323 #ifdef DEBUG_FSWATCHER
309 std::cerr << "FsWatcher: Directory " << path << " has changed, but not in a way that we are monitoring" << std::endl; 324 std::cerr << "FsWatcher: Directory " << path << " has changed, but not in a way that we are monitoring" << std::endl;
310 #endif 325 #endif
311 return; 326
327 #ifdef Q_OS_MAC
328 haveChanges = manuallyCheckTrackedFiles();
329 #endif
330
312 } else { 331 } else {
332
313 #ifdef DEBUG_FSWATCHER 333 #ifdef DEBUG_FSWATCHER
314 std::cerr << "FsWatcher: Directory " << path << " has changed" << std::endl; 334 std::cerr << "FsWatcher: Directory " << path << " has changed" << std::endl;
315 #endif 335 #endif
316 m_dirContents[path] = files; 336 m_dirContents[path] = files;
317 } 337 size_t counter = ++m_lastCounter;
318 338 m_changes[path] = counter;
319 size_t counter = ++m_lastCounter; 339 haveChanges = true;
320 m_changes[path] = counter; 340 }
321 } 341 }
322 342
323 emit changed(); 343 if (haveChanges) {
344 emit changed();
345 }
324 } 346 }
325 347
326 void 348 void
327 FsWatcher::fsFileChanged(QString path) 349 FsWatcher::fsFileChanged(QString path)
328 { 350 {
330 QMutexLocker locker(&m_mutex); 352 QMutexLocker locker(&m_mutex);
331 353
332 // We don't check whether the file matches an ignore pattern, 354 // We don't check whether the file matches an ignore pattern,
333 // because we are only notified for file changes if we are 355 // because we are only notified for file changes if we are
334 // watching the file explicitly, i.e. the file is in the 356 // watching the file explicitly, i.e. the file is in the
335 // tracked file paths list. So we never want to ignore them 357 // tracked file paths list. So we never want to ignore these
336 358
337 #ifdef DEBUG_FSWATCHER 359 #ifdef DEBUG_FSWATCHER
338 std::cerr << "FsWatcher: Tracked file " << path << " has changed" << std::endl; 360 std::cerr << "FsWatcher: Tracked file " << path << " has changed" << std::endl;
339 #endif 361 #endif
340 362
342 m_changes[path] = counter; 364 m_changes[path] = counter;
343 } 365 }
344 366
345 emit changed(); 367 emit changed();
346 } 368 }
369
370 #ifdef Q_OS_MAC
371 bool
372 FsWatcher::manuallyCheckTrackedFiles()
373 {
374 bool foundChanges = false;
375
376 for (PathTimeMap::iterator i = m_trackedFileUpdates.begin();
377 i != m_trackedFileUpdates.end(); ++i) {
378
379 QString path = i.key();
380 QDateTime prevUpdate = i.value();
381
382 QFileInfo fi(path);
383 QDateTime currUpdate = fi.lastModified();
384
385 if (currUpdate > prevUpdate) {
386
387 #ifdef DEBUG_FSWATCHER
388 std::cerr << "FsWatcher: Tracked file " << path << " has been changed since last check" << std::endl;
389 #endif
390 i.value() = currUpdate;
391
392 size_t counter = ++m_lastCounter;
393 m_changes[path] = counter;
394 foundChanges = true;
395 }
396 }
397
398 return foundChanges;
399 }
400 #endif
347 401
348 bool 402 bool
349 FsWatcher::shouldIgnore(QString path) 403 FsWatcher::shouldIgnore(QString path)
350 { 404 {
351 QFileInfo fi(path); 405 QFileInfo fi(path);
381 if (entry.startsWith('.')) continue; 435 if (entry.startsWith('.')) continue;
382 if (shouldIgnore(entry)) continue; 436 if (shouldIgnore(entry)) continue;
383 files.insert(entry); 437 files.insert(entry);
384 } 438 }
385 } 439 }
386 // std::cerr << "scanDirectory:" << std::endl;
387 // foreach (QString f, files) std::cerr << f << std::endl;
388 return files; 440 return files;
389 } 441 }
390 442
391 void 443 void
392 FsWatcher::debugPrint() 444 FsWatcher::debugPrint()