void WatcherData::notifyCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) { (void)eventIds; WatcherData* watcher = static_cast<WatcherData*>(clientCallBackInfo); std::lock_guard<std::mutex> locker(watcher->mutex); watcher->since = FSEventStreamGetLatestEventId(streamRef); char** paths = reinterpret_cast<char**>(eventPaths); FileSystemWatcher *fsWatcher = watcher->watcher; { std::lock_guard<std::mutex> l(fsWatcher->mMutex); for (size_t i = 0; i < numEvents; ++i) { const FSEventStreamEventFlags flags = eventFlags[i]; if (flags & kFSEventStreamEventFlagHistoryDone) continue; if (flags & kFSEventStreamEventFlagItemIsFile) { const Path path(paths[i]); if (flags & kFSEventStreamEventFlagItemCreated) { fsWatcher->add(FileSystemWatcher::Add, path); } if (flags & kFSEventStreamEventFlagItemRemoved) { fsWatcher->add(FileSystemWatcher::Remove, path); } if (flags & kFSEventStreamEventFlagItemRenamed) { if (path.isFile()) { fsWatcher->add(FileSystemWatcher::Add, path); } else { fsWatcher->add(FileSystemWatcher::Remove, path); } } if (flags & (kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemInodeMetaMod)) { fsWatcher->add(FileSystemWatcher::Modified, path); } } } } std::weak_ptr<WatcherData> that = watcher->shared_from_this(); EventLoop::eventLoop()->callLater([that] { if (std::shared_ptr<WatcherData> watcherData = that.lock()) { watcherData->watcher->processChanges(); } }); }
void WatcherThread::notifyCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) { WatcherThread* watcher = static_cast<WatcherThread*>(clientCallBackInfo); MutexLocker locker(&watcher->mutex); watcher->since = FSEventStreamGetLatestEventId(streamRef); char** paths = reinterpret_cast<char**>(eventPaths); Set<Path> created, removed, modified; for (size_t i = 0; i < numEvents; ++i) { const FSEventStreamEventFlags flags = eventFlags[i]; if (flags & kFSEventStreamEventFlagHistoryDone) continue; if (flags & kFSEventStreamEventFlagItemIsFile) { if (flags & kFSEventStreamEventFlagItemCreated) { created.insert(Path(paths[i])); } else if (flags & kFSEventStreamEventFlagItemRemoved) { removed.insert(Path(paths[i])); } else if (flags & (kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemInodeMetaMod)) { modified.insert(Path(paths[i])); } } } if (!created.empty()) watcher->receiver->postEvent(new WatcherEvent(WatcherEvent::Created, created)); if (!removed.empty()) watcher->receiver->postEvent(new WatcherEvent(WatcherEvent::Removed, removed)); if (!modified.empty()) { watcher->receiver->postEvent(new WatcherEvent(WatcherEvent::Modified, modified)); } }
QStringList QFSEventsFileSystemWatcherEngine::removePaths(const QStringList &paths, QStringList *files, QStringList *directories) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 stop(); wait(); QMutexLocker locker(&mutex); // short circuit for smarties that call remove before add and we have nothing. if (pathsToWatch == 0) return paths; QStringList failedToRemove; // if we have a running FSStreamEvent, we have to stop it, we'll re-add the stream soon. FSEventStreamEventId idToCheck; if (fsStream) { idToCheck = FSEventStreamGetLatestEventId(fsStream); cleanupFSStream(fsStream); fsStream = 0; } else { idToCheck = kFSEventStreamEventIdSinceNow; } CFIndex itemCount = CFArrayGetCount(pathsToWatch); QCFType<CFMutableArrayRef> tmpArray = CFArrayCreateMutableCopy(kCFAllocatorDefault, itemCount, pathsToWatch); CFRelease(pathsToWatch); pathsToWatch = 0; for (int i = 0; i < paths.size(); ++i) { // Get the itemCount at the beginning to avoid any overruns during the iteration. itemCount = CFArrayGetCount(tmpArray); const QString &path = paths.at(i); QFileInfo fi(path); QCFString cfpath(createFSStreamPath(fi.canonicalPath())); CFIndex index = CFArrayGetFirstIndexOfValue(tmpArray, CFRangeMake(0, itemCount), cfpath); if (index != -1) { CFArrayRemoveValueAtIndex(tmpArray, index); files->removeAll(path); removePathFromHash(filePathInfoHash, cfpath, path); } else { // Could be a directory we are watching instead. QCFString cfdirpath(createFSStreamPath(fi.canonicalFilePath())); index = CFArrayGetFirstIndexOfValue(tmpArray, CFRangeMake(0, itemCount), cfdirpath); if (index != -1) { CFArrayRemoveValueAtIndex(tmpArray, index); directories->removeAll(path); removePathFromHash(dirPathInfoHash, cfpath, path); } else { failedToRemove.append(path); } } } itemCount = CFArrayGetCount(tmpArray); if (itemCount != 0) { pathsToWatch = CFArrayCreateCopy(kCFAllocatorDefault, tmpArray); FSEventStreamContext context = { 0, this, 0, 0, 0 }; fsStream = FSEventStreamCreate(kCFAllocatorDefault, QFSEventsFileSystemWatcherEngine::fseventsCallback, &context, pathsToWatch, idToCheck, Latency, QtFSEventFlags); warmUpFSEvents(); } return failedToRemove; #else Q_UNUSED(paths); Q_UNUSED(files); Q_UNUSED(directories); return QStringList(); #endif }
QStringList QFSEventsFileSystemWatcherEngine::addPaths(const QStringList &paths, QStringList *files, QStringList *directories) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 stop(); wait(); QMutexLocker locker(&mutex); QStringList failedToAdd; // if we have a running FSStreamEvent, we have to kill it, we'll re-add the stream soon. FSEventStreamEventId idToCheck; if (fsStream) { idToCheck = FSEventStreamGetLatestEventId(fsStream); cleanupFSStream(fsStream); } else { idToCheck = kFSEventStreamEventIdSinceNow; } // Brain-dead approach, but works. FSEvents actually can already read sub-trees, but since it's // work to figure out if we are doing a double register, we just register it twice as FSEvents // seems smart enough to only deliver one event. We also duplicate directory entries in here // (e.g., if you watch five files in the same directory, you get that directory included in the // array 5 times). This stupidity also makes remove work correctly though. I'll freely admit // that we could make this a bit smarter. If you do, check the auto-tests, they should catch at // least a couple of the issues. QCFType<CFMutableArrayRef> tmpArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); for (int i = 0; i < paths.size(); ++i) { const QString &path = paths.at(i); QFileInfo fileInfo(path); if (!fileInfo.exists()) { failedToAdd.append(path); continue; } if (fileInfo.isDir()) { if (directories->contains(path)) { failedToAdd.append(path); continue; } else { directories->append(path); // Full file path for dirs. QCFString cfpath(createFSStreamPath(fileInfo.canonicalFilePath())); addPathToHash(dirPathInfoHash, cfpath, fileInfo, path); CFArrayAppendValue(tmpArray, cfpath); } } else { if (files->contains(path)) { failedToAdd.append(path); continue; } else { // Just the absolute path (minus it's filename) for files. QCFString cfpath(createFSStreamPath(fileInfo.canonicalPath())); files->append(path); addPathToHash(filePathInfoHash, cfpath, fileInfo, path); CFArrayAppendValue(tmpArray, cfpath); } } } if (!pathsToWatch && failedToAdd.size() == paths.size()) { return failedToAdd; } if (CFArrayGetCount(tmpArray) > 0) { if (pathsToWatch) { CFArrayAppendArray(tmpArray, pathsToWatch, CFRangeMake(0, CFArrayGetCount(pathsToWatch))); CFRelease(pathsToWatch); } pathsToWatch = CFArrayCreateCopy(kCFAllocatorDefault, tmpArray); } FSEventStreamContext context = { 0, this, 0, 0, 0 }; fsStream = FSEventStreamCreate(kCFAllocatorDefault, QFSEventsFileSystemWatcherEngine::fseventsCallback, &context, pathsToWatch, idToCheck, Latency, QtFSEventFlags); warmUpFSEvents(); return failedToAdd; #else Q_UNUSED(paths); Q_UNUSED(files); Q_UNUSED(directories); return QStringList(); #endif }