xcodecocoaicloudnsmetadataquery

Setting up NSMetadataQueryDidUpdateNotification for a simple response


I'm trying to get up and running with an NSMetadataQueryDidUpdateNotification on an OS X app, to alert me when a file in my iCloud ubiquity container is updated. I've been doing a lot of research (including reading other Stack answers like this, this, this, and this), but I still don't have it quite right, it seems.

I've got a "CloudDocument" object subclassed from NSDocument, which includes this code in the H:

@property (nonatomic, strong) NSMetadataQuery *alertQuery;

and this is the M file:

@synthesize alertQuery;

-(id)init {
    self = [super init];
    if (self) {
        if (alertQuery) {
            [alertQuery stopQuery];
        } else {
            alertQuery = [[NSMetadataQuery alloc]init];
        }

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:nil];
        NSLog(@"Notification created");

        [alertQuery startQuery];
    }
    return self;
}

-(void)queryDidUpdate:(NSNotification *)notification {
    NSLog(@"Something changed!!!");
}

According to my best understanding, that should stop a pre-existing query if one is running, set up a notification for changes to the ubiquity container, and then start the query so it will monitor changes from here on out.

Except, clearly that's not the case because I get Notification created in the log on launch but never Something changed!!! when I change the iCloud document.

Can anyone tell me what I'm missing? And if you're extra-super-sauce awesome, you'll help me out with some code samples and/or tutorials?

Edit: If it matters/helps, there is only one file in my ubiquity container being synced around. It's called "notes", so I access it using the URL result from:

+(NSURL *)notesURL {
    NSURL *url = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    return [url URLByAppendingPathComponent:kAllNotes];
}

where "kAllNotes" is set with #define kAllNotes @"notes".

EDIT #2: There have been a lot of updates to my code through my conversation with Daij-Djan, so here is my updated code:

@synthesize alertQuery;

-(id)init {
    self = [super init];
    if (self) {
        alertQuery = [[NSMetadataQuery alloc] init];
        if (alertQuery) {
            [alertQuery setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];

            NSString *STEDocFilenameExtension = @"*";
            NSString* filePattern = [NSString stringWithFormat:@"*.%@", STEDocFilenameExtension];
            [alertQuery setPredicate:[NSPredicate predicateWithFormat:@"%K LIKE %@", NSMetadataItemFSNameKey, filePattern]];
        }

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidUpdate:) name:NSMetadataQueryDidFinishGatheringNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:nil];

        NSLog(@"Notification created");

        [alertQuery startQuery];
    }
    return self;
}

-(void)queryDidUpdate:(NSNotification *)notification {
    [alertQuery disableUpdates];

    NSLog(@"Something changed!!!");

    [alertQuery enableUpdates];
}

Solution

  • How do you save your document - what url do you give it? Unless you give it an extension yourself, it won't automatically be given one - so your *.* pattern will never match a file that does not have an extension. Try * as the pattern and see what happens.

    Also, it helps to log what is happening within queryDidUpdate, until you've worked out exactly what's going on :

    Try something like:

    -(void)queryDidUpdate:(NSNotification *)notification {
        [alertQuery disableUpdates];
    
        NSLog(@"Something changed!!!");
    
        // Look at each element returned by the search
        // - note it returns the entire list each time this method is called, NOT just the changes
        int resultCount = [alertQuery resultCount];
        for (int i = 0; i < resultCount; i++) {
            NSMetadataItem *item = [alertQuery resultAtIndex:i];
            [self logAllCloudStorageKeysForMetadataItem:item];
        }
    
        [alertQuery enableUpdates];
    }
    
    - (void)logAllCloudStorageKeysForMetadataItem:(NSMetadataItem *)item
    {
        NSNumber *isUbiquitous = [item valueForAttribute:NSMetadataItemIsUbiquitousKey];
        NSNumber *hasUnresolvedConflicts = [item valueForAttribute:NSMetadataUbiquitousItemHasUnresolvedConflictsKey];
        NSNumber *isDownloaded = [item valueForAttribute:NSMetadataUbiquitousItemIsDownloadedKey];
        NSNumber *isDownloading = [item valueForAttribute:NSMetadataUbiquitousItemIsDownloadingKey];
        NSNumber *isUploaded = [item valueForAttribute:NSMetadataUbiquitousItemIsUploadedKey];
        NSNumber *isUploading = [item valueForAttribute:NSMetadataUbiquitousItemIsUploadingKey];
        NSNumber *percentDownloaded = [item valueForAttribute:NSMetadataUbiquitousItemPercentDownloadedKey];
        NSNumber *percentUploaded = [item valueForAttribute:NSMetadataUbiquitousItemPercentUploadedKey];
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
    
        BOOL documentExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];
    
        NSLog(@"isUbiquitous:%@ hasUnresolvedConflicts:%@ isDownloaded:%@ isDownloading:%@ isUploaded:%@ isUploading:%@ %%downloaded:%@ %%uploaded:%@ documentExists:%i - %@", isUbiquitous, hasUnresolvedConflicts, isDownloaded, isDownloading, isUploaded, isUploading, percentDownloaded, percentUploaded, documentExists, url);
    }