Mac: FSEvents-based QFileSystemWatcherEngine.
Use FSEvents to monitor changes in the filesystem instead of the kqueue implementation. This removes the limit of wathed files: kqueue uses a file descriptor for each file monitored, for which the ulimit was set by default to 256. Now the OSX implementation on par with the other major desktop platforms. Change-Id: I2d46cca811978621989fd35201138df88a37c0fb Reviewed-by: Morten Johan Sørvig <morten.sorvig@digia.com>
This commit is contained in:
parent
97c187da3c
commit
4273c14e57
@ -135,6 +135,10 @@ win32 {
|
||||
OBJECTIVE_SOURCES += io/qurl_mac.mm
|
||||
}
|
||||
mac {
|
||||
osx {
|
||||
OBJECTIVE_SOURCES += io/qfilesystemwatcher_fsevents.mm
|
||||
HEADERS += io/qfilesystemwatcher_fsevents_p.h
|
||||
}
|
||||
macx {
|
||||
SOURCES += io/qstandardpaths_mac.cpp
|
||||
} else:ios {
|
||||
|
@ -60,8 +60,10 @@
|
||||
# include "qfilesystemwatcher_win_p.h"
|
||||
#elif defined(USE_INOTIFY)
|
||||
# include "qfilesystemwatcher_inotify_p.h"
|
||||
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
|
||||
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_IOS) || (defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7)
|
||||
# include "qfilesystemwatcher_kqueue_p.h"
|
||||
#elif defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_6
|
||||
# include "qfilesystemwatcher_fsevents_p.h"
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -74,8 +76,10 @@ QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine(QObject
|
||||
// there is a chance that inotify may fail on Linux pre-2.6.13 (August
|
||||
// 2005), so we can't just new inotify directly.
|
||||
return QInotifyFileSystemWatcherEngine::create(parent);
|
||||
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
|
||||
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_IOS) || (defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7)
|
||||
return QKqueueFileSystemWatcherEngine::create(parent);
|
||||
#elif defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_6
|
||||
return QFseventsFileSystemWatcherEngine::create(parent);
|
||||
#else
|
||||
Q_UNUSED(parent);
|
||||
return 0;
|
||||
|
507
src/corelib/io/qfilesystemwatcher_fsevents.mm
Normal file
507
src/corelib/io/qfilesystemwatcher_fsevents.mm
Normal file
@ -0,0 +1,507 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, 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, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia 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.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <qplatformdefs.h>
|
||||
|
||||
#include "qdiriterator.h"
|
||||
#include "qfilesystemwatcher.h"
|
||||
#include "qfilesystemwatcher_fsevents_p.h"
|
||||
#include "private/qcore_unix_p.h"
|
||||
#include "kernel/qcore_mac_p.h"
|
||||
|
||||
#ifndef QT_NO_FILESYSTEMWATCHER
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qdir.h>
|
||||
#include <qfile.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qvarlengtharray.h>
|
||||
|
||||
//#define FSEVENT_DEBUG
|
||||
#ifdef FSEVENT_DEBUG
|
||||
# define DEBUG if (true) qDebug
|
||||
#else
|
||||
# define DEBUG if (false) qDebug
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
static void callBackFunction(ConstFSEventStreamRef streamRef,
|
||||
void *clientCallBackInfo,
|
||||
size_t numEvents,
|
||||
void *eventPaths,
|
||||
const FSEventStreamEventFlags eventFlags[],
|
||||
const FSEventStreamEventId eventIds[])
|
||||
{
|
||||
char **paths = static_cast<char **>(eventPaths);
|
||||
QFseventsFileSystemWatcherEngine *engine = static_cast<QFseventsFileSystemWatcherEngine *>(clientCallBackInfo);
|
||||
engine->processEvent(streamRef, numEvents, paths, eventFlags, eventIds);
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::checkDir(DirsByName::iterator &it)
|
||||
{
|
||||
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);
|
||||
emit emitDirectoryChanged(info.origPath, true);
|
||||
it = watchedDirectories.erase(it);
|
||||
} else if (st.st_ctimespec != info.ctime || st.st_mode != info.mode) {
|
||||
info.ctime = st.st_ctimespec;
|
||||
info.mode = st.st_mode;
|
||||
emit emitDirectoryChanged(info.origPath, false);
|
||||
++it;
|
||||
} else {
|
||||
bool dirChanged = false;
|
||||
InfoByName &entries = it->entries;
|
||||
// check known entries:
|
||||
for (InfoByName::iterator i = entries.begin(); i != entries.end(); ) {
|
||||
if (QT_STAT(QFile::encodeName(i.key()), &st) == -1) {
|
||||
// entry disappeared
|
||||
dirChanged = true;
|
||||
i = entries.erase(i);
|
||||
} else {
|
||||
if (i->ctime != st.st_ctimespec || i->mode != st.st_mode) {
|
||||
// entry changed
|
||||
dirChanged = true;
|
||||
i->ctime = st.st_ctimespec;
|
||||
i->mode = st.st_mode;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
// check for new entries:
|
||||
QDirIterator dirIt(name);
|
||||
while (dirIt.hasNext()) {
|
||||
dirIt.next();
|
||||
QString entryName = dirIt.filePath();
|
||||
if (!entries.contains(entryName)) {
|
||||
dirChanged = true;
|
||||
QT_STATBUF st;
|
||||
if (QT_STAT(QFile::encodeName(entryName), &st) == -1)
|
||||
continue;
|
||||
entries.insert(entryName, Info(QString(), st.st_ctimespec, st.st_mode, QString()));
|
||||
|
||||
}
|
||||
}
|
||||
if (dirChanged)
|
||||
emit emitDirectoryChanged(info.origPath, false);
|
||||
}
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::rescanDirs(const QString &path)
|
||||
{
|
||||
for (DirsByName::iterator it = watchedDirectories.begin(); it != watchedDirectories.end(); ) {
|
||||
if (it.key().startsWith(path))
|
||||
checkDir(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::rescanFiles(InfoByName &filesInPath)
|
||||
{
|
||||
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);
|
||||
emit emitFileChanged(it.value().origPath, true);
|
||||
it = filesInPath.erase(it);
|
||||
continue;
|
||||
} else if (st.st_ctimespec != it->ctime || st.st_mode != it->mode) {
|
||||
it->ctime = st.st_ctimespec;
|
||||
it->mode = st.st_mode;
|
||||
emit emitFileChanged(it.value().origPath, false);
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::rescanFiles(const QString &path)
|
||||
{
|
||||
for (FilesByPath::iterator i = watchedFiles.begin(); i != watchedFiles.end(); ) {
|
||||
if (i.key().startsWith(path)) {
|
||||
rescanFiles(i.value());
|
||||
if (i.value().isEmpty()) {
|
||||
i = watchedFiles.erase(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::processEvent(ConstFSEventStreamRef streamRef,
|
||||
size_t numEvents,
|
||||
char **eventPaths,
|
||||
const FSEventStreamEventFlags eventFlags[],
|
||||
const FSEventStreamEventId eventIds[])
|
||||
{
|
||||
#if defined(Q_OS_OSX) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_6
|
||||
Q_UNUSED(streamRef);
|
||||
|
||||
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);
|
||||
QString path = QFile::decodeName(eventPaths[i]);
|
||||
if (path.endsWith(QDir::separator()))
|
||||
path = path.mid(0, path.size() - 1);
|
||||
|
||||
if (eFlags & kFSEventStreamEventFlagMustScanSubDirs) {
|
||||
DEBUG("\tmust rescan directory because of coalesced events");
|
||||
if (eFlags & kFSEventStreamEventFlagUserDropped)
|
||||
DEBUG("\t\t... user dropped.");
|
||||
if (eFlags & kFSEventStreamEventFlagKernelDropped)
|
||||
DEBUG("\t\t... kernel dropped.");
|
||||
rescanDirs(path);
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((eFlags & kFSEventStreamEventFlagItemIsDir) && (eFlags & kFSEventStreamEventFlagItemRemoved))
|
||||
rescanDirs(path);
|
||||
|
||||
// check watched directories:
|
||||
DirsByName::iterator dirIt = watchedDirectories.find(path);
|
||||
if (dirIt != watchedDirectories.end())
|
||||
checkDir(dirIt);
|
||||
|
||||
// check watched files:
|
||||
FilesByPath::iterator pIt = watchedFiles.find(path);
|
||||
if (pIt != watchedFiles.end())
|
||||
rescanFiles(pIt.value());
|
||||
}
|
||||
#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,
|
||||
// which obviously won't link.
|
||||
//
|
||||
// So the trick is to still compile this class on 10.6, but never instantiate it.
|
||||
|
||||
Q_UNUSED(streamRef);
|
||||
Q_UNUSED(numEvents);
|
||||
Q_UNUSED(eventPaths);
|
||||
Q_UNUSED(eventFlags);
|
||||
Q_UNUSED(eventIds);
|
||||
#endif
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::doEmitFileChanged(const QString path, bool removed)
|
||||
{
|
||||
emit fileChanged(path, removed);
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::doEmitDirectoryChanged(const QString path, bool removed)
|
||||
{
|
||||
emit directoryChanged(path, removed);
|
||||
}
|
||||
|
||||
QFseventsFileSystemWatcherEngine *QFseventsFileSystemWatcherEngine::create(QObject *parent)
|
||||
{
|
||||
return new QFseventsFileSystemWatcherEngine(parent);
|
||||
}
|
||||
|
||||
QFseventsFileSystemWatcherEngine::QFseventsFileSystemWatcherEngine(QObject *parent)
|
||||
: QFileSystemWatcherEngine(parent)
|
||||
, stream(0)
|
||||
{
|
||||
|
||||
// We cannot use signal-to-signal queued connections, because the
|
||||
// QSignalSpy cannot spot signals fired from other/alien threads.
|
||||
connect(this, SIGNAL(emitDirectoryChanged(const QString, bool)),
|
||||
this, SLOT(doEmitDirectoryChanged(const QString, bool)), Qt::QueuedConnection);
|
||||
connect(this, SIGNAL(emitFileChanged(const QString, bool)),
|
||||
this, SLOT(doEmitFileChanged(const QString, bool)), Qt::QueuedConnection);
|
||||
|
||||
queue = dispatch_queue_create("org.qt-project.QFseventsFileSystemWatcherEngine", NULL);
|
||||
}
|
||||
|
||||
QFseventsFileSystemWatcherEngine::~QFseventsFileSystemWatcherEngine()
|
||||
{
|
||||
if (stream)
|
||||
FSEventStreamStop(stream);
|
||||
|
||||
// The assumption with the locking strategy is that this class cannot and will not be subclassed!
|
||||
QMutexLocker locker(&lock);
|
||||
|
||||
stopStream();
|
||||
dispatch_release(queue);
|
||||
}
|
||||
|
||||
QStringList QFseventsFileSystemWatcherEngine::addPaths(const QStringList &paths,
|
||||
QStringList *files,
|
||||
QStringList *directories)
|
||||
{
|
||||
if (stream)
|
||||
FSEventStreamFlushSync(stream);
|
||||
|
||||
QMutexLocker locker(&lock);
|
||||
|
||||
bool newWatchPathsFound = false;
|
||||
|
||||
QStringList p = paths;
|
||||
QMutableListIterator<QString> it(p);
|
||||
while (it.hasNext()) {
|
||||
QString origPath = it.next();
|
||||
QString realPath = origPath;
|
||||
if (realPath.endsWith(QDir::separator()))
|
||||
realPath = realPath.mid(0, realPath.size() - 1);
|
||||
QString watchedPath, parentPath;
|
||||
|
||||
realPath = QFileInfo(realPath).canonicalFilePath();
|
||||
QFileInfo fi(realPath);
|
||||
if (realPath.isEmpty())
|
||||
continue;
|
||||
|
||||
QT_STATBUF st;
|
||||
if (QT_STAT(QFile::encodeName(realPath), &st) == -1)
|
||||
continue;
|
||||
|
||||
const bool isDir = S_ISDIR(st.st_mode);
|
||||
if (isDir) {
|
||||
if (watchedDirectories.contains(realPath))
|
||||
continue;
|
||||
directories->append(origPath);
|
||||
watchedPath = realPath;
|
||||
it.remove();
|
||||
} else {
|
||||
if (files->contains(origPath))
|
||||
continue;
|
||||
files->append(origPath);
|
||||
it.remove();
|
||||
|
||||
watchedPath = fi.path();
|
||||
parentPath = watchedPath;
|
||||
}
|
||||
|
||||
for (PathRefCounts::const_iterator i = watchedPaths.begin(), ei = watchedPaths.end(); i != ei; ++i) {
|
||||
if (watchedPath.startsWith(i.key())) {
|
||||
watchedPath = i.key();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PathRefCounts::iterator it = watchedPaths.find(watchedPath);
|
||||
if (it == watchedPaths.end()) {
|
||||
newWatchPathsFound = true;
|
||||
watchedPaths.insert(watchedPath, 1);
|
||||
} else {
|
||||
++it.value();
|
||||
}
|
||||
|
||||
Info info(origPath, st.st_ctimespec, st.st_mode, watchedPath);
|
||||
if (isDir) {
|
||||
DirInfo dirInfo;
|
||||
dirInfo.dirInfo = info;
|
||||
dirInfo.entries = scanForDirEntries(realPath);
|
||||
watchedDirectories.insert(realPath, dirInfo);
|
||||
} else {
|
||||
watchedFiles[parentPath].insert(realPath, info);
|
||||
}
|
||||
}
|
||||
|
||||
if (newWatchPathsFound) {
|
||||
stopStream();
|
||||
if (!startStream())
|
||||
p = paths;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
QStringList QFseventsFileSystemWatcherEngine::removePaths(const QStringList &paths,
|
||||
QStringList *files,
|
||||
QStringList *directories)
|
||||
{
|
||||
QMutexLocker locker(&lock);
|
||||
|
||||
QStringList p = paths;
|
||||
QMutableListIterator<QString> it(p);
|
||||
while (it.hasNext()) {
|
||||
QString origPath = it.next();
|
||||
QString realPath = origPath;
|
||||
if (realPath.endsWith(QDir::separator()))
|
||||
realPath = realPath.mid(0, realPath.size() - 1);
|
||||
|
||||
QFileInfo fi(realPath);
|
||||
realPath = fi.canonicalFilePath();
|
||||
|
||||
if (fi.isDir()) {
|
||||
DirsByName::iterator dirIt = watchedDirectories.find(realPath);
|
||||
if (dirIt != watchedDirectories.end()) {
|
||||
derefPath(dirIt->dirInfo.watchedPath);
|
||||
watchedDirectories.erase(dirIt);
|
||||
directories->removeAll(origPath);
|
||||
it.remove();
|
||||
}
|
||||
} else {
|
||||
QFileInfo fi(realPath);
|
||||
QString parentPath = fi.path();
|
||||
FilesByPath::iterator pIt = watchedFiles.find(parentPath);
|
||||
if (pIt != watchedFiles.end()) {
|
||||
InfoByName &filesInDir = pIt.value();
|
||||
InfoByName::iterator fIt = filesInDir.find(realPath);
|
||||
if (fIt != filesInDir.end()) {
|
||||
derefPath(fIt->watchedPath);
|
||||
filesInDir.erase(fIt);
|
||||
if (filesInDir.isEmpty())
|
||||
watchedFiles.erase(pIt);
|
||||
files->removeAll(origPath);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
bool QFseventsFileSystemWatcherEngine::startStream()
|
||||
{
|
||||
if (watchedPaths.isEmpty())
|
||||
return false;
|
||||
|
||||
DEBUG() << "Starting stream with paths" << watchedPaths.keys();
|
||||
|
||||
NSMutableArray *pathsToWatch = [NSMutableArray arrayWithCapacity:watchedPaths.size()];
|
||||
for (PathRefCounts::const_iterator i = watchedPaths.begin(), ei = watchedPaths.end(); i != ei; ++i)
|
||||
[pathsToWatch addObject:reinterpret_cast<const NSString *>(QCFString::toCFStringRef(i.key()))];
|
||||
|
||||
struct FSEventStreamContext callBackInfo = {
|
||||
0,
|
||||
this,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
const CFAbsoluteTime latency = .5; // in seconds
|
||||
FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagWatchRoot;
|
||||
|
||||
stream = FSEventStreamCreate(NULL,
|
||||
&callBackFunction,
|
||||
&callBackInfo,
|
||||
reinterpret_cast<CFArrayRef>(pathsToWatch),
|
||||
kFSEventStreamEventIdSinceNow,
|
||||
latency,
|
||||
flags);
|
||||
|
||||
if (!stream) {
|
||||
DEBUG() << "Failed to create stream!";
|
||||
return false;
|
||||
}
|
||||
|
||||
FSEventStreamSetDispatchQueue(stream, queue);
|
||||
|
||||
if (FSEventStreamStart(stream)) {
|
||||
DEBUG() << "Stream started successfully.";
|
||||
return true;
|
||||
} else {
|
||||
DEBUG() << "Stream failed to start!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::stopStream(bool isStopped)
|
||||
{
|
||||
if (stream) {
|
||||
if (!isStopped)
|
||||
FSEventStreamStop(stream);
|
||||
FSEventStreamInvalidate(stream);
|
||||
FSEventStreamRelease(stream);
|
||||
stream = 0;
|
||||
DEBUG() << "Stream stopped.";
|
||||
}
|
||||
}
|
||||
|
||||
QFseventsFileSystemWatcherEngine::InfoByName QFseventsFileSystemWatcherEngine::scanForDirEntries(const QString &path)
|
||||
{
|
||||
InfoByName entries;
|
||||
|
||||
QDirIterator it(path);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
QString entryName = it.filePath();
|
||||
QT_STATBUF st;
|
||||
if (QT_STAT(QFile::encodeName(entryName), &st) == -1)
|
||||
continue;
|
||||
entries.insert(entryName, Info(QString(), st.st_ctimespec, st.st_mode, QString()));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
void QFseventsFileSystemWatcherEngine::derefPath(const QString &watchedPath)
|
||||
{
|
||||
PathRefCounts::iterator it = watchedPaths.find(watchedPath);
|
||||
if (it == watchedPaths.end())
|
||||
return;
|
||||
if (--it.value() < 1) {
|
||||
watchedPaths.erase(it);
|
||||
stopStream();
|
||||
startStream();
|
||||
}
|
||||
}
|
||||
|
||||
#endif //QT_NO_FILESYSTEMWATCHER
|
||||
|
||||
QT_END_NAMESPACE
|
142
src/corelib/io/qfilesystemwatcher_fsevents_p.h
Normal file
142
src/corelib/io/qfilesystemwatcher_fsevents_p.h
Normal file
@ -0,0 +1,142 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, 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, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia 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.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QFILESYSTEMWATCHER_FSEVENTS_P_H
|
||||
#define QFILESYSTEMWATCHER_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"
|
||||
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qhash.h>
|
||||
#include <QtCore/qthread.h>
|
||||
#include <QtCore/qvector.h>
|
||||
#include <QtCore/qsocketnotifier.h>
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
#ifndef QT_NO_FILESYSTEMWATCHER
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QFseventsFileSystemWatcherEngine : public QFileSystemWatcherEngine
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
~QFseventsFileSystemWatcherEngine();
|
||||
|
||||
static QFseventsFileSystemWatcherEngine *create(QObject *parent);
|
||||
|
||||
QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories);
|
||||
QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories);
|
||||
|
||||
void processEvent(ConstFSEventStreamRef streamRef, size_t numEvents, char **eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);
|
||||
|
||||
Q_SIGNALS:
|
||||
void emitFileChanged(const QString path, bool removed);
|
||||
void emitDirectoryChanged(const QString path, bool removed);
|
||||
|
||||
private slots:
|
||||
void doEmitFileChanged(const QString path, bool removed);
|
||||
void doEmitDirectoryChanged(const QString path, bool removed);
|
||||
|
||||
private:
|
||||
struct Info {
|
||||
QString origPath;
|
||||
timespec ctime;
|
||||
mode_t mode;
|
||||
QString watchedPath;
|
||||
|
||||
Info(): mode(0)
|
||||
{
|
||||
ctime.tv_sec = 0;
|
||||
ctime.tv_nsec = 0;
|
||||
}
|
||||
|
||||
Info(const QString &origPath, const timespec &ctime, mode_t mode, const QString &watchedPath)
|
||||
: origPath(origPath)
|
||||
, ctime(ctime)
|
||||
, mode(mode)
|
||||
, watchedPath(watchedPath)
|
||||
{}
|
||||
};
|
||||
typedef QHash<QString, Info> InfoByName;
|
||||
typedef QHash<QString, InfoByName> FilesByPath;
|
||||
struct DirInfo {
|
||||
Info dirInfo;
|
||||
InfoByName entries;
|
||||
};
|
||||
typedef QHash<QString, DirInfo> DirsByName;
|
||||
typedef QHash<QString, qint64> PathRefCounts;
|
||||
|
||||
QFseventsFileSystemWatcherEngine(QObject *parent);
|
||||
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);
|
||||
|
||||
QMutex lock;
|
||||
dispatch_queue_t queue;
|
||||
FSEventStreamRef stream;
|
||||
FilesByPath watchedFiles;
|
||||
DirsByName watchedDirectories;
|
||||
PathRefCounts watchedPaths;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif //QT_NO_FILESYSTEMWATCHER
|
||||
#endif // QFILESYSTEMWATCHER_FSEVENTS_P_H
|
Loading…
Reference in New Issue
Block a user