Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to listen for file system changes MAC - kFSEventStreamCreateFlagWatchRoot

Tags:

cocoa

fsevents

I am listening for Directory and disk changes in a Cocoa project using FSEvents. I need to get events when a root folder is renamed or deleted. So, I passed kFSEventStreamCreateFlagWatchRoot while creating the FSEventStream. But even if I delete or rename the root folder I am not getting corresponding FSEventStreamEventFlags. Any idea what could possibly be the issue. I am listening for changes in a USB mounted device. I used both FSEventStreamCreate and FSEventStreamCreateRelativeToDevice. One thing I notices is when I try with FSEventStreamCreate I get the following error message while creating FSEventStream:

(CarbonCore.framework) FSEventStreamCreate: watch_all_parents:
error trying to add kqueue for fd 7 (/Volumes/NO NAME; Operation not supported)

But with FSEventStreamCreateRelativeToDevice there are no errors but still not getting kFSEventStreamEventFlagRootChanged in event flags. Also, while creation using FSEventStreamCreateRelativeToDevice apple say's if I want to listen to root path changes pass emty string "". But I am not able to listen to root path changes by passing empty string. But when I pass "/" it works. But even for "/" I do not get any proper FSEventStreamEventFlags. I am pasting the code here:

-(void) subscribeFileSystemChanges:(NSString*) path
{
    PRINT_FUNCTION_BEGIN;

    // if already subscribed then unsubscribe
    if (stream)
    {
        FSEventStreamStop(stream);
        FSEventStreamInvalidate(stream); /* will remove from runloop */
        FSEventStreamRelease(stream);
    }

    FSEventStreamContext cntxt = {0};
    cntxt.info = self;

    CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void**)&path, 1, NULL);


    stream = FSEventStreamCreate(NULL, &feCallback, &cntxt, 
                                 pathsToWatch, kFSEventStreamEventIdSinceNow, 1,
                                 kFSEventStreamCreateFlagWatchRoot );


    FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), 
                                     kCFRunLoopDefaultMode);

    FSEventStreamStart(stream);


}

Call back function:

static void feCallback(ConstFSEventStreamRef streamRef, void* pClientCallBackInfo, 
                       size_t numEvents, void* pEventPaths, const    FSEventStreamEventFlags eventFlags[], 
                       const FSEventStreamEventId eventIds[]) 

{
char** ppPaths = (char**)pEventPaths; int i;

    for (i = 0; i < numEvents; i++)
    {
        NSLog(@"Event Flags %lu Event Id %llu", eventFlags[i], eventIds[i]); 
        NSLog(@"Path changed: %@", 
              [NSString stringWithUTF8String:ppPaths[i]]); 
    }    
}

Thanks a lot in advance.

like image 765
wantro Avatar asked Jan 28 '10 13:01

wantro


2 Answers

I had the same problem and I think I figured it out. Apparently kFSEventStreamCreateFlagWatchRoot is just simply busted when using FSEventStreamCreateRelativeToDevice. You have to use FSEventStreamCreate. Since the former form is preferable if you're relying on historical event ids, you might need to create 2 streams. Also, note that it appears that you don't get kEventFlagChangedRoot sent to you if your app isn't running, so you'll need to stat the directory when you start up.

like image 84
Scott S Avatar answered Nov 16 '22 02:11

Scott S


I think the change of the volume name is not counted as a change in the file system reported by FSEvents. Remember, the volume name itself does not really exists as a file system entry. The ones under /Volumes is cooked up by the OS.

It's instead covered by Disk Arbitration.

A short sample code follows. First, define the callback

#import <DiskArbitration/DiskArbitration.h>
void callBack(DADiskRef disk,CFArrayRef keys,void *context )
{
    CFDictionaryRef dict=DADiskCopyDescription(disk);
    NSString*mountPoint=[(NSDictionary*)dict objectForKey:(NSString*)kDADiskDescriptionVolumePathKey];
    NSLog(@"disk at %@:",mountPoint);
    for(NSString*key in (NSArray*)keys){
    NSLog(@"key %@ changed: %@",key,[(NSDictionary*)dict objectForKey:key]);    
    }
    CFRelease(dict);
}

and then install the handler like this

DASessionRef session=DASessionCreate(NULL);
DARegisterDiskDescriptionChangedCallback(session, NULL, NULL, callBack, NULL);
DASessionScheduleWithRunLoop(session, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopCommonModes);
like image 28
Yuji Avatar answered Nov 16 '22 02:11

Yuji