Remove OS X FSEvents watcher.

Per QTBUG-9249, this backend is buggy, and not recommended for use by Apple.

Change-Id: I72ce88006a4badbbfdd825717020078778d16a36
Reviewed-by: Sergio Ahumada <sergio.ahumada@nokia.com>
This commit is contained in:
Robin Burchell 2012-01-03 21:31:08 +01:00 committed by Qt by Nokia
parent 8ad583b7f9
commit 848f53a268
4 changed files with 2 additions and 635 deletions

View File

@ -92,9 +92,8 @@ win32 {
io/qfilesystemiterator_unix.cpp \
!nacl:macx-*: {
HEADERS += io/qfilesystemwatcher_fsevents_p.h
SOURCES += io/qfilesystemengine_mac.cpp
SOURCES += io/qsettings_mac.cpp io/qfilesystemwatcher_fsevents.cpp
SOURCES += io/qsettings_mac.cpp
}
macx-*: {
SOURCES += io/qstandardpaths_mac.cpp

View File

@ -59,9 +59,6 @@
#elif defined(Q_OS_LINUX)
# include "qfilesystemwatcher_inotify_p.h"
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
# if (defined Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
# include "qfilesystemwatcher_fsevents_p.h"
# endif //MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
# include "qfilesystemwatcher_kqueue_p.h"
#endif
@ -76,12 +73,7 @@ QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine()
// 2005), so we can't just new inotify directly.
return QInotifyFileSystemWatcherEngine::create();
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
# if 0 && defined(Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5)
return QFSEventsFileSystemWatcherEngine::create();
else
# endif
return QKqueueFileSystemWatcherEngine::create();
return QKqueueFileSystemWatcherEngine::create();
#else
return 0;
#endif

View File

@ -1,492 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#define _DARWIN_USE_64_BIT_INODE
#include <qplatformdefs.h>
#include "qfilesystemwatcher.h"
#include "qfilesystemwatcher_fsevents_p.h"
#ifndef QT_NO_FILESYSTEMWATCHER
#include <qdebug.h>
#include <qfile.h>
#include <qdatetime.h>
#include <qfileinfo.h>
#include <qvarlengtharray.h>
#include <mach/mach.h>
#include <sys/types.h>
#include <CoreFoundation/CFRunLoop.h>
#include <CoreFoundation/CFUUID.h>
#include <CoreServices/CoreServices.h>
#include <AvailabilityMacros.h>
#include <private/qcore_mac_p.h>
QT_BEGIN_NAMESPACE
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
// Static operator overloading so for the sake of some convieniece.
// They only live in this compilation unit to avoid polluting Qt in general.
static bool operator==(const struct ::timespec &left, const struct ::timespec &right)
{
return left.tv_sec == right.tv_sec
&& left.tv_nsec == right.tv_nsec;
}
static bool operator==(const struct ::stat &left, const struct ::stat &right)
{
return left.st_dev == right.st_dev
&& left.st_mode == right.st_mode
&& left.st_size == right.st_size
&& left.st_ino == right.st_ino
&& left.st_uid == right.st_uid
&& left.st_gid == right.st_gid
&& left.st_mtimespec == right.st_mtimespec
&& left.st_ctimespec == right.st_ctimespec
&& left.st_flags == right.st_flags;
}
static bool operator!=(const struct ::stat &left, const struct ::stat &right)
{
return !(operator==(left, right));
}
static void addPathToHash(PathHash &pathHash, const QString &key, const QFileInfo &fileInfo,
const QString &path)
{
PathInfoList &list = pathHash[key];
list.push_back(PathInfo(path,
fileInfo.canonicalFilePath().normalized(QString::NormalizationForm_D).toUtf8()));
pathHash.insert(key, list);
}
static void removePathFromHash(PathHash &pathHash, const QString &key, const QString &path)
{
PathInfoList &list = pathHash[key];
// We make the assumption that the list contains unique paths
PathInfoList::iterator End = list.end();
PathInfoList::iterator it = list.begin();
while (it != End) {
if (it->originalPath == path) {
list.erase(it);
break;
}
++it;
}
if (list.isEmpty())
pathHash.remove(key);
}
static void stopFSStream(FSEventStreamRef stream)
{
if (stream) {
FSEventStreamStop(stream);
FSEventStreamInvalidate(stream);
}
}
static QString createFSStreamPath(const QString &absolutePath)
{
// The path returned has a trailing slash, so ensure that here.
QString string = absolutePath;
string.reserve(string.size() + 1);
string.append(QLatin1Char('/'));
return string;
}
static void cleanupFSStream(FSEventStreamRef stream)
{
if (stream)
FSEventStreamRelease(stream);
}
const FSEventStreamCreateFlags QtFSEventFlags = (kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagNoDefer /* | kFSEventStreamCreateFlagWatchRoot*/);
const CFTimeInterval Latency = 0.033; // This will do updates 30 times a second which is probably more than you need.
#endif
QFSEventsFileSystemWatcherEngine::QFSEventsFileSystemWatcherEngine()
: fsStream(0), pathsToWatch(0), threadsRunLoop(0)
{
}
QFSEventsFileSystemWatcherEngine::~QFSEventsFileSystemWatcherEngine()
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
// I assume that at this point, QFileSystemWatcher has already called stop
// on me, so I don't need to invalidate or stop my stream, simply
// release it.
cleanupFSStream(fsStream);
if (pathsToWatch)
CFRelease(pathsToWatch);
#endif
}
QFSEventsFileSystemWatcherEngine *QFSEventsFileSystemWatcherEngine::create()
{
return new QFSEventsFileSystemWatcherEngine();
}
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
}
void QFSEventsFileSystemWatcherEngine::warmUpFSEvents()
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
// This function assumes that the mutex has already been grabbed before calling it.
// It exits with the mutex still locked (Q_ASSERT(mutex.isLocked()) ;-).
start();
waitCondition.wait(&mutex);
#endif
}
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
}
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
void QFSEventsFileSystemWatcherEngine::updateList(PathInfoList &list, bool directory, bool emitSignals)
{
PathInfoList::iterator End = list.end();
PathInfoList::iterator it = list.begin();
while (it != End) {
struct ::stat newInfo;
if (::stat(it->absolutePath, &newInfo) == 0) {
if (emitSignals) {
if (newInfo != it->savedInfo) {
it->savedInfo = newInfo;
if (directory)
emit directoryChanged(it->originalPath, false);
else
emit fileChanged(it->originalPath, false);
}
} else {
it->savedInfo = newInfo;
}
} else {
if (errno == ENOENT) {
if (emitSignals) {
if (directory)
emit directoryChanged(it->originalPath, true);
else
emit fileChanged(it->originalPath, true);
}
it = list.erase(it);
continue;
} else {
qWarning("%s:%d:QFSEventsFileSystemWatcherEngine: stat error on %s:%s",
__FILE__, __LINE__, qPrintable(it->originalPath), strerror(errno));
}
}
++it;
}
}
void QFSEventsFileSystemWatcherEngine::updateHash(PathHash &pathHash)
{
PathHash::iterator HashEnd = pathHash.end();
PathHash::iterator it = pathHash.begin();
const bool IsDirectory = (&pathHash == &dirPathInfoHash);
while (it != HashEnd) {
updateList(it.value(), IsDirectory, false);
if (it.value().isEmpty())
it = pathHash.erase(it);
else
++it;
}
}
#endif
void QFSEventsFileSystemWatcherEngine::fseventsCallback(ConstFSEventStreamRef ,
void *clientCallBackInfo, size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId [])
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
QFSEventsFileSystemWatcherEngine *watcher = static_cast<QFSEventsFileSystemWatcherEngine *>(clientCallBackInfo);
QMutexLocker locker(&watcher->mutex);
CFArrayRef paths = static_cast<CFArrayRef>(eventPaths);
for (size_t i = 0; i < numEvents; ++i) {
const QString path = QCFString::toQString(
static_cast<CFStringRef>(CFArrayGetValueAtIndex(paths, i)));
const FSEventStreamEventFlags pathFlags = eventFlags[i];
// There are several flags that may be passed, but we really don't care about them ATM.
// Here they are and why we don't care.
// kFSEventStreamEventFlagHistoryDone--(very unlikely to be gotten, but even then, not much changes).
// kFSEventStreamEventFlagMustScanSubDirs--Likely means the data is very much out of date, we
// aren't coalescing our directories, so again not so much of an issue
// kFSEventStreamEventFlagRootChanged | kFSEventStreamEventFlagMount | kFSEventStreamEventFlagUnmount--
// These three flags indicate something has changed, but the stat will likely show this, so
// there's not really much to worry about.
// (btw, FSEvents is not the correct way of checking for mounts/unmounts,
// there are real CarbonCore events for that.)
Q_UNUSED(pathFlags);
if (watcher->filePathInfoHash.contains(path))
watcher->updateList(watcher->filePathInfoHash[path], false, true);
if (watcher->dirPathInfoHash.contains(path))
watcher->updateList(watcher->dirPathInfoHash[path], true, true);
}
#else
Q_UNUSED(clientCallBackInfo);
Q_UNUSED(numEvents);
Q_UNUSED(eventPaths);
Q_UNUSED(eventFlags);
#endif
}
void QFSEventsFileSystemWatcherEngine::stop()
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
QMutexLocker locker(&mutex);
stopFSStream(fsStream);
if (threadsRunLoop) {
CFRunLoopStop(threadsRunLoop);
waitForStop.wait(&mutex);
}
#endif
}
void QFSEventsFileSystemWatcherEngine::updateFiles()
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
QMutexLocker locker(&mutex);
updateHash(filePathInfoHash);
updateHash(dirPathInfoHash);
if (filePathInfoHash.isEmpty() && dirPathInfoHash.isEmpty()) {
// Everything disappeared before we got to start, don't bother.
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
// Code duplicated from stop(), with the exception that we
// don't wait on waitForStop here. Doing this will lead to
// a deadlock since this function is called from the worker
// thread. (waitForStop.wakeAll() is only called from the
// end of run()).
stopFSStream(fsStream);
if (threadsRunLoop)
CFRunLoopStop(threadsRunLoop);
#endif
cleanupFSStream(fsStream);
}
waitCondition.wakeAll();
#endif
}
void QFSEventsFileSystemWatcherEngine::run()
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
threadsRunLoop = CFRunLoopGetCurrent();
FSEventStreamScheduleWithRunLoop(fsStream, threadsRunLoop, kCFRunLoopDefaultMode);
bool startedOK = FSEventStreamStart(fsStream);
// It's recommended by Apple that you only update the files after you've started
// the stream, because otherwise you might miss an update in between starting it.
updateFiles();
#ifdef QT_NO_DEBUG
Q_UNUSED(startedOK);
#else
Q_ASSERT(startedOK);
#endif
// If for some reason we called stop up above (and invalidated our stream), this call will return
// immediately.
CFRunLoopRun();
threadsRunLoop = 0;
QMutexLocker locker(&mutex);
waitForStop.wakeAll();
#endif
}
QT_END_NAMESPACE
#endif //QT_NO_FILESYSTEMWATCHER

