summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/io/qfilesystemwatcher_fsevents.mm134
-rw-r--r--src/corelib/io/qfilesystemwatcher_fsevents_p.h13
-rw-r--r--tests/auto/corelib/io/qfilesystemwatcher/tst_qfilesystemwatcher.cpp3
3 files changed, 108 insertions, 42 deletions
diff --git a/src/corelib/io/qfilesystemwatcher_fsevents.mm b/src/corelib/io/qfilesystemwatcher_fsevents.mm
index cb6ddd913e..bfcae2a3a2 100644
--- a/src/corelib/io/qfilesystemwatcher_fsevents.mm
+++ b/src/corelib/io/qfilesystemwatcher_fsevents.mm
@@ -55,7 +55,7 @@
#include <qfileinfo.h>
#include <qvarlengtharray.h>
-//#define FSEVENT_DEBUG
+#undef FSEVENT_DEBUG
#ifdef FSEVENT_DEBUG
# define DEBUG if (true) qDebug
#else
@@ -76,14 +76,16 @@ static void callBackFunction(ConstFSEventStreamRef streamRef,
engine->processEvent(streamRef, numEvents, paths, eventFlags, eventIds);
}
-void QFseventsFileSystemWatcherEngine::checkDir(DirsByName::iterator &it)
+bool QFseventsFileSystemWatcherEngine::checkDir(DirsByName::iterator &it)
{
+ bool needsRestart = false;
+
QT_STATBUF st;
const QString &name = it.key();
Info &info = it->dirInfo;
const int res = QT_STAT(QFile::encodeName(name), &st);
if (res == -1) {
- derefPath(info.watchedPath);
+ needsRestart |= derefPath(info.watchedPath);
emit emitDirectoryChanged(info.origPath, true);
it = watchedDirectories.erase(it);
} else if (st.st_ctimespec != info.ctime || st.st_mode != info.mode) {
@@ -127,26 +129,34 @@ void QFseventsFileSystemWatcherEngine::checkDir(DirsByName::iterator &it)
if (dirChanged)
emit emitDirectoryChanged(info.origPath, false);
}
+
+ return needsRestart;
}
-void QFseventsFileSystemWatcherEngine::rescanDirs(const QString &path)
+bool QFseventsFileSystemWatcherEngine::rescanDirs(const QString &path)
{
+ bool needsRestart = false;
+
for (DirsByName::iterator it = watchedDirectories.begin(); it != watchedDirectories.end(); ) {
if (it.key().startsWith(path))
- checkDir(it);
+ needsRestart |= checkDir(it);
else
++it;
}
+
+ return needsRestart;
}
-void QFseventsFileSystemWatcherEngine::rescanFiles(InfoByName &filesInPath)
+bool QFseventsFileSystemWatcherEngine::rescanFiles(InfoByName &filesInPath)
{
+ bool needsRestart = false;
+
for (InfoByName::iterator it = filesInPath.begin(); it != filesInPath.end(); ) {
QT_STATBUF st;
QString name = it.key();
const int res = QT_STAT(QFile::encodeName(name), &st);
if (res == -1) {
- derefPath(it->watchedPath);
+ needsRestart |= derefPath(it->watchedPath);
emit emitFileChanged(it.value().origPath, true);
it = filesInPath.erase(it);
continue;
@@ -158,13 +168,17 @@ void QFseventsFileSystemWatcherEngine::rescanFiles(InfoByName &filesInPath)
++it;
}
+
+ return needsRestart;
}
-void QFseventsFileSystemWatcherEngine::rescanFiles(const QString &path)
+bool QFseventsFileSystemWatcherEngine::rescanFiles(const QString &path)
{
+ bool needsRestart = false;
+
for (FilesByPath::iterator i = watchedFiles.begin(); i != watchedFiles.end(); ) {
if (i.key().startsWith(path)) {
- rescanFiles(i.value());
+ needsRestart |= rescanFiles(i.value());
if (i.value().isEmpty()) {
i = watchedFiles.erase(i);
continue;
@@ -173,6 +187,8 @@ void QFseventsFileSystemWatcherEngine::rescanFiles(const QString &path)
++i;
}
+
+ return needsRestart;
}
void QFseventsFileSystemWatcherEngine::processEvent(ConstFSEventStreamRef streamRef,
@@ -184,11 +200,20 @@ void QFseventsFileSystemWatcherEngine::processEvent(ConstFSEventStreamRef stream
#if defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_6
Q_UNUSED(streamRef);
+ bool needsRestart = false;
+
QMutexLocker locker(&lock);
for (size_t i = 0; i < numEvents; ++i) {
FSEventStreamEventFlags eFlags = eventFlags[i];
DEBUG("Change %llu in %s, flags %x", eventIds[i], eventPaths[i], (unsigned int)eFlags);
+
+ if (eFlags & kFSEventStreamEventFlagEventIdsWrapped) {
+ DEBUG("\tthe event ids wrapped");
+ lastReceivedEvent = 0;
+ }
+ lastReceivedEvent = qMax(lastReceivedEvent, eventIds[i]);
+
QString path = QFile::decodeName(eventPaths[i]);
if (path.endsWith(QDir::separator()))
path = path.mid(0, path.size() - 1);
@@ -199,38 +224,36 @@ void QFseventsFileSystemWatcherEngine::processEvent(ConstFSEventStreamRef stream
DEBUG("\t\t... user dropped.");
if (eFlags & kFSEventStreamEventFlagKernelDropped)
DEBUG("\t\t... kernel dropped.");
- rescanDirs(path);
- rescanFiles(path);
+ needsRestart |= rescanDirs(path);
+ needsRestart |= rescanFiles(path);
continue;
}
- if (eFlags & kFSEventStreamEventFlagEventIdsWrapped) {
- DEBUG("\tthe event ids wrapped");
- // TODO: verify if we need to do something
- }
-
if (eFlags & kFSEventStreamEventFlagRootChanged) {
// re-check everything:
DirsByName::iterator dirIt = watchedDirectories.find(path);
if (dirIt != watchedDirectories.end())
- checkDir(dirIt);
- rescanFiles(path);
+ needsRestart |= checkDir(dirIt);
+ needsRestart |= rescanFiles(path);
continue;
}
if ((eFlags & kFSEventStreamEventFlagItemIsDir) && (eFlags & kFSEventStreamEventFlagItemRemoved))
- rescanDirs(path);
+ needsRestart |= rescanDirs(path);
// check watched directories:
DirsByName::iterator dirIt = watchedDirectories.find(path);
if (dirIt != watchedDirectories.end())
- checkDir(dirIt);
+ needsRestart |= checkDir(dirIt);
// check watched files:
FilesByPath::iterator pIt = watchedFiles.find(path);
if (pIt != watchedFiles.end())
- rescanFiles(pIt.value());
+ needsRestart |= rescanFiles(pIt.value());
}
+
+ if (needsRestart)
+ emit scheduleStreamRestart();
#else
// This is a work-around for moc: when we put the version check at the top of the header file,
// moc will still see the Q_OBJECT macro and generate a meta-object when compiling for 10.6,
@@ -248,14 +271,23 @@ void QFseventsFileSystemWatcherEngine::processEvent(ConstFSEventStreamRef stream
void QFseventsFileSystemWatcherEngine::doEmitFileChanged(const QString path, bool removed)
{
+ DEBUG() << "emitting fileChanged for" << path << "with removed =" << removed;
emit fileChanged(path, removed);
}
void QFseventsFileSystemWatcherEngine::doEmitDirectoryChanged(const QString path, bool removed)
{
+ DEBUG() << "emitting directoryChanged for" << path << "with removed =" << removed;
emit directoryChanged(path, removed);
}
+void QFseventsFileSystemWatcherEngine::restartStream()
+{
+ QMutexLocker locker(&lock);
+ stopStream();
+ startStream();
+}
+
QFseventsFileSystemWatcherEngine *QFseventsFileSystemWatcherEngine::create(QObject *parent)
{
return new QFseventsFileSystemWatcherEngine(parent);
@@ -264,6 +296,7 @@ QFseventsFileSystemWatcherEngine *QFseventsFileSystemWatcherEngine::create(QObje
QFseventsFileSystemWatcherEngine::QFseventsFileSystemWatcherEngine(QObject *parent)
: QFileSystemWatcherEngine(parent)
, stream(0)
+ , lastReceivedEvent(kFSEventStreamEventIdSinceNow)
{
// We cannot use signal-to-signal queued connections, because the
@@ -272,6 +305,8 @@ QFseventsFileSystemWatcherEngine::QFseventsFileSystemWatcherEngine(QObject *pare
this, SLOT(doEmitDirectoryChanged(const QString, bool)), Qt::QueuedConnection);
connect(this, SIGNAL(emitFileChanged(const QString, bool)),
this, SLOT(doEmitFileChanged(const QString, bool)), Qt::QueuedConnection);
+ connect(this, SIGNAL(scheduleStreamRestart()),
+ this, SLOT(restartStream()), Qt::QueuedConnection);
queue = dispatch_queue_create("org.qt-project.QFseventsFileSystemWatcherEngine", NULL);
}
@@ -284,7 +319,7 @@ QFseventsFileSystemWatcherEngine::~QFseventsFileSystemWatcherEngine()
// The assumption with the locking strategy is that this class cannot and will not be subclassed!
QMutexLocker locker(&lock);
- stopStream();
+ stopStream(true);
dispatch_release(queue);
}
@@ -292,12 +327,14 @@ QStringList QFseventsFileSystemWatcherEngine::addPaths(const QStringList &paths,
QStringList *files,
QStringList *directories)
{
- if (stream)
+ if (stream) {
+ DEBUG("Flushing, last id is %llu", FSEventStreamGetLatestEventId(stream));
FSEventStreamFlushSync(stream);
+ }
QMutexLocker locker(&lock);
- bool newWatchPathsFound = false;
+ bool needsRestart = false;
QStringList p = paths;
QMutableListIterator<QString> it(p);
@@ -343,8 +380,9 @@ QStringList QFseventsFileSystemWatcherEngine::addPaths(const QStringList &paths,
PathRefCounts::iterator it = watchedPaths.find(watchedPath);
if (it == watchedPaths.end()) {
- newWatchPathsFound = true;
+ needsRestart = true;
watchedPaths.insert(watchedPath, 1);
+ DEBUG("Adding '%s' to watchedPaths", qPrintable(watchedPath));
} else {
++it.value();
}
@@ -355,12 +393,14 @@ QStringList QFseventsFileSystemWatcherEngine::addPaths(const QStringList &paths,
dirInfo.dirInfo = info;
dirInfo.entries = scanForDirEntries(realPath);
watchedDirectories.insert(realPath, dirInfo);
+ DEBUG("-- Also adding '%s' to watchedDirectories", qPrintable(realPath));
} else {
watchedFiles[parentPath].insert(realPath, info);
+ DEBUG("-- Also adding '%s' to watchedFiles", qPrintable(realPath));
}
}
- if (newWatchPathsFound) {
+ if (needsRestart) {
stopStream();
if (!startStream())
p = paths;
@@ -375,6 +415,8 @@ QStringList QFseventsFileSystemWatcherEngine::removePaths(const QStringList &pat
{
QMutexLocker locker(&lock);
+ bool needsRestart = false;
+
QStringList p = paths;
QMutableListIterator<QString> it(p);
while (it.hasNext()) {
@@ -389,10 +431,11 @@ QStringList QFseventsFileSystemWatcherEngine::removePaths(const QStringList &pat
if (fi.isDir()) {
DirsByName::iterator dirIt = watchedDirectories.find(realPath);
if (dirIt != watchedDirectories.end()) {
- derefPath(dirIt->dirInfo.watchedPath);
+ needsRestart |= derefPath(dirIt->dirInfo.watchedPath);
watchedDirectories.erase(dirIt);
directories->removeAll(origPath);
it.remove();
+ DEBUG("Removed directory '%s'", qPrintable(realPath));
}
} else {
QFileInfo fi(realPath);
@@ -402,22 +445,32 @@ QStringList QFseventsFileSystemWatcherEngine::removePaths(const QStringList &pat
InfoByName &filesInDir = pIt.value();
InfoByName::iterator fIt = filesInDir.find(realPath);
if (fIt != filesInDir.end()) {
- derefPath(fIt->watchedPath);
+ needsRestart |= derefPath(fIt->watchedPath);
filesInDir.erase(fIt);
if (filesInDir.isEmpty())
watchedFiles.erase(pIt);
files->removeAll(origPath);
it.remove();
+ DEBUG("Removed file '%s'", qPrintable(realPath));
}
}
}
}
+ locker.unlock();
+
+ if (needsRestart)
+ restartStream();
+
return p;
}
bool QFseventsFileSystemWatcherEngine::startStream()
{
+ Q_ASSERT(stream == 0);
+ if (stream) // This shouldn't happen, but let's be nice and handle it.
+ stopStream();
+
if (watchedPaths.isEmpty())
return false;
@@ -437,11 +490,15 @@ bool QFseventsFileSystemWatcherEngine::startStream()
const CFAbsoluteTime latency = .5; // in seconds
FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagWatchRoot;
+ // Never start with kFSEventStreamEventIdSinceNow, because this will generate an invalid
+ // soft-assert in FSEventStreamFlushSync in CarbonCore when no event occurred.
+ if (lastReceivedEvent == kFSEventStreamEventIdSinceNow)
+ lastReceivedEvent = FSEventsGetCurrentEventId();
stream = FSEventStreamCreate(NULL,
&callBackFunction,
&callBackInfo,
reinterpret_cast<CFArrayRef>(pathsToWatch),
- kFSEventStreamEventIdSinceNow,
+ lastReceivedEvent,
latency,
flags);
@@ -453,10 +510,13 @@ bool QFseventsFileSystemWatcherEngine::startStream()
FSEventStreamSetDispatchQueue(stream, queue);
if (FSEventStreamStart(stream)) {
- DEBUG() << "Stream started successfully.";
+ DEBUG() << "Stream started successfully with sinceWhen =" << lastReceivedEvent;
return true;
} else {
DEBUG() << "Stream failed to start!";
+ FSEventStreamInvalidate(stream);
+ FSEventStreamRelease(stream);
+ stream = 0;
return false;
}
}
@@ -469,7 +529,7 @@ void QFseventsFileSystemWatcherEngine::stopStream(bool isStopped)
FSEventStreamInvalidate(stream);
FSEventStreamRelease(stream);
stream = 0;
- DEBUG() << "Stream stopped.";
+ DEBUG() << "Stream stopped. Last event ID:" << lastReceivedEvent;
}
}
@@ -490,16 +550,16 @@ QFseventsFileSystemWatcherEngine::InfoByName QFseventsFileSystemWatcherEngine::s
return entries;
}
-void QFseventsFileSystemWatcherEngine::derefPath(const QString &watchedPath)
+bool QFseventsFileSystemWatcherEngine::derefPath(const QString &watchedPath)
{
PathRefCounts::iterator it = watchedPaths.find(watchedPath);
- if (it == watchedPaths.end())
- return;
- if (--it.value() < 1) {
+ if (it != watchedPaths.end() && --it.value() < 1) {
watchedPaths.erase(it);
- stopStream();
- startStream();
+ DEBUG("Removing '%s' from watchedPaths.", qPrintable(watchedPath));
+ return true;
}
+
+ return false;
}
#endif //QT_NO_FILESYSTEMWATCHER
diff --git a/src/corelib/io/qfilesystemwatcher_fsevents_p.h b/src/corelib/io/qfilesystemwatcher_fsevents_p.h
index c899c556c8..2de2bf4f18 100644
--- a/src/corelib/io/qfilesystemwatcher_fsevents_p.h
+++ b/src/corelib/io/qfilesystemwatcher_fsevents_p.h
@@ -84,10 +84,12 @@ public:
Q_SIGNALS:
void emitFileChanged(const QString path, bool removed);
void emitDirectoryChanged(const QString path, bool removed);
+ void scheduleStreamRestart();
private slots:
void doEmitFileChanged(const QString path, bool removed);
void doEmitDirectoryChanged(const QString path, bool removed);
+ void restartStream();
private:
struct Info {
@@ -122,11 +124,11 @@ private:
bool startStream();
void stopStream(bool isStopped = false);
InfoByName scanForDirEntries(const QString &path);
- void derefPath(const QString &watchedPath);
- void checkDir(DirsByName::iterator &it);
- void rescanDirs(const QString &path);
- void rescanFiles(InfoByName &filesInPath);
- void rescanFiles(const QString &path);
+ bool derefPath(const QString &watchedPath);
+ bool checkDir(DirsByName::iterator &it);
+ bool rescanDirs(const QString &path);
+ bool rescanFiles(InfoByName &filesInPath);
+ bool rescanFiles(const QString &path);
QMutex lock;
dispatch_queue_t queue;
@@ -134,6 +136,7 @@ private:
FilesByPath watchedFiles;
DirsByName watchedDirectories;
PathRefCounts watchedPaths;
+ FSEventStreamEventId lastReceivedEvent;
};
QT_END_NAMESPACE
diff --git a/tests/auto/corelib/io/qfilesystemwatcher/tst_qfilesystemwatcher.cpp b/tests/auto/corelib/io/qfilesystemwatcher/tst_qfilesystemwatcher.cpp
index b681cec802..a58c7dfb4b 100644
--- a/tests/auto/corelib/io/qfilesystemwatcher/tst_qfilesystemwatcher.cpp
+++ b/tests/auto/corelib/io/qfilesystemwatcher/tst_qfilesystemwatcher.cpp
@@ -324,6 +324,9 @@ void tst_QFileSystemWatcher::watchDirectory()
fileName = changedSpy.at(1).at(0).toString();
QCOMPARE(fileName, testDir.absolutePath());
+ // flush pending signals (like the one from the rmdir above)
+ timer.start(5000);
+ eventLoop.exec();
changedSpy.clear();
// recreate the file, we should not get any notification