6

I am trying to monitor file changes in local and iCloud directories and have implemented the NSFilePresenter protocol methods but the only method that gets called is presentedItemAtURL.

Am I correct in assuming that I should be able to monitor a local or an iCloud directory and get notified any time any process adds, modifies or deletes a file in the directory.

Here is the basic code for the OS X App:

- (void)awakeFromNib {
        _presentedItemURL = myDocumentsDirectoryURL;
        _presentedItemOperationQueue = [[NSOperationQueue alloc] init];
        [_presentedItemOperationQueue setMaxConcurrentOperationCount: 1];
        _fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
}
- (NSURL*) presentedItemURL {
    FLOG(@" called %@", _presentedItemURL);
    return _presentedItemURL;
}

- (NSOperationQueue*) presentedItemOperationQueue {
    FLOG(@" called");
    return _presentedItemOperationQueue;
}

- (void)presentedItemDidChange {
    FLOG(@" called");
    dispatch_async(dispatch_get_main_queue(), ^{
        [self reloadData];
    });
}
-(void)accommodatePresentedItemDeletionWithCompletionHandler:(void (^)(NSError *errorOrNil))completionHandler
{   FLOG(@" called");
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self reloadData];
    }];
    completionHandler(nil);
}
-(void)presentedSubitemDidChangeAtURL:(NSURL *)url {
    FLOG(@" called");
    dispatch_async(dispatch_get_main_queue(), ^{
        [self reloadData];
    });
}
-(void)presentedSubitemDidAppearAtURL:(NSURL *)url {
    FLOG(@" called");
    dispatch_async(dispatch_get_main_queue(), ^{
        [self reloadData];
    });

}
Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76

3 Answers3

4

Long time ago, I know, but perhaps this will still help. NSFilePresenter will only notify you about changes made by another process that makes changes to a directory or file USING AN NSFileCoordinator. If another process (eg: iTunes file sharing) makes changes without an NSFileCoordinator, you won't be notified.

0

This is in no way my final implementation and I will edit/update as I improve. But since there is nil examples on how to do this, i figured i'd share something that works!!! That's right, it works. I am able to read the file in my app, and at the same time make a change in textedit and the changes propagate to my app. Hope this helps bud.

PBDocument.h

@interface PBDocument : NSObject <NSFilePresenter>

@property (nonatomic, strong) NSTextView *textView;

#pragma mark - NSFilePresenter properties
@property (readonly) NSURL *presentedItemURL;
@property (readonly) NSOperationQueue *presentedItemOperationQueue;

- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError *__autoreleasing *)outError textView:(NSTextView*)textView;

@end

PBDocument.m

@interface PBDocument ()

@property (readwrite) NSURL *presentedItemURL;
@property (readwrite) NSOperationQueue *presentedItemOperationQueue;
@property (readwrite) NSFileCoordinator *fileCoordinator;

@end

@implementation PBDocument

- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError *__autoreleasing *)outError textView:(NSTextView*)textView {
    self = [super init];
    if (self) {
        _textView = textView;
        _presentedItemURL = url;

        _presentedItemOperationQueue = [NSOperationQueue mainQueue];

        [NSFileCoordinator addFilePresenter:self];
        _fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];

        [self readWithCoordination];
    }
    return self;
}

- (void)readWithCoordination {
    NSError *error = nil;

    [self.fileCoordinator coordinateReadingItemAtURL:_presentedItemURL options:NSFileCoordinatorReadingWithoutChanges error:&error byAccessor:^(NSURL *newURL) {
        NSLog(@"Coordinating Read");
        NSError *error = nil;
        NSFileWrapper *wrapper = [[NSFileWrapper alloc] initWithURL:newURL options:0 error:&error];

        if (!error) {
            [self readFromFileWrapper:wrapper ofType:[self.presentedItemURL pathExtension] error:&error];
        }

        if (error) @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@", error] userInfo:nil];
    }];

    if (error) @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@", error] userInfo:nil];
}

- (void)presentedItemDidChange {
    [self readWithCoordination];
}

@end
Brad G
  • 2,528
  • 1
  • 22
  • 23
  • Doesn't keeping the URL duplicate what NSDocument does anyway? How does this scale in terms of tracking changes to the file location compared to the standard methods? – ctietze May 03 '16 at 08:03
  • 1
    Yes, keeping track of the URL replicates NSDocument functionality. If i recall correctly, at the time a single window instance represented a NSDocument (I don't remember a way around that) and that didn't fit my requirements (I wanted all documents shown in a hierarchical fashion in my sidebar). While this worked at the time, APIs changed enough that my app doesn't run anymore and there is probably a better way now. It was a pretty slick way to get notified about changes to text files, and have those changes somewhat immediately reflected in my app. – Brad G May 12 '16 at 12:50
0

If it's any help to anyone this is the approach (FSEvents) I ended up using recently for a file sync solution and it seems to work for any file system. I have not done any research recently on NSFileCoordinator to see whether this is better worse or what the use cases are as a comparison.

I also did not test every use case so your mileage may vary.

https://github.com/eonil/FSEvents

Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76