| Chris@538 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@538 | 2 | 
| Chris@538 | 3 /* | 
| Chris@538 | 4     EasyMercurial | 
| Chris@538 | 5 | 
| Chris@538 | 6     Based on hgExplorer by Jari Korhonen | 
| Chris@538 | 7     Copyright (c) 2010 Jari Korhonen | 
| Chris@644 | 8     Copyright (c) 2013 Chris Cannam | 
| Chris@644 | 9     Copyright (c) 2013 Queen Mary, University of London | 
| Chris@538 | 10 | 
| Chris@538 | 11     This program is free software; you can redistribute it and/or | 
| Chris@538 | 12     modify it under the terms of the GNU General Public License as | 
| Chris@538 | 13     published by the Free Software Foundation; either version 2 of the | 
| Chris@538 | 14     License, or (at your option) any later version.  See the file | 
| Chris@538 | 15     COPYING included with this distribution for more information. | 
| Chris@538 | 16 */ | 
| Chris@538 | 17 | 
| Chris@584 | 18 #include <QMutexLocker> | 
| Chris@584 | 19 #include <QDir> | 
| Chris@584 | 20 | 
| Chris@584 | 21 #ifdef Q_OS_MAC | 
| Chris@584 | 22 // Must include this before debug.h | 
| Chris@584 | 23 #include <CoreServices/CoreServices.h> | 
| Chris@584 | 24 #endif | 
| Chris@584 | 25 | 
| Chris@538 | 26 #include "fswatcher.h" | 
| Chris@540 | 27 #include "debug.h" | 
| Chris@538 | 28 | 
| Chris@538 | 29 #include <deque> | 
| Chris@538 | 30 | 
| Chris@603 | 31 //#define DEBUG_FSWATCHER 1 | 
| Chris@540 | 32 | 
| Chris@539 | 33 /* | 
| Chris@539 | 34  * Watching the filesystem is trickier than it seems at first glance. | 
| Chris@539 | 35  * | 
| Chris@539 | 36  * We ideally should watch every directory, and every file that is | 
| Chris@539 | 37  * tracked by Hg. If a new file is created in a directory, then we | 
| Chris@539 | 38  * need to respond in order to show it as a potential candidate to be | 
| Chris@539 | 39  * added. | 
| Chris@539 | 40  * | 
| Chris@539 | 41  * Complicating matters though is that Hg itself might modify the | 
| Chris@539 | 42  * filesystem. This can happen even in "read-only" operations: for | 
| Chris@539 | 43  * example, hg stat creates files called hg-checklink and hg-checkexec | 
| Chris@539 | 44  * to test properties of the filesystem. So we need to know to ignore | 
| Chris@539 | 45  * those files; unfortunately, when watching a directory (which is how | 
| Chris@539 | 46  * we find out about the creation of new files) we are notified only | 
| Chris@540 | 47  * that the directory has changed -- we aren't told what changed. | 
| Chris@540 | 48  * | 
| Chris@540 | 49  * This means that, when a directory changes, we need to rescan the | 
| Chris@540 | 50  * directory to learn whether the set of files in it _excluding_ files | 
| Chris@540 | 51  * matching our ignore patterns differs from the previous scan, and | 
| Chris@540 | 52  * ignore the change if it doesn't. | 
| Chris@584 | 53  * | 
| Chris@584 | 54  */ | 
| Chris@584 | 55 | 
| Chris@584 | 56 /* | 
| Chris@584 | 57  * 20120312 -- Another complication. The documentation for | 
| Chris@584 | 58  * QFileSystemWatcher says: | 
| Chris@584 | 59  * | 
| Chris@584 | 60  *     On Mac OS X 10.4 [...] an open file descriptor is required for | 
| Chris@584 | 61  *     each monitored file. [...] This means that addPath() and | 
| Chris@584 | 62  *     addPaths() will fail if your process tries to add more than 256 | 
| Chris@584 | 63  *     files or directories to the file system monitor [...] Mac OS X | 
| Chris@584 | 64  *     10.5 and up use a different backend and do not suffer from this | 
| Chris@584 | 65  *     issue. | 
| Chris@584 | 66  * | 
| Chris@584 | 67  * Unfortunately, the last sentence above is not true: | 
| Chris@584 | 68  * http://qt.gitorious.org/qt/qt/commit/6d1baf9979346d6f15da81a535becb4046278962 | 
| Chris@584 | 69  * ("Removing the usage of FSEvents-based backend for now as it has a | 
| Chris@584 | 70  * few bugs...").  It can't be restored without hacking the Qt source, | 
| Chris@584 | 71  * which we don't want to do in this context. The commit log doesn't | 
| Chris@584 | 72  * make clear how serious the bugs were -- an example is given but it | 
| Chris@584 | 73  * doesn't indicate whether it's an edge case or a common case and | 
| Chris@584 | 74  * whether the result was a crash or failure to notify. | 
| Chris@584 | 75  * | 
| Chris@584 | 76  * This means the Qt class uses kqueue instead on OS/X, but that | 
| Chris@584 | 77  * doesn't really work for us -- it can only monitor 256 files (or | 
| Chris@584 | 78  * whatever the fd ulimit is set to, but that's the default) and it | 
| Chris@584 | 79  * doesn't notify if a file within a directory is modified unless the | 
| Chris@584 | 80  * metadata changes. The main limitation of FSEvents is that it only | 
| Chris@586 | 81  * notifies with directory granularity, but that might be OK for us so | 
| Chris@586 | 82  * long as notifications are actually provoked by file changes as | 
| Chris@586 | 83  * well. (In OS/X 10.7 there appear to be file-level notifications | 
| Chris@586 | 84  * too, but that doesn't help us.) | 
| Chris@584 | 85  * | 
| Chris@584 | 86  * One other problem with FSEvents is that the API only exists on OS/X | 
| Chris@584 | 87  * 10.5 or newer -- on older versions we would have no option but to | 
| Chris@584 | 88  * use kqueue via QFileSystemWatcher. But we can't ship a binary | 
| Chris@584 | 89  * linked with the FSEvents API to run on 10.4 without some fiddling, | 
| Chris@584 | 90  * and I'm not really keen to do that either.  That may be our cue to | 
| Chris@584 | 91  * drop 10.4 support for EasyMercurial. | 
| Chris@539 | 92  */ | 
| Chris@539 | 93 | 
| Chris@730 | 94 static bool abandoning = false; // emergency flag for use by non-member callback | 
| Chris@730 | 95 | 
| Chris@538 | 96 FsWatcher::FsWatcher() : | 
| Chris@538 | 97     m_lastToken(0), | 
| Chris@538 | 98     m_lastCounter(0) | 
| Chris@538 | 99 { | 
| Chris@584 | 100 #ifdef Q_OS_MAC | 
| Chris@584 | 101     m_stream = 0; // create when we have a path | 
| Chris@584 | 102 #else | 
| Chris@538 | 103     connect(&m_watcher, SIGNAL(directoryChanged(QString)), | 
| Chris@538 | 104 	    this, SLOT(fsDirectoryChanged(QString))); | 
| Chris@538 | 105     connect(&m_watcher, SIGNAL(fileChanged(QString)), | 
| Chris@538 | 106 	    this, SLOT(fsFileChanged(QString))); | 
| Chris@584 | 107 #endif | 
| Chris@538 | 108 } | 
| Chris@538 | 109 | 
| Chris@538 | 110 FsWatcher::~FsWatcher() | 
| Chris@538 | 111 { | 
| Chris@730 | 112     QMutexLocker locker(&m_mutex); | 
| Chris@730 | 113     abandoning = true; | 
| Chris@538 | 114 } | 
| Chris@538 | 115 | 
| Chris@538 | 116 void | 
| Chris@538 | 117 FsWatcher::setWorkDirPath(QString path) | 
| Chris@538 | 118 { | 
| Chris@538 | 119     QMutexLocker locker(&m_mutex); | 
| Chris@541 | 120     if (m_workDirPath == path) return; | 
| Chris@584 | 121     clearWatchedPaths(); | 
| Chris@584 | 122     m_workDirPath = path; | 
| Chris@584 | 123     addWorkDirectory(path); | 
| Chris@584 | 124     debugPrint(); | 
| Chris@584 | 125 } | 
| Chris@584 | 126 | 
| Chris@584 | 127 void | 
| Chris@584 | 128 FsWatcher::clearWatchedPaths() | 
| Chris@584 | 129 { | 
| Chris@584 | 130 #ifdef Q_OS_MAC | 
| Chris@584 | 131     FSEventStreamRef stream = (FSEventStreamRef)m_stream; | 
| Chris@584 | 132     if (stream) { | 
| Chris@584 | 133         FSEventStreamStop(stream); | 
| Chris@584 | 134         FSEventStreamInvalidate(stream); | 
| Chris@584 | 135         FSEventStreamRelease(stream); | 
| Chris@584 | 136     } | 
| Chris@584 | 137     m_stream = 0; | 
| Chris@584 | 138 #else | 
| Chris@562 | 139     // annoyingly, removePaths prints a warning if given an empty list | 
| Chris@562 | 140     if (!m_watcher.directories().empty()) { | 
| Chris@562 | 141         m_watcher.removePaths(m_watcher.directories()); | 
| Chris@562 | 142     } | 
| Chris@562 | 143     if (!m_watcher.files().empty()) { | 
| Chris@562 | 144         m_watcher.removePaths(m_watcher.files()); | 
| Chris@562 | 145     } | 
| Chris@584 | 146 #endif | 
| Chris@538 | 147 } | 
| Chris@538 | 148 | 
| Chris@584 | 149 #ifdef Q_OS_MAC | 
| Chris@584 | 150 static void | 
| Chris@699 | 151 fsEventsCallback(ConstFSEventStreamRef /* streamRef */, | 
| Chris@584 | 152                  void *clientCallBackInfo, | 
| Chris@585 | 153                  size_t numEvents, | 
| Chris@585 | 154                  void *paths, | 
| Chris@699 | 155                  const FSEventStreamEventFlags /* eventFlags */[], | 
| Chris@699 | 156                  const FSEventStreamEventId /*eventIDs */[]) | 
| Chris@539 | 157 { | 
| Chris@730 | 158     if (abandoning) return; | 
| Chris@585 | 159     FsWatcher *watcher = reinterpret_cast<FsWatcher *>(clientCallBackInfo); | 
| Chris@585 | 160     const char *const *cpaths = reinterpret_cast<const char *const *>(paths); | 
| Chris@585 | 161     for (size_t i = 0; i < numEvents; ++i) { | 
| Chris@647 | 162 #ifdef DEBUG_FSWATCHER | 
| Chris@586 | 163         std::cerr << "path " << i << " = " << cpaths[i] << std::endl; | 
| Chris@647 | 164 #endif | 
| Chris@585 | 165         watcher->fsDirectoryChanged(QString::fromLocal8Bit(cpaths[i])); | 
| Chris@585 | 166     } | 
| Chris@539 | 167 } | 
| Chris@584 | 168 #endif | 
| Chris@539 | 169 | 
| Chris@539 | 170 void | 
| Chris@538 | 171 FsWatcher::addWorkDirectory(QString path) | 
| Chris@538 | 172 { | 
| Chris@584 | 173 #ifdef Q_OS_MAC | 
| Chris@585 | 174 | 
| Chris@585 | 175     CFStringRef cfPath = CFStringCreateWithCharacters | 
| Chris@585 | 176         (0, reinterpret_cast<const UniChar *>(path.unicode()), | 
| Chris@585 | 177          path.length()); | 
| Chris@585 | 178 | 
| Chris@585 | 179     CFArrayRef cfPaths = CFArrayCreate(0, (const void **)&cfPath, 1, 0); | 
| Chris@585 | 180 | 
| Chris@585 | 181     FSEventStreamContext ctx = { 0, 0, 0, 0, 0 }; | 
| Chris@585 | 182     ctx.info = this; | 
| Chris@585 | 183 | 
| Chris@584 | 184     FSEventStreamRef stream = | 
| Chris@584 | 185         FSEventStreamCreate(kCFAllocatorDefault, | 
| Chris@585 | 186                             &fsEventsCallback, | 
| Chris@585 | 187                             &ctx, | 
| Chris@584 | 188                             cfPaths, | 
| Chris@584 | 189                             kFSEventStreamEventIdSinceNow, | 
| Chris@584 | 190                             1.0, // latency, seconds | 
| Chris@584 | 191                             kFSEventStreamCreateFlagNone); | 
| Chris@584 | 192 | 
| Chris@584 | 193     m_stream = stream; | 
| Chris@584 | 194 | 
| Chris@584 | 195     FSEventStreamScheduleWithRunLoop(stream, | 
| Chris@584 | 196                                      CFRunLoopGetCurrent(), | 
| Chris@584 | 197                                      kCFRunLoopDefaultMode); | 
| Chris@584 | 198 | 
| Chris@584 | 199     if (!FSEventStreamStart(stream)) { | 
| Chris@584 | 200         std::cerr << "ERROR: FsWatcher::addWorkDirectory: Failed to start FSEvent stream" << std::endl; | 
| Chris@584 | 201     } | 
| Chris@584 | 202 #else | 
| Chris@538 | 203     // QFileSystemWatcher will refuse to add a file or directory to | 
| Chris@538 | 204     // its watch list that it is already watching -- fine -- but it | 
| Chris@538 | 205     // prints a warning when this happens, which we wouldn't want.  So | 
| Chris@538 | 206     // we'll check for duplicates ourselves. | 
| Chris@538 | 207     QSet<QString> alreadyWatched = | 
| Chris@538 | 208 	QSet<QString>::fromList(m_watcher.directories()); | 
| Chris@538 | 209 | 
| Chris@538 | 210     std::deque<QString> pending; | 
| Chris@538 | 211     pending.push_back(path); | 
| Chris@538 | 212 | 
| Chris@538 | 213     while (!pending.empty()) { | 
| Chris@538 | 214 | 
| Chris@538 | 215         QString path = pending.front(); | 
| Chris@538 | 216         pending.pop_front(); | 
| Chris@538 | 217         if (!alreadyWatched.contains(path)) { | 
| Chris@538 | 218             m_watcher.addPath(path); | 
| Chris@540 | 219             m_dirContents[path] = scanDirectory(path); | 
| Chris@538 | 220         } | 
| Chris@538 | 221 | 
| Chris@538 | 222         QDir d(path); | 
| Chris@538 | 223         if (d.exists()) { | 
| Chris@538 | 224             d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | | 
| Chris@538 | 225                         QDir::Readable | QDir::NoSymLinks); | 
| Chris@538 | 226             foreach (QString entry, d.entryList()) { | 
| Chris@538 | 227                 if (entry.startsWith('.')) continue; | 
| Chris@538 | 228                 QString entryPath = d.absoluteFilePath(entry); | 
| Chris@538 | 229                 pending.push_back(entryPath); | 
| Chris@538 | 230             } | 
| Chris@538 | 231         } | 
| Chris@538 | 232     } | 
| Chris@584 | 233 #endif | 
| Chris@584 | 234 } | 
| Chris@584 | 235 | 
| Chris@584 | 236 void | 
| Chris@584 | 237 FsWatcher::setTrackedFilePaths(QStringList paths) | 
| Chris@584 | 238 { | 
| Chris@584 | 239 #ifdef Q_OS_MAC | 
| Chris@586 | 240 | 
| Chris@586 | 241     // FSEvents will notify when any file in the directory changes, | 
| Chris@586 | 242     // but we need to be able to check whether the file change was | 
| Chris@586 | 243     // meaningful to us if it didn't result in any files being added | 
| Chris@586 | 244     // or removed -- and we have to do that by examining timestamps on | 
| Chris@586 | 245     // the files we care about | 
| Chris@586 | 246     foreach (QString p, paths) { | 
| Chris@586 | 247         m_trackedFileUpdates[p] = QDateTime::currentDateTime(); | 
| Chris@586 | 248     } | 
| Chris@586 | 249 | 
| Chris@584 | 250 #else | 
| Chris@586 | 251 | 
| Chris@584 | 252     QMutexLocker locker(&m_mutex); | 
| Chris@584 | 253 | 
| Chris@584 | 254     QSet<QString> alreadyWatched = | 
| Chris@584 | 255 	QSet<QString>::fromList(m_watcher.files()); | 
| Chris@584 | 256 | 
| Chris@584 | 257     foreach (QString path, paths) { | 
| Chris@584 | 258         path = m_workDirPath + QDir::separator() + path; | 
| Chris@584 | 259         if (!alreadyWatched.contains(path)) { | 
| Chris@584 | 260             m_watcher.addPath(path); | 
| Chris@584 | 261         } else { | 
| Chris@584 | 262             alreadyWatched.remove(path); | 
| Chris@584 | 263         } | 
| Chris@584 | 264     } | 
| Chris@584 | 265 | 
| Chris@584 | 266     // Remove the remaining paths, those that were being watched | 
| Chris@584 | 267     // before but that are not in the list we were given | 
| Chris@584 | 268     foreach (QString path, alreadyWatched) { | 
| Chris@584 | 269         m_watcher.removePath(path); | 
| Chris@584 | 270     } | 
| Chris@584 | 271 | 
| Chris@584 | 272     debugPrint(); | 
| Chris@586 | 273 | 
| Chris@584 | 274 #endif | 
| Chris@538 | 275 } | 
| Chris@538 | 276 | 
| Chris@538 | 277 void | 
| Chris@538 | 278 FsWatcher::setIgnoredFilePrefixes(QStringList prefixes) | 
| Chris@538 | 279 { | 
| Chris@538 | 280     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 281     m_ignoredPrefixes = prefixes; | 
| Chris@538 | 282 } | 
| Chris@538 | 283 | 
| Chris@538 | 284 void | 
| Chris@538 | 285 FsWatcher::setIgnoredFileSuffixes(QStringList suffixes) | 
| Chris@538 | 286 { | 
| Chris@538 | 287     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 288     m_ignoredSuffixes = suffixes; | 
| Chris@538 | 289 } | 
| Chris@538 | 290 | 
| Chris@538 | 291 int | 
| Chris@538 | 292 FsWatcher::getNewToken() | 
| Chris@538 | 293 { | 
| Chris@538 | 294     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 295     int token = ++m_lastToken; | 
| Chris@538 | 296     m_tokenMap[token] = m_lastCounter; | 
| Chris@538 | 297     return token; | 
| Chris@538 | 298 } | 
| Chris@538 | 299 | 
| Chris@538 | 300 QSet<QString> | 
| Chris@538 | 301 FsWatcher::getChangedPaths(int token) | 
| Chris@538 | 302 { | 
| Chris@538 | 303     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 304     size_t lastUpdatedAt = m_tokenMap[token]; | 
| Chris@538 | 305     QSet<QString> changed; | 
| Chris@538 | 306     for (QHash<QString, size_t>::const_iterator i = m_changes.begin(); | 
| Chris@593 | 307          i != m_changes.end(); ++i) { | 
| Chris@593 | 308         if (i.value() > lastUpdatedAt) { | 
| Chris@593 | 309              changed.insert(i.key()); | 
| Chris@593 | 310         } | 
| Chris@538 | 311     } | 
| Chris@538 | 312     m_tokenMap[token] = m_lastCounter; | 
| Chris@538 | 313     return changed; | 
| Chris@538 | 314 } | 
| Chris@538 | 315 | 
| Chris@538 | 316 void | 
| Chris@538 | 317 FsWatcher::fsDirectoryChanged(QString path) | 
| Chris@538 | 318 { | 
| Chris@586 | 319     bool haveChanges = false; | 
| Chris@586 | 320 | 
| Chris@538 | 321     { | 
| Chris@538 | 322 	QMutexLocker locker(&m_mutex); | 
| Chris@540 | 323 | 
| Chris@538 | 324 	if (shouldIgnore(path)) return; | 
| Chris@540 | 325 | 
| Chris@540 | 326         QSet<QString> files = scanDirectory(path); | 
| Chris@586 | 327 | 
| Chris@540 | 328         if (files == m_dirContents[path]) { | 
| Chris@586 | 329 | 
| Chris@540 | 330 #ifdef DEBUG_FSWATCHER | 
| Chris@593 | 331             std::cerr << "FsWatcher: Directory " << path << " has changed, but not in a way that we are monitoring -- doing manual check" << std::endl; | 
| Chris@540 | 332 #endif | 
| Chris@586 | 333 | 
| Chris@586 | 334 #ifdef Q_OS_MAC | 
| Chris@586 | 335             haveChanges = manuallyCheckTrackedFiles(); | 
| Chris@586 | 336 #endif | 
| Chris@586 | 337 | 
| Chris@541 | 338         } else { | 
| Chris@586 | 339 | 
| Chris@541 | 340 #ifdef DEBUG_FSWATCHER | 
| Chris@541 | 341             std::cerr << "FsWatcher: Directory " << path << " has changed" << std::endl; | 
| Chris@541 | 342 #endif | 
| Chris@541 | 343             m_dirContents[path] = files; | 
| Chris@586 | 344             size_t counter = ++m_lastCounter; | 
| Chris@586 | 345             m_changes[path] = counter; | 
| Chris@586 | 346             haveChanges = true; | 
| Chris@540 | 347         } | 
| Chris@538 | 348     } | 
| Chris@540 | 349 | 
| Chris@586 | 350     if (haveChanges) { | 
| Chris@586 | 351         emit changed(); | 
| Chris@586 | 352     } | 
| Chris@538 | 353 } | 
| Chris@538 | 354 | 
| Chris@538 | 355 void | 
| Chris@538 | 356 FsWatcher::fsFileChanged(QString path) | 
| Chris@538 | 357 { | 
| Chris@540 | 358     { | 
| Chris@540 | 359         QMutexLocker locker(&m_mutex); | 
| Chris@540 | 360 | 
| Chris@540 | 361         // We don't check whether the file matches an ignore pattern, | 
| Chris@540 | 362         // because we are only notified for file changes if we are | 
| Chris@540 | 363         // watching the file explicitly, i.e. the file is in the | 
| Chris@586 | 364         // tracked file paths list. So we never want to ignore these | 
| Chris@540 | 365 | 
| Chris@563 | 366 #ifdef DEBUG_FSWATCHER | 
| Chris@541 | 367         std::cerr << "FsWatcher: Tracked file " << path << " has changed" << std::endl; | 
| Chris@563 | 368 #endif | 
| Chris@541 | 369 | 
| Chris@540 | 370         size_t counter = ++m_lastCounter; | 
| Chris@540 | 371         m_changes[path] = counter; | 
| Chris@540 | 372     } | 
| Chris@540 | 373 | 
| Chris@540 | 374     emit changed(); | 
| Chris@538 | 375 } | 
| Chris@538 | 376 | 
| Chris@586 | 377 #ifdef Q_OS_MAC | 
| Chris@586 | 378 bool | 
| Chris@586 | 379 FsWatcher::manuallyCheckTrackedFiles() | 
| Chris@586 | 380 { | 
| Chris@647 | 381 #ifdef DEBUG_FSWATCHER | 
| Chris@593 | 382     std::cerr << "FsWatcher::manuallyCheckTrackedFiles" << std::endl; | 
| Chris@647 | 383 #endif | 
| Chris@586 | 384     bool foundChanges = false; | 
| Chris@586 | 385 | 
| Chris@586 | 386     for (PathTimeMap::iterator i = m_trackedFileUpdates.begin(); | 
| Chris@586 | 387          i != m_trackedFileUpdates.end(); ++i) { | 
| Chris@586 | 388 | 
| Chris@586 | 389         QString path = i.key(); | 
| Chris@586 | 390         QDateTime prevUpdate = i.value(); | 
| Chris@586 | 391 | 
| Chris@593 | 392         QFileInfo fi(m_workDirPath + QDir::separator() + path); | 
| Chris@586 | 393         QDateTime currUpdate = fi.lastModified(); | 
| Chris@586 | 394 | 
| Chris@593 | 395 //        std::cerr << "FsWatcher: Tracked file " << path << " previously changed at " | 
| Chris@593 | 396 //                  << prevUpdate.toString().toStdString() | 
| Chris@593 | 397 //                  << ", currently at " << currUpdate.toString().toStdString() << std::endl; | 
| Chris@593 | 398 | 
| Chris@586 | 399         if (currUpdate > prevUpdate) { | 
| Chris@586 | 400 | 
| Chris@586 | 401 #ifdef DEBUG_FSWATCHER | 
| Chris@586 | 402             std::cerr << "FsWatcher: Tracked file " << path << " has been changed since last check" << std::endl; | 
| Chris@586 | 403 #endif | 
| Chris@586 | 404             i.value() = currUpdate; | 
| Chris@586 | 405 | 
| Chris@586 | 406             size_t counter = ++m_lastCounter; | 
| Chris@586 | 407             m_changes[path] = counter; | 
| Chris@586 | 408             foundChanges = true; | 
| Chris@586 | 409         } | 
| Chris@586 | 410     } | 
| Chris@586 | 411 | 
| Chris@586 | 412     return foundChanges; | 
| Chris@586 | 413 } | 
| Chris@586 | 414 #endif | 
| Chris@586 | 415 | 
| Chris@538 | 416 bool | 
| Chris@538 | 417 FsWatcher::shouldIgnore(QString path) | 
| Chris@538 | 418 { | 
| Chris@540 | 419     QFileInfo fi(path); | 
| Chris@540 | 420     QString fn(fi.fileName()); | 
| Chris@540 | 421     foreach (QString pfx, m_ignoredPrefixes) { | 
| Chris@541 | 422         if (fn.startsWith(pfx)) { | 
| Chris@563 | 423 #ifdef DEBUG_FSWATCHER | 
| Chris@541 | 424             std::cerr << "(ignoring: " << path << ")" << std::endl; | 
| Chris@563 | 425 #endif | 
| Chris@541 | 426             return true; | 
| Chris@541 | 427         } | 
| Chris@540 | 428     } | 
| Chris@540 | 429     foreach (QString sfx, m_ignoredSuffixes) { | 
| Chris@541 | 430         if (fn.endsWith(sfx)) { | 
| Chris@563 | 431 #ifdef DEBUG_FSWATCHER | 
| Chris@541 | 432             std::cerr << "(ignoring: " << path << ")" << std::endl; | 
| Chris@563 | 433 #endif | 
| Chris@541 | 434             return true; | 
| Chris@541 | 435         } | 
| Chris@540 | 436     } | 
| Chris@540 | 437     return false; | 
| Chris@538 | 438 } | 
| Chris@538 | 439 | 
| Chris@540 | 440 QSet<QString> | 
| Chris@540 | 441 FsWatcher::scanDirectory(QString path) | 
| Chris@540 | 442 { | 
| Chris@540 | 443     QSet<QString> files; | 
| Chris@540 | 444     QDir d(path); | 
| Chris@540 | 445     if (d.exists()) { | 
| Chris@540 | 446         d.setFilter(QDir::Files | QDir::NoDotAndDotDot | | 
| Chris@540 | 447                     QDir::Readable | QDir::NoSymLinks); | 
| Chris@540 | 448         foreach (QString entry, d.entryList()) { | 
| Chris@540 | 449             if (entry.startsWith('.')) continue; | 
| Chris@540 | 450             if (shouldIgnore(entry)) continue; | 
| Chris@540 | 451             files.insert(entry); | 
| Chris@540 | 452         } | 
| Chris@540 | 453     } | 
| Chris@540 | 454     return files; | 
| Chris@540 | 455 } | 
| Chris@540 | 456 | 
| Chris@540 | 457 void | 
| Chris@540 | 458 FsWatcher::debugPrint() | 
| Chris@540 | 459 { | 
| Chris@540 | 460 #ifdef DEBUG_FSWATCHER | 
| Chris@585 | 461 #ifndef Q_OS_MAC | 
| Chris@540 | 462     std::cerr << "FsWatcher: Now watching " << m_watcher.directories().size() | 
| Chris@540 | 463               << " directories and " << m_watcher.files().size() | 
| Chris@540 | 464               << " files" << std::endl; | 
| Chris@540 | 465 #endif | 
| Chris@585 | 466 #endif | 
| Chris@540 | 467 } |