Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Notification of changes to the iPhone's /Documents directory

We have an app that uses file sharing. UIFileSharingEnable is set etc. and it all seems to work fine, but I'm looking for some sort of notification of when files have been added/deleted on the iPhone side. Can anyone advise?

Cheers in advance.

like image 759
Paul Carruthers Avatar asked Jul 05 '10 19:07

Paul Carruthers


2 Answers

This thread on the Apple Developer Forums may be of interest, in which it is suggested that you run a kqueue in its own thread, tracking the app's Documents folder.

An Apple tech followed up with some sample code here:

- (void)kqueueFired
{
    int             kq;
    struct kevent   event;
    struct timespec timeout = { 0, 0 };
    int             eventCount;

    kq = CFFileDescriptorGetNativeDescriptor(self->_kqRef);
    assert(kq >= 0);

    eventCount = kevent(kq, NULL, 0, &event, 1, &timeout);
    assert( (eventCount >= 0) && (eventCount < 2) );

    if (eventCount == 1) {
        NSLog(@"dir changed");
    }    

    CFFileDescriptorEnableCallBacks(self->_kqRef, kCFFileDescriptorReadCallBack);
}

static void KQCallback(CFFileDescriptorRef kqRef, CFOptionFlags callBackTypes, void *info)
{
    ViewController *    obj;

    obj = (ViewController *) info;
    assert([obj isKindOfClass:[ViewController class]]);
    assert(kqRef == obj->_kqRef);
    assert(callBackTypes == kCFFileDescriptorReadCallBack);

    [obj kqueueFired];
}

- (IBAction)testAction:(id)sender
{
    #pragma unused(sender)
    NSString *              docPath;
    int                     dirFD;
    int                     kq;
    int                     retVal;
    struct kevent           eventToAdd;
    CFFileDescriptorContext context = { 0, self, NULL, NULL, NULL };
    CFRunLoopSourceRef      rls;

    docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    assert(docPath != 0);

    NSLog(@"%@", docPath);

    dirFD = open([docPath fileSystemRepresentation], O_EVTONLY);
    assert(dirFD >= 0);

    kq = kqueue();
    assert(kq >= 0);

    eventToAdd.ident  = dirFD;
    eventToAdd.filter = EVFILT_VNODE;
    eventToAdd.flags  = EV_ADD | EV_CLEAR;
    eventToAdd.fflags = NOTE_WRITE;
    eventToAdd.data   = 0;
    eventToAdd.udata  = NULL;

    retVal = kevent(kq, &eventToAdd, 1, NULL, 0, NULL);
    assert(retVal == 0);

    assert(self->_kqRef == NULL);

    self->_kqRef = CFFileDescriptorCreate(NULL, kq, true, KQCallback, &context);
    assert(self->_kqRef != NULL);

    rls = CFFileDescriptorCreateRunLoopSource(NULL, self->_kqRef, 0);
    assert(rls != NULL);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);

    CFRelease(rls);

    CFFileDescriptorEnableCallBacks(self->_kqRef, kCFFileDescriptorReadCallBack);
}
like image 176
Alex Reynolds Avatar answered Oct 12 '22 16:10

Alex Reynolds


Here's an alternative solution using Grand Central Dispatch (GCD) that allows you to receive file change notifications from NSNotificationCenter:

Add these variables to the class's interface:

// Dispatch queue
dispatch_queue_t _dispatchQueue;

// A source of potential notifications
dispatch_source_t _source;

Add the following code to the implementation:

    #define fileChangedNotification @"fileChangedNotification"

    // Get the path to the home directory
    NSString * homeDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

    // Create a new file descriptor - we need to convert the NSString to a char * i.e. C style string
    int filedes = open([homeDirectory cStringUsingEncoding:NSASCIIStringEncoding], O_EVTONLY);

    // Create a dispatch queue - when a file changes the event will be sent to this queue
    _dispatchQueue = dispatch_queue_create("FileMonitorQueue", 0);

    // Create a GCD source. This will monitor the file descriptor to see if a write command is detected
    // The following options are available

    /*!
     * @typedef dispatch_source_vnode_flags_t
     * Type of dispatch_source_vnode flags
     *
     * @constant DISPATCH_VNODE_DELETE
     * The filesystem object was deleted from the namespace.
     *
     * @constant DISPATCH_VNODE_WRITE
     * The filesystem object data changed.
     *
     * @constant DISPATCH_VNODE_EXTEND
     * The filesystem object changed in size.
     *
     * @constant DISPATCH_VNODE_ATTRIB
     * The filesystem object metadata changed.
     *
     * @constant DISPATCH_VNODE_LINK
     * The filesystem object link count changed.
     *
     * @constant DISPATCH_VNODE_RENAME
     * The filesystem object was renamed in the namespace.
     *
     * @constant DISPATCH_VNODE_REVOKE
     * The filesystem object was revoked.
     */

    // Write covers - adding a file, renaming a file and deleting a file...
    _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,filedes,
                                                       DISPATCH_VNODE_WRITE,
                                                       _dispatchQueue);


    // This block will be called when teh file changes
    dispatch_source_set_event_handler(_source, ^(){
        // We call an NSNotification so the file can change can be detected anywhere
        [[NSNotificationCenter defaultCenter] postNotificationName:fileChangedNotification object:Nil];
    });

    // When we stop monitoring the file this will be called and it will close the file descriptor
    dispatch_source_set_cancel_handler(_source, ^() {
        close(filedes);
    });

    // Start monitoring the file...
    dispatch_resume(_source);

    //...

    // When we want to stop monitoring the file we call this
    //dispatch_source_cancel(source);


    // To recieve a notification about the file change we can use the NSNotificationCenter
    [[NSNotificationCenter defaultCenter] addObserverForName:fileChangedNotification object:Nil queue:Nil usingBlock:^(NSNotification * notification) {
        NSLog(@"File change detected!");
    }];
like image 23
James Andrews Avatar answered Oct 12 '22 16:10

James Andrews