View File

@ -1,132 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef FILEWATCHER_FSEVENTS_P_H
#define FILEWATCHER_FSEVENTS_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of the QLibrary class. This header file may change from
// version to version without notice, or even be removed.
//
// We mean it.
//
#include "qfilesystemwatcher_p.h"
#ifndef QT_NO_FILESYSTEMWATCHER
#include <QtCore/qmutex.h>
#include <QtCore/qwaitcondition.h>
#include <QtCore/qthread.h>
#include <QtCore/qhash.h>
#include <QtCore/qlinkedlist.h>
#include <private/qcore_mac_p.h>
#include <sys/stat.h>
typedef struct __FSEventStream *FSEventStreamRef;
typedef const struct __FSEventStream *ConstFSEventStreamRef;
typedef const struct __CFArray *CFArrayRef;
typedef UInt32 FSEventStreamEventFlags;
typedef uint64_t FSEventStreamEventId;
QT_BEGIN_NAMESPACE
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
// Yes, I use a stat element here. QFileInfo requires too much knowledge about implementation
// details to be used as a long-standing record. Since I'm going to have to store this information, I can
// do the stat myself too.
struct PathInfo {
PathInfo(const QString &path, const QByteArray &absPath)
: originalPath(path), absolutePath(absPath) {}
QString originalPath; // The path we need to emit
QByteArray absolutePath; // The path we need to stat.
struct ::stat savedInfo; // All the info for the path so we can compare it.
};
typedef QLinkedList<PathInfo> PathInfoList;
typedef QHash<QString, PathInfoList> PathHash;
#endif
class QFSEventsFileSystemWatcherEngine : public QFileSystemWatcherEngine
{
Q_OBJECT
public:
~QFSEventsFileSystemWatcherEngine();
static QFSEventsFileSystemWatcherEngine *create();
QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories);
QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories);
void stop();
private:
QFSEventsFileSystemWatcherEngine();
void warmUpFSEvents();
void updateFiles();
static void fseventsCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents,
void *eventPaths, const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]);
void run();
FSEventStreamRef fsStream;
CFArrayRef pathsToWatch;
CFRunLoopRef threadsRunLoop;
QMutex mutex;
QWaitCondition waitCondition;
QWaitCondition waitForStop;
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
PathHash filePathInfoHash;
PathHash dirPathInfoHash;
void updateHash(PathHash &pathHash);
void updateList(PathInfoList &list, bool directory, bool emitSignals);
#endif
};
#endif //QT_NO_FILESYSTEMWATCHER
#endif
QT_END_NAMESPACE