5#include <qplatformdefs.h>
10#include "private/qcore_unix_p.h"
11#include "kernel/qcore_mac_p.h"
17#include <qvarlengtharray.h>
18#include <qscopeguard.h>
22# define DEBUG if (true) qDebug
24# define DEBUG if (false) qDebug
30 void *clientCallBackInfo,
33 const FSEventStreamEventFlags eventFlags[],
34 const FSEventStreamEventId eventIds[])
38 char **paths =
static_cast<
char **>(eventPaths);
39 QFseventsFileSystemWatcherEngine *engine =
static_cast<QFseventsFileSystemWatcherEngine *>(clientCallBackInfo);
40 engine->processEvent(streamRef, numEvents, paths, eventFlags, eventIds);
43bool QFseventsFileSystemWatcherEngine::checkDir(DirsByName::iterator &it)
45 bool needsRestart =
false;
48 const QString &name = it.key();
49 Info &info = it->dirInfo;
50 const int res = QT_STAT(QFile::encodeName(name), &st);
52 needsRestart |= derefPath(info.watchedPath);
53 emit emitDirectoryChanged(info.origPath,
true);
54 it = watchingState.watchedDirectories.erase(it);
55 }
else if (st.st_ctimespec != info.ctime || st.st_mode != info.mode) {
56 info.ctime = st.st_ctimespec;
57 info.mode = st.st_mode;
58 emit emitDirectoryChanged(info.origPath,
false);
61 bool dirChanged =
false;
62 InfoByName &entries = it->entries;
64 for (InfoByName::iterator i = entries.begin(); i != entries.end(); ) {
65 if (QT_STAT(QFile::encodeName(i.key()), &st) == -1) {
70 if (i->ctime != st.st_ctimespec || i->mode != st.st_mode) {
73 i->ctime = st.st_ctimespec;
83 QDirListing::IteratorFlags flags = QDirListing::IteratorFlag::IncludeHidden;
86 QDirListing listing(name, flags);
87 for (
auto dirIt : listing) {
88 QString entryName = dirIt.filePath();
89 if (!entries.contains(entryName)) {
92 if (QT_STAT(QFile::encodeName(entryName), &st) == -1)
94 entries.insert(entryName, Info(QString(), st.st_ctimespec, st.st_mode, QString()));
99 emit emitDirectoryChanged(info.origPath,
false);
106bool QFseventsFileSystemWatcherEngine::rescanDirs(
const QString &path)
108 bool needsRestart =
false;
110 for (DirsByName::iterator it = watchingState.watchedDirectories.begin();
111 it != watchingState.watchedDirectories.end(); ) {
112 if (it.key().startsWith(path))
113 needsRestart |= checkDir(it);
121bool QFseventsFileSystemWatcherEngine::rescanFiles(InfoByName &filesInPath)
123 bool needsRestart =
false;
125 for (InfoByName::iterator it = filesInPath.begin(); it != filesInPath.end(); ) {
127 QString name = it.key();
128 const int res = QT_STAT(QFile::encodeName(name), &st);
130 needsRestart |= derefPath(it->watchedPath);
131 emit emitFileChanged(it.value().origPath,
true);
132 it = filesInPath.erase(it);
134 }
else if (st.st_ctimespec != it->ctime || st.st_mode != it->mode) {
135 it->ctime = st.st_ctimespec;
136 it->mode = st.st_mode;
137 emit emitFileChanged(it.value().origPath,
false);
146bool QFseventsFileSystemWatcherEngine::rescanFiles(
const QString &path)
148 bool needsRestart =
false;
150 for (FilesByPath::iterator i = watchingState.watchedFiles.begin();
151 i != watchingState.watchedFiles.end(); ) {
152 if (i.key().startsWith(path)) {
153 needsRestart |= rescanFiles(i.value());
154 if (i.value().isEmpty()) {
155 i = watchingState.watchedFiles.erase(i);
166void QFseventsFileSystemWatcherEngine::processEvent(ConstFSEventStreamRef streamRef,
169 const FSEventStreamEventFlags eventFlags[],
170 const FSEventStreamEventId eventIds[])
172#if defined(Q_OS_MACOS)
175 bool needsRestart =
false;
177 QMutexLocker locker(&lock);
179 for (size_t i = 0; i < numEvents; ++i) {
180 FSEventStreamEventFlags eFlags = eventFlags[i];
181 DEBUG(
"Change %llu in %s, flags %x", eventIds[i], eventPaths[i], (
unsigned int)eFlags);
183 if (eFlags & kFSEventStreamEventFlagEventIdsWrapped) {
184 DEBUG(
"\tthe event ids wrapped");
185 lastReceivedEvent = 0;
187 lastReceivedEvent = qMax(lastReceivedEvent, eventIds[i]);
189 QString path = QFile::decodeName(eventPaths[i]);
190 if (path.size() > 1 && path.endsWith(QDir::separator()))
193 if (eFlags & kFSEventStreamEventFlagMustScanSubDirs) {
194 DEBUG(
"\tmust rescan directory because of coalesced events");
195 if (eFlags & kFSEventStreamEventFlagUserDropped)
196 DEBUG(
"\t\t... user dropped.");
197 if (eFlags & kFSEventStreamEventFlagKernelDropped)
198 DEBUG(
"\t\t... kernel dropped.");
199 needsRestart |= rescanDirs(path);
200 needsRestart |= rescanFiles(path);
204 if (eFlags & kFSEventStreamEventFlagRootChanged) {
206 DirsByName::iterator dirIt = watchingState.watchedDirectories.find(path);
207 if (dirIt != watchingState.watchedDirectories.end())
208 needsRestart |= checkDir(dirIt);
209 needsRestart |= rescanFiles(path);
213 if ((eFlags & kFSEventStreamEventFlagItemIsDir) && (eFlags & kFSEventStreamEventFlagItemRemoved))
214 needsRestart |= rescanDirs(path);
217 DirsByName::iterator dirIt = watchingState.watchedDirectories.find(path);
218 if (dirIt != watchingState.watchedDirectories.end())
219 needsRestart |= checkDir(dirIt);
222 FilesByPath::iterator pIt = watchingState.watchedFiles.find(path);
223 if (pIt != watchingState.watchedFiles.end())
224 needsRestart |= rescanFiles(pIt.value());
228 emit scheduleStreamRestart();
232 Q_UNUSED(eventPaths);
233 Q_UNUSED(eventFlags);
238void QFseventsFileSystemWatcherEngine::doEmitFileChanged(
const QString &path,
bool removed)
240 DEBUG() <<
"emitting fileChanged for" << path <<
"with removed =" << removed;
241 emit fileChanged(path, removed);
244void QFseventsFileSystemWatcherEngine::doEmitDirectoryChanged(
const QString &path,
bool removed)
246 DEBUG() <<
"emitting directoryChanged for" << path <<
"with removed =" << removed;
247 emit directoryChanged(path, removed);
250bool QFseventsFileSystemWatcherEngine::restartStream()
252 QMutexLocker locker(&lock);
254 return startStream();
257QFseventsFileSystemWatcherEngine *QFseventsFileSystemWatcherEngine::create(QObject *parent)
259 return new QFseventsFileSystemWatcherEngine(parent);
262QFseventsFileSystemWatcherEngine::QFseventsFileSystemWatcherEngine(QObject *parent)
263 : QFileSystemWatcherEngine(parent)
265 , lastReceivedEvent(kFSEventStreamEventIdSinceNow)
270 connect(
this, SIGNAL(emitDirectoryChanged(QString,
bool)),
271 this, SLOT(doEmitDirectoryChanged(QString,
bool)), Qt::QueuedConnection);
272 connect(
this, SIGNAL(emitFileChanged(QString,
bool)),
273 this, SLOT(doEmitFileChanged(QString,
bool)), Qt::QueuedConnection);
274 connect(
this, SIGNAL(scheduleStreamRestart()),
275 this, SLOT(restartStream()), Qt::QueuedConnection);
277 queue = dispatch_queue_create(
"org.qt-project.QFseventsFileSystemWatcherEngine", NULL);
280QFseventsFileSystemWatcherEngine::~QFseventsFileSystemWatcherEngine()
284 dispatch_sync(queue, ^{
287 FSEventStreamStop(stream);
290 QMutexLocker locker(&lock);
294 dispatch_release(queue);
297QStringList QFseventsFileSystemWatcherEngine::addPaths(
const QStringList &paths,
299 QStringList *directories)
304 DEBUG(
"Flushing, last id is %llu", FSEventStreamGetLatestEventId(stream));
305 FSEventStreamFlushSync(stream);
308 QMutexLocker locker(&lock);
310 bool wasRunning = stream !=
nullptr;
311 bool needsRestart =
false;
313 WatchingState oldState = watchingState;
314 QStringList unhandled;
315 for (
const QString &path : paths) {
316 auto sg = qScopeGuard([&]{ unhandled.push_back(path); });
317 QString origPath = path.normalized(QString::NormalizationForm_C);
318 QString realPath = origPath;
319 if (realPath.size() > 1 && realPath.endsWith(QDir::separator()))
321 QString watchedPath, parentPath;
323 realPath = QFileInfo(realPath).canonicalFilePath();
324 QFileInfo fi(realPath);
325 if (realPath.isEmpty())
329 if (QT_STAT(QFile::encodeName(realPath), &st) == -1)
332 const bool isDir = S_ISDIR(st.st_mode);
334 if (watchingState.watchedDirectories.contains(realPath))
336 directories->append(origPath);
337 watchedPath = realPath;
339 if (files->contains(origPath))
341 files->append(origPath);
343 watchedPath = fi.path();
344 parentPath = watchedPath;
349 for (PathRefCounts::const_iterator i = watchingState.watchedPaths.begin(),
350 ei = watchingState.watchedPaths.end(); i != ei; ++i) {
351 if (watchedPath.startsWith(i.key() % QDir::separator())) {
352 watchedPath = i.key();
357 PathRefCounts::iterator it = watchingState.watchedPaths.find(watchedPath);
358 if (it == watchingState.watchedPaths.end()) {
360 watchingState.watchedPaths.insert(watchedPath, 1);
366 Info info(origPath, st.st_ctimespec, st.st_mode, watchedPath);
369 dirInfo.dirInfo = info;
370 dirInfo.entries = scanForDirEntries(realPath);
371 watchingState.watchedDirectories.insert(realPath, dirInfo);
372 DEBUG(
"-- Also adding '%s' to watchedDirectories",
qPrintable(realPath));
374 watchingState.watchedFiles[parentPath].insert(realPath, info);
381 if (!startStream()) {
383 watchingState =
std::move(oldState);
395QStringList QFseventsFileSystemWatcherEngine::removePaths(
const QStringList &paths,
397 QStringList *directories)
401 QMutexLocker locker(&lock);
403 bool needsRestart =
false;
405 WatchingState oldState = watchingState;
406 QStringList unhandled;
407 for (
const QString &origPath : paths) {
408 auto sg = qScopeGuard([&]{ unhandled.push_back(origPath); });
409 QString realPath = origPath;
410 if (realPath.size() > 1 && realPath.endsWith(QDir::separator()))
413 QFileInfo fi(realPath);
414 realPath = fi.canonicalFilePath();
417 DirsByName::iterator dirIt = watchingState.watchedDirectories.find(realPath);
418 if (dirIt != watchingState.watchedDirectories.end()) {
419 needsRestart |= derefPath(dirIt->dirInfo.watchedPath);
420 watchingState.watchedDirectories.erase(dirIt);
421 directories->removeAll(origPath);
426 QFileInfo fi(realPath);
427 QString parentPath = fi.path();
428 FilesByPath::iterator pIt = watchingState.watchedFiles.find(parentPath);
429 if (pIt != watchingState.watchedFiles.end()) {
430 InfoByName &filesInDir = pIt.value();
431 InfoByName::iterator fIt = filesInDir.find(realPath);
432 if (fIt != filesInDir.end()) {
433 needsRestart |= derefPath(fIt->watchedPath);
434 filesInDir.erase(fIt);
435 if (filesInDir.isEmpty())
436 watchingState.watchedFiles.erase(pIt);
437 files->removeAll(origPath);
448 if (!restartStream()) {
449 watchingState =
std::move(oldState);
459bool QFseventsFileSystemWatcherEngine::startStream()
461 Q_ASSERT(stream == 0);
467 if (watchingState.watchedPaths.isEmpty())
470 DEBUG() <<
"Starting stream with paths" << watchingState.watchedPaths.keys();
472 NSMutableArray<NSString *> *pathsToWatch = [NSMutableArray<NSString *> arrayWithCapacity:watchingState.watchedPaths.size()];
473 for (PathRefCounts::const_iterator i = watchingState.watchedPaths.begin(), ei = watchingState.watchedPaths.end(); i != ei; ++i)
474 [pathsToWatch addObject:i.key().toNSString()];
476 struct FSEventStreamContext callBackInfo = {
483 const CFAbsoluteTime latency = .5;
487 if (lastReceivedEvent == kFSEventStreamEventIdSinceNow)
488 lastReceivedEvent = FSEventsGetCurrentEventId();
489 stream = FSEventStreamCreate(NULL,
492 reinterpret_cast<CFArrayRef>(pathsToWatch),
495 FSEventStreamCreateFlags(0));
498 DEBUG() <<
"Failed to create stream!";
502 FSEventStreamSetDispatchQueue(stream, queue);
504 if (FSEventStreamStart(stream)) {
505 DEBUG() <<
"Stream started successfully with sinceWhen =" << lastReceivedEvent;
508 DEBUG() <<
"Stream failed to start!";
509 FSEventStreamInvalidate(stream);
510 FSEventStreamRelease(stream);
516void QFseventsFileSystemWatcherEngine::stopStream(
bool isStopped)
521 FSEventStreamStop(stream);
522 FSEventStreamInvalidate(stream);
523 FSEventStreamRelease(stream);
525 DEBUG() <<
"Stream stopped. Last event ID:" << lastReceivedEvent;
529QFseventsFileSystemWatcherEngine::InfoByName QFseventsFileSystemWatcherEngine::scanForDirEntries(
const QString &path)
533 QDirIterator it(path);
534 while (it.hasNext()) {
536 QString entryName = it.filePath();
538 if (QT_STAT(QFile::encodeName(entryName), &st) == -1)
540 entries.insert(entryName, Info(QString(), st.st_ctimespec, st.st_mode, QString()));
546bool QFseventsFileSystemWatcherEngine::derefPath(
const QString &watchedPath)
548 PathRefCounts::iterator it = watchingState.watchedPaths.find(watchedPath);
549 if (it != watchingState.watchedPaths.end() && --it.value() < 1) {
550 watchingState.watchedPaths.erase(it);
const QString & asString(const QString &s)
static QT_BEGIN_NAMESPACE void callBackFunction(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
#define qPrintable(string)