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 const QFseventsFileSystemWatcherEngine::Info &info)
32 const timespec ts = statbuff.st_ctimespec;
33 return (ts.tv_sec == info.ctime.tv_sec && ts.tv_nsec == info.ctime.tv_nsec)
34 && statbuff.st_mode == info.mode;
38 void *clientCallBackInfo,
41 const FSEventStreamEventFlags eventFlags[],
42 const FSEventStreamEventId eventIds[])
46 char **paths =
static_cast<
char **>(eventPaths);
47 QFseventsFileSystemWatcherEngine *engine =
static_cast<QFseventsFileSystemWatcherEngine *>(clientCallBackInfo);
48 engine->processEvent(streamRef, numEvents, paths, eventFlags, eventIds);
51bool QFseventsFileSystemWatcherEngine::checkDir(DirsByName::iterator &it)
53 bool needsRestart =
false;
56 const QString &name = it.key();
57 Info &info = it->dirInfo;
58 const int res = QT_STAT(QFile::encodeName(name), &st);
60 needsRestart |= derefPath(info.watchedPath);
61 emit emitDirectoryChanged(info.origPath,
true);
62 it = watchingState.watchedDirectories.erase(it);
63 }
else if (!isSameTimestampAndMode(st, info)) {
64 info.ctime = st.st_ctimespec;
65 info.mode = st.st_mode;
66 emit emitDirectoryChanged(info.origPath,
false);
69 bool dirChanged =
false;
70 InfoByName &entries = it->entries;
72 for (InfoByName::iterator i = entries.begin(); i != entries.end(); ) {
73 if (QT_STAT(QFile::encodeName(i.key()), &st) == -1) {
78 if (!isSameTimestampAndMode(st, i.value())) {
81 i->ctime = st.st_ctimespec;
91 QDirListing::IteratorFlags flags = QDirListing::IteratorFlag::IncludeHidden;
94 QDirListing listing(name, flags);
95 for (
auto dirIt : listing) {
96 QString entryName = dirIt.filePath();
97 if (!entries.contains(entryName)) {
100 if (QT_STAT(QFile::encodeName(entryName), &st) == -1)
102 entries.insert(entryName, Info(QString(), st.st_ctimespec, st.st_mode, QString()));
107 emit emitDirectoryChanged(info.origPath,
false);
114bool QFseventsFileSystemWatcherEngine::rescanDirs(
const QString &path)
116 bool needsRestart =
false;
118 for (DirsByName::iterator it = watchingState.watchedDirectories.begin();
119 it != watchingState.watchedDirectories.end(); ) {
120 if (it.key().startsWith(path))
121 needsRestart |= checkDir(it);
129bool QFseventsFileSystemWatcherEngine::rescanFiles(InfoByName &filesInPath)
131 bool needsRestart =
false;
133 for (InfoByName::iterator it = filesInPath.begin(); it != filesInPath.end(); ) {
135 QString name = it.key();
136 const int res = QT_STAT(QFile::encodeName(name), &st);
138 needsRestart |= derefPath(it->watchedPath);
139 emit emitFileChanged(it.value().origPath,
true);
140 it = filesInPath.erase(it);
142 }
else if (!isSameTimestampAndMode(st, it.value())) {
143 it->ctime = st.st_ctimespec;
144 it->mode = st.st_mode;
145 emit emitFileChanged(it.value().origPath,
false);
154bool QFseventsFileSystemWatcherEngine::rescanFiles(
const QString &path)
156 bool needsRestart =
false;
158 for (FilesByPath::iterator i = watchingState.watchedFiles.begin();
159 i != watchingState.watchedFiles.end(); ) {
160 if (i.key().startsWith(path)) {
161 needsRestart |= rescanFiles(i.value());
162 if (i.value().isEmpty()) {
163 i = watchingState.watchedFiles.erase(i);
174void QFseventsFileSystemWatcherEngine::processEvent(ConstFSEventStreamRef streamRef,
177 const FSEventStreamEventFlags eventFlags[],
178 const FSEventStreamEventId eventIds[])
180#if defined(Q_OS_MACOS)
183 bool needsRestart =
false;
185 QMutexLocker locker(&lock);
187 for (size_t i = 0; i < numEvents; ++i) {
188 FSEventStreamEventFlags eFlags = eventFlags[i];
189 DEBUG(
"Change %llu in %s, flags %x", eventIds[i], eventPaths[i], (
unsigned int)eFlags);
191 if (eFlags & kFSEventStreamEventFlagEventIdsWrapped) {
192 DEBUG(
"\tthe event ids wrapped");
193 lastReceivedEvent = 0;
195 lastReceivedEvent = qMax(lastReceivedEvent, eventIds[i]);
197 QString path = QFile::decodeName(eventPaths[i]);
198 if (path.size() > 1 && path.endsWith(QDir::separator()))
201 if (eFlags & kFSEventStreamEventFlagMustScanSubDirs) {
202 DEBUG(
"\tmust rescan directory because of coalesced events");
203 if (eFlags & kFSEventStreamEventFlagUserDropped)
204 DEBUG(
"\t\t... user dropped.");
205 if (eFlags & kFSEventStreamEventFlagKernelDropped)
206 DEBUG(
"\t\t... kernel dropped.");
207 needsRestart |= rescanDirs(path);
208 needsRestart |= rescanFiles(path);
212 if (eFlags & kFSEventStreamEventFlagRootChanged) {
214 DirsByName::iterator dirIt = watchingState.watchedDirectories.find(path);
215 if (dirIt != watchingState.watchedDirectories.end())
216 needsRestart |= checkDir(dirIt);
217 needsRestart |= rescanFiles(path);
221 if ((eFlags & kFSEventStreamEventFlagItemIsDir) && (eFlags & kFSEventStreamEventFlagItemRemoved))
222 needsRestart |= rescanDirs(path);
225 DirsByName::iterator dirIt = watchingState.watchedDirectories.find(path);
226 if (dirIt != watchingState.watchedDirectories.end())
227 needsRestart |= checkDir(dirIt);
230 FilesByPath::iterator pIt = watchingState.watchedFiles.find(path);
231 if (pIt != watchingState.watchedFiles.end())
232 needsRestart |= rescanFiles(pIt.value());
236 emit scheduleStreamRestart();
240 Q_UNUSED(eventPaths);
241 Q_UNUSED(eventFlags);
246void QFseventsFileSystemWatcherEngine::doEmitFileChanged(
const QString &path,
bool removed)
248 DEBUG() <<
"emitting fileChanged for" << path <<
"with removed =" << removed;
249 emit fileChanged(path, removed);
252void QFseventsFileSystemWatcherEngine::doEmitDirectoryChanged(
const QString &path,
bool removed)
254 DEBUG() <<
"emitting directoryChanged for" << path <<
"with removed =" << removed;
255 emit directoryChanged(path, removed);
258bool QFseventsFileSystemWatcherEngine::restartStream()
260 QMutexLocker locker(&lock);
262 return startStream();
265QFseventsFileSystemWatcherEngine *QFseventsFileSystemWatcherEngine::create(QObject *parent)
267 return new QFseventsFileSystemWatcherEngine(parent);
270QFseventsFileSystemWatcherEngine::QFseventsFileSystemWatcherEngine(QObject *parent)
271 : QFileSystemWatcherEngine(parent)
273 , lastReceivedEvent(kFSEventStreamEventIdSinceNow)
278 connect(
this, SIGNAL(emitDirectoryChanged(QString,
bool)),
279 this, SLOT(doEmitDirectoryChanged(QString,
bool)), Qt::QueuedConnection);
280 connect(
this, SIGNAL(emitFileChanged(QString,
bool)),
281 this, SLOT(doEmitFileChanged(QString,
bool)), Qt::QueuedConnection);
282 connect(
this, SIGNAL(scheduleStreamRestart()),
283 this, SLOT(restartStream()), Qt::QueuedConnection);
285 queue = dispatch_queue_create(
"org.qt-project.QFseventsFileSystemWatcherEngine", NULL);
288QFseventsFileSystemWatcherEngine::~QFseventsFileSystemWatcherEngine()
292 dispatch_sync(queue, ^{
295 FSEventStreamStop(stream);
298 QMutexLocker locker(&lock);
302 dispatch_release(queue);
305QStringList QFseventsFileSystemWatcherEngine::addPaths(
const QStringList &paths,
307 QStringList *directories)
312 DEBUG(
"Flushing, last id is %llu", FSEventStreamGetLatestEventId(stream));
313 FSEventStreamFlushSync(stream);
316 QMutexLocker locker(&lock);
318 bool wasRunning = stream !=
nullptr;
319 bool needsRestart =
false;
321 WatchingState oldState = watchingState;
322 QStringList unhandled;
323 for (
const QString &path : paths) {
324 auto sg = qScopeGuard([&]{ unhandled.push_back(path); });
325 QString origPath = path.normalized(QString::NormalizationForm_C);
326 QString realPath = origPath;
327 if (realPath.size() > 1 && realPath.endsWith(QDir::separator()))
329 QString watchedPath, parentPath;
331 realPath = QFileInfo(realPath).canonicalFilePath();
332 QFileInfo fi(realPath);
333 if (realPath.isEmpty())
337 if (QT_STAT(QFile::encodeName(realPath), &st) == -1)
340 const bool isDir = S_ISDIR(st.st_mode);
342 if (watchingState.watchedDirectories.contains(realPath))
344 directories->append(origPath);
345 watchedPath = realPath;
347 if (files->contains(origPath))
349 files->append(origPath);
351 watchedPath = fi.path();
352 parentPath = watchedPath;
357 for (PathRefCounts::const_iterator i = watchingState.watchedPaths.begin(),
358 ei = watchingState.watchedPaths.end(); i != ei; ++i) {
359 if (watchedPath.startsWith(i.key() % QDir::separator())) {
360 watchedPath = i.key();
365 PathRefCounts::iterator it = watchingState.watchedPaths.find(watchedPath);
366 if (it == watchingState.watchedPaths.end()) {
368 watchingState.watchedPaths.insert(watchedPath, 1);
374 Info info(origPath, st.st_ctimespec, st.st_mode, watchedPath);
377 dirInfo.dirInfo = info;
378 dirInfo.entries = scanForDirEntries(realPath);
379 watchingState.watchedDirectories.insert(realPath, dirInfo);
380 DEBUG(
"-- Also adding '%s' to watchedDirectories",
qPrintable(realPath));
382 watchingState.watchedFiles[parentPath].insert(realPath, info);
389 if (!startStream()) {
391 watchingState =
std::move(oldState);
403QStringList QFseventsFileSystemWatcherEngine::removePaths(
const QStringList &paths,
405 QStringList *directories)
409 QMutexLocker locker(&lock);
411 bool needsRestart =
false;
413 WatchingState oldState = watchingState;
414 QStringList unhandled;
415 for (
const QString &origPath : paths) {
416 auto sg = qScopeGuard([&]{ unhandled.push_back(origPath); });
417 QString realPath = origPath;
418 if (realPath.size() > 1 && realPath.endsWith(QDir::separator()))
421 QFileInfo fi(realPath);
422 realPath = fi.canonicalFilePath();
425 DirsByName::iterator dirIt = watchingState.watchedDirectories.find(realPath);
426 if (dirIt != watchingState.watchedDirectories.end()) {
427 needsRestart |= derefPath(dirIt->dirInfo.watchedPath);
428 watchingState.watchedDirectories.erase(dirIt);
429 directories->removeAll(origPath);
434 QFileInfo fi(realPath);
435 QString parentPath = fi.path();
436 FilesByPath::iterator pIt = watchingState.watchedFiles.find(parentPath);
437 if (pIt != watchingState.watchedFiles.end()) {
438 InfoByName &filesInDir = pIt.value();
439 InfoByName::iterator fIt = filesInDir.find(realPath);
440 if (fIt != filesInDir.end()) {
441 needsRestart |= derefPath(fIt->watchedPath);
442 filesInDir.erase(fIt);
443 if (filesInDir.isEmpty())
444 watchingState.watchedFiles.erase(pIt);
445 files->removeAll(origPath);
456 if (!restartStream()) {
457 watchingState =
std::move(oldState);
467bool QFseventsFileSystemWatcherEngine::startStream()
469 Q_ASSERT(stream == 0);
475 if (watchingState.watchedPaths.isEmpty())
478 DEBUG() <<
"Starting stream with paths" << watchingState.watchedPaths.keys();
480 NSMutableArray<NSString *> *pathsToWatch = [NSMutableArray<NSString *> arrayWithCapacity:watchingState.watchedPaths.size()];
481 for (PathRefCounts::const_iterator i = watchingState.watchedPaths.begin(), ei = watchingState.watchedPaths.end(); i != ei; ++i)
482 [pathsToWatch addObject:i.key().toNSString()];
484 struct FSEventStreamContext callBackInfo = {
491 const CFAbsoluteTime latency = .5;
495 if (lastReceivedEvent == kFSEventStreamEventIdSinceNow)
496 lastReceivedEvent = FSEventsGetCurrentEventId();
497 stream = FSEventStreamCreate(NULL,
500 reinterpret_cast<CFArrayRef>(pathsToWatch),
503 FSEventStreamCreateFlags(0));
506 DEBUG() <<
"Failed to create stream!";
510 FSEventStreamSetDispatchQueue(stream, queue);
512 if (FSEventStreamStart(stream)) {
513 DEBUG() <<
"Stream started successfully with sinceWhen =" << lastReceivedEvent;
516 DEBUG() <<
"Stream failed to start!";
517 FSEventStreamInvalidate(stream);
518 FSEventStreamRelease(stream);
524void QFseventsFileSystemWatcherEngine::stopStream(
bool isStopped)
529 FSEventStreamStop(stream);
530 FSEventStreamInvalidate(stream);
531 FSEventStreamRelease(stream);
533 DEBUG() <<
"Stream stopped. Last event ID:" << lastReceivedEvent;
537QFseventsFileSystemWatcherEngine::InfoByName QFseventsFileSystemWatcherEngine::scanForDirEntries(
const QString &path)
541 QDirIterator it(path);
542 while (it.hasNext()) {
544 QString entryName = it.filePath();
546 if (QT_STAT(QFile::encodeName(entryName), &st) == -1)
548 entries.insert(entryName, Info(QString(), st.st_ctimespec, st.st_mode, QString()));
554bool QFseventsFileSystemWatcherEngine::derefPath(
const QString &watchedPath)
556 PathRefCounts::iterator it = watchingState.watchedPaths.find(watchedPath);
557 if (it != watchingState.watchedPaths.end() && --it.value() < 1) {
558 watchingState.watchedPaths.erase(it);
Combined button and popup list for selecting options.
const QString & asString(const QString &s)
static void callBackFunction(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
static QT_BEGIN_NAMESPACE bool isSameTimestampAndMode(const QT_STATBUF &statbuff, const QFseventsFileSystemWatcherEngine::Info &info)
#define qPrintable(string)