适用于Mac OS X的文件系统观察程序

时间:2013-03-04 04:40:32

标签: c++ qt filesystemwatcher kqueue

目前,我们使用属于Qt的QFileSystemWatcher。由于Mac OS X中的支持有限,它只能通知我们两个事件:目录已更改或文件已更改。

但是,后一个事件(文件已更改)在其大小略大时会多次触发,而写入磁盘的时间会稍长。

我们的解决方法是设置一个计时器,以便在1秒内检查文件。如果在计时器到期之前有关于该文件的更多信号,我们将重置计时器。

有没有办法在文件写入磁盘时获得通知(写完)?没有必要限制Qt,任何库都可以。


我们知道kqueue监控方法,但是这个级别太低了,我们不希望为每个文件执行此操作,因为我们正在监视文件系统的大部分内容。

2 个答案:

答案 0 :(得分:2)

我在项目中遇到同样的问题,最后我决定实现一个原生观察者。这很简单:

在.h:

class OSXWatcher : public Watcher
{
public:

    OSXWatcher(const QString& strDirectory);
    virtual ~OSXWatcher();

    virtual bool Start();
    virtual bool Stop();

private:

    /**
     * Callback function of the OS X FSEvent API.
     */
    static void fileSystemEventCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);

    FSEventStreamRef stream;
};

.cpp:

bool OSXWatcher::Start()
{
    CFStringRef pathToWatchCF = CFStringCreateWithCString(NULL, this->dirToWatch.toUtf8().constData(), kCFStringEncodingUTF8);
    CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&pathToWatchCF, 1, NULL);

    FSEventStreamContext context;
    context.version = 0;
    context.info = this;
    context.retain = NULL;
    context.release = NULL;
    context.copyDescription = NULL;

    stream = FSEventStreamCreate(NULL, &OSXWatcher::fileSystemEventCallback, &context, pathsToWatch, kFSEventStreamEventIdSinceNow, 3.0, kFSEventStreamCreateFlagFileEvents);
    FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    FSEventStreamStart(stream);

    CFRelease(pathToWatchCF);

    // Read the folder content to protect any unprotected or pending file
    ReadFolderContent();
}

bool OSXWatcher::Stop()
{
    FSEventStreamStop(stream);
    FSEventStreamInvalidate(stream);
    FSEventStreamRelease(stream);
}

void OSXWatcher::fileSystemEventCallback(ConstFSEventStreamRef /*streamRef*/, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
{
    char **paths = (char **)eventPaths;

    for (size_t i=0; i<numEvents; i++) {
        // When a file is created we receive first a kFSEventStreamEventFlagItemCreated and second a (kFSEventStreamEventFlagItemCreated & kFSEventStreamEventFlagItemModified)
        // when the file is finally copied. Catch this second event.
        if (eventFlags[i] & kFSEventStreamEventFlagItemCreated
                && eventFlags[i] & kFSEventStreamEventFlagItemModified
                && !(eventFlags[i] & kFSEventStreamEventFlagItemIsDir)
                && !(eventFlags[i] & kFSEventStreamEventFlagItemIsSymlink)
                && !(eventFlags[i] & kFSEventStreamEventFlagItemFinderInfoMod)) {

            OSXWatcher *watcher = (OSXWatcher *)clientCallBackInfo;
            if (watcher->FileValidator(paths[i]))
                emit watcher->yourSignalHere();
        }
    }
}

答案 1 :(得分:1)

我有同样的问题,但有文件夹。当您将许多文件复制到文件夹时,会发出太多的信号,但我只需要一个。所以我有以下解决方案:

void folderChanged(const QString& folder)
{
    m_pTimerForChanges->start();
}

folderChangeddirectoryChanged()信号的插槽。并且计时器有另一个超时连接,所以当时间结束时,应该进行处理。定时器有1s间隔。它背后的想法是文件夹不应该比我的间隔更频繁地更新,如果它比我需要更频繁地发送信号,那么我不需要立即处理它们。更确切地说,每次信号发出时我都会重新启动计时器,所有这些都是我唯一一次处理的变化。我认为你可以采用相同的方法。

另一种可能对你有用的方法是检查你的处理中的文件修改日期,如果它的当前修改日期在你上次修改日期的某个epsilon(小间隔)内,那么你有重复的信号,不应对它做出反应。存储此修改日期并继续。