Home > Articles > Mobile Application Development & Programming

  • Print
  • + Share This
This chapter is from the book

Recipe: Monitoring the Documents Folder

At some point in the future, you will be able to use a simple NSMetadataQuery monitor to watch your Documents folder and report updates. At the time this book was written, that metadata surveillance was not yet extended beyond iCloud for use with other folders. Code ported from OS X fails to work as expected on iOS. As I write, there are precisely two available search domains for iOS: the ubiquitous data scope, and the ubiquitous documents scope (i.e., iCloud and iCloud). See Chapter 18, “iCloud Basics,” for examples of using metadata queries.

Until that functionality arrives in iOS, you can still fall back to kqueue. This older technology provides scalable event notification (and, apparently, what powers Apple’s Grand Central Dispatch). With kqueue, you can monitor add and clear events in the Documents folder. This roughly equates to looking for files being added and deleted, which are the primary kinds of updates you want to react to. Recipe 16-3 presents a kqueue implementation for watching the Documents folder.

The following snippet demonstrates how you might use this watcher to update a file list in your GUI. Whenever the contents change, an update notification allows the app to refresh those directory contents listings.

- (void) contentsChanged: (NSNotification *) notification

{

    NSString *docPath =

        [NSSearchPathForDirectoriesInDomains(

            NSDocumentDirectory, NSUserDomainMask, YES)

            objectAtIndex:0];

    NSArray *array = [[NSFileManager defaultManager]

        contentsOfDirectoryAtPath:docPath error:nil];

    textView.text = [array componentsJoinedByString:@"\n"];

}



- (void) loadView

{

    [super loadView];

    self.view.backgroundColor = [UIColor whiteColor];



    // Establish a text view

    textView = [[UITextView alloc] initWithFrame:CGRectZero];

    textView.font = [UIFont fontWithName:@"Futura"

        size: IS_IPAD ? 32.0f : 16.0f];

    textView.delegate = self;

    [self.view addSubview:textView];



    // Start monitoring

    [[NSNotificationCenter defaultCenter]

        addObserver:self selector:@selector(contentsChanged:)

        name:kDocumentChanged object:nil];

    NSString *docPath =

        [NSSearchPathForDirectoriesInDomains(

            NSDocumentDirectory, NSUserDomainMask, YES)

            objectAtIndex:0];

    helper = [DocWatchHelper watcherForPath:docPath];



    [self contentsChanged:nil];

}

This recipe is best tested by connecting a device to iTunes and adding and removing items. The device’s onboard file list should update to reflect those changes in real time.

Recipe 16-3. Using a kqueue File Monitor

#import <fcntl.h>

#import <sys/event.h>



#define kDocumentChanged 
    @"DocumentsFolderContentsDidChangeNotification"



@interface DocWatchHelper : NSObject

{

    CFFileDescriptorRef kqref;

    CFRunLoopSourceRef  rls;

}

@property (strong) NSString *path;

+ (id) watcherForPath: (NSString *) aPath;

@end



@implementation DocWatchHelper

@synthesize path;



- (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)

        [[NSNotificationCenter defaultCenter]

            postNotificationName:kDocumentChanged

            object:self];



    CFFileDescriptorEnableCallBacks(self->kqref,

        kCFFileDescriptorReadCallBack);

}



static void KQCallback(CFFileDescriptorRef kqRef,

    CFOptionFlags callBackTypes, void *info)

{

    DocWatchHelper *helper =

        (DocWatchHelper *)(__bridge id)(CFTypeRef) info;

    [helper kqueueFired];

}



- (void) beginGeneratingDocumentNotificationsInPath:

    (NSString *) docPath

{

    int                     dirFD;

    int                     kq;

    int                     retVal;

    struct kevent           eventToAdd;

    CFFileDescriptorContext context =

        {  0, (void *)(__bridge CFTypeRef) self,

            NULL, NULL, NULL };



    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);



    self->kqref = CFFileDescriptorCreate(NULL, kq,

        true, KQCallback, &context);

    rls = CFFileDescriptorCreateRunLoopSource(

        NULL, self->kqref, 0);

    assert(rls != NULL);



    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,

        kCFRunLoopDefaultMode);

    CFRelease(rls);



    CFFileDescriptorEnableCallBacks(self->kqref,

        kCFFileDescriptorReadCallBack);

}



- (void) dealloc

{

    self.path = nil;

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), rls,

        kCFRunLoopDefaultMode);

    CFFileDescriptorDisableCallBacks(self->kqref,

        kCFFileDescriptorReadCallBack);

}



+ (id) watcherForPath: (NSString *) aPath

{

    DocWatchHelper *watcher = [[self alloc] init];

    watcher.path = aPath;

    [watcher beginGeneratingDocumentNotificationsInPath:aPath];

    return watcher;

}

@end
  • + Share This
  • 🔖 Save To Your Account