1

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];
}
Community
  • 1
  • 1
Nerrolken
  • 1,975
  • 3
  • 24
  • 53

2 Answers2

2

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);
}
Rob Glassey
  • 2,237
  • 20
  • 21
  • Looking at your notesURL above, I think the pattern `*.*` is definitely causing your bother as you haven't added an extension to your filename. Try `NSString* filePattern = @"*";` instead above. – Rob Glassey Jun 16 '13 at 12:25
  • Ok, so here's a thing: I plugged in the code that Rob provided, and I changed the `filePattern` NSString to be just an asterisk. But `logAllCloudStorageKeysForMetadataItem` is never getting called because `resultCount` is 0. How could that be possible, though, when the data is being successfully downloaded at launch, and uploaded whenever it is changed? Clearly something is there... To answer your question about my URL, I use `NSURL *url = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; return [url URLByAppendingPathComponent:kAllNotes];`, where "kAllNotes" = "notes". – Nerrolken Jun 16 '13 at 19:22
  • 1
    Use [developer.icloud.com](http://developer.icloud.com) to see what files are actually being uploaded to iCloud. Actually, I've just noticed, your URL will create the file in the ROOT of the ubiquity container - but your search scope for the query is set to `NSMetadataQueryUbiquitousDocumentsScope` - this means it's searching in the Documents/ folder ONLY - you need to use `NSMetadataQueryUbiquitousDataScope` to search all files held outside Documents/. With this change, you should find the query fires every time a file is changed within the defined search scope of the query. – Rob Glassey Jun 16 '13 at 22:03
  • HOLY CRAP I THINK IT'S WORKING!!!!! I'm getting updates whenever I change the file from the iOS app. Four days of working on this one function, and now I think it may be done. THANK YOU!!!! – Nerrolken Jun 16 '13 at 22:14
  • Hurrah! Four days? Is that all? You're actually doing really well! iCloud is never as straightforward as you think it's going to be when you first start... – Rob Glassey Jun 16 '13 at 22:16
1

you never allocate your alertQuery....

somewhere you need to alloc,init a NSMetaDataQuery for it

example


NSMetadataQuery* aQuery = [[NSMetadataQuery alloc] init];
if (aQuery) {
    // Search the Documents subdirectory only.
    [aQuery setSearchScopes:[NSArray
                arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];

    // Add a predicate for finding the documents.
    NSString* filePattern = [NSString stringWithFormat:@"*"];
    [aQuery setPredicate:[NSPredicate predicateWithFormat:@"%K LIKE %@",
                NSMetadataItemFSNameKey, filePattern]];

   // Register for the metadata query notifications.
   [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(processFiles:)
        name:NSMetadataQueryDidFinishGatheringNotification
        object:nil];
   [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(processFiles:)
        name:NSMetadataQueryDidUpdateNotification
        object:nil];

   // Start the query and let it run.
   [aQuery startQuery];

}

processFiles method

== your queryDidUpdate

- processFiles(NSNotification*)note {
     [aQuery disableUpdates];

     .....

     [aQuery enableUpdates];
}

NOTE: disable and reenable search there!

see:

http://developer.apple.com/library/ios/#documentation/General/Conceptual/iCloud101/SearchingforiCloudDocuments/SearchingforiCloudDocuments.html

Community
  • 1
  • 1
Daij-Djan
  • 49,552
  • 17
  • 113
  • 135
  • Excellent, thank you! I'm starting to understand everything, but I am getting one error: `Use of undeclared identifier "STEDocFilenameExtension".` Is that something I'm supposed to declare upfront or something? The link you attached seems to treat it as a given, although that article is for iOS and not OS X. Is there an equivalent for Cocoa, or some other step I can take? – Nerrolken Jun 16 '13 at 08:13
  • I got from [here](http://developer.apple.com/library/ios/#documentation/General/Conceptual/iCloud101/CodeListings/CodeListings.html) that they just set `STEDocFilenameExtension` to "stedoc", so I did the same. The code is free of errors now, and matching closely with the walkthrough in the link you attached, but it's still not working... (Thank you again for helping me out, I am currently quite noobish, I know.) – Nerrolken Jun 16 '13 at 08:19
  • 1
    stedoc is likely only valid for their files. you should set it to the extension you use for your files – Daij-Djan Jun 16 '13 at 08:21
  • Ok so the "DidFinishGatheringNotification" line is calling `queryDidUpdate` at launch, but never again. In my wildly uneducated opinion that's because I don't know what file extension I'm using, because I'm new and using a lot of tutorial code. Is there a default extension, or a way to look it up? I've got lines like `[NSKeyedArchiver archivedDataWithRootObject:[Data getAllNotes]];` and `[cloudDoc saveToURL:[self notesURL] ofType:NSPlainTextDocumentType forSaveOperation:NSSaveOperation error:nil];` and `[url URLByAppendingPathComponent:@"notes"];`, but I don't know what to make of them. – Nerrolken Jun 16 '13 at 08:53
  • Hmm. I've cycled through every major file extension I can think of (e.g. plist, txt, dat, data, and so on), along with every one I can find through searching online, and I even tried leaving the extension as an asterisk hoping that it would apply to all files of all filetypes (since I only have one file that I'm altering anyway), but I just can't get a response. Every time, it'll call the `queryDidUpdate` method 1-3 times right at launch, and then never again. Any thoughts? Or is the file extension not my main problem? (PS Thank you again for all your help!) – Nerrolken Jun 16 '13 at 09:40
  • maybe you should post some more of your code .. maybe you do a sscce project !? – Daij-Djan Jun 16 '13 at 09:52
  • I just re-edited my original post with the current code. The results I'm getting when I launch are one NSLog of "Notification created", then 2-3 immediate log messages of "Something changed!!!", and then nothing else regardless of how often I change the synced files from the other side of my app. (And I know the changes are being synced from the other side, because the app I'm working on loads them correctly at launch each time, it just doesn't pick up that they've changed while it's open.) – Nerrolken Jun 16 '13 at 10:04
  • try removing the predicate altogether for now – Daij-Djan Jun 16 '13 at 10:05
  • I commented out the `setPredicate` line (and the two NSStrings preceding it), and then on a second attempt I commented out the `setSearchScopes` line along with it. Both times, same thing: two calls of `queryDidUpdate:` at launch, and then nothing. – Nerrolken Jun 16 '13 at 10:11
  • you have to tell it to `enableUpdates` – Daij-Djan Jun 16 '13 at 12:56