NSMetadataQuery doesn't finish gathering (no notification) - iphone

I'm making a backup managrer for my App (via iCloud). I did some tests and the basics worked. But few days later it stopped. I'm using NSMetadataQuery for searching if backup file exists. My backup files are named e.g. Backup29112011154133.xml where numbers represent date of the backup (formatted as ddMMyyyyHHmmss). I check for it in -viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
[self checkForRemoteFile];
}
- (void)checkForRemoteFile {
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"%K like 'Backup*'",NSMetadataItemFSNameKey];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud is unavailable at the moment" message:nil delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert setTag:TAG_ALERT_NOICLOUD];
[alert show];
}
}
- (void)queryDidFinishGathering:(NSNotification *)notif {
NSMetadataQuery *query = [notif object];
[query disableUpdates];
[query stopQuery];
[self loadRemoteFile:query];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
}
- (void)loadRemoteFile:(NSMetadataQuery *)query {
if ([query resultCount] == 1) {
canRestore = YES;
NSMetadataItem *item = [query resultAtIndex:0];
// parse the backup file
[self.tableView reloadData];
} else {
canRestore = NO;
modifDate = #"never";
backupInfoLoaded = YES;
[self.tableView reloadData];
}
}
The problem is that - (void)queryDidFinishGathering:(NSNotification *)notif is never executed. I put breakpints and NSLogs ion there but nothing happend.
I also tried to check for other notifications e.g. 'query did start gathering' and 'query process'. Only 'query did start' notification is posted.
I also have AppID with iCloud registered and entitlements file attached.
Can you help me out what's going on? Maybe I missed something?

First of all NSMetadataQuery doesn't works if startQuery was called not from the MaintThread.
There is possibility that predicate fails for every path also.
Following code works for me.
NSURL *mobileDocumentsDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
...
query.predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%%K like \"%#*\"", [mobileDocumentsDirectoryURL path]], NSMetadataItemPathKey];
[query startQuery];

FIXED by creating ivar for NSMetadataQuery.
I don't know why the application can't read data without NSMetadataquery ivar.

Unfortunately there have been many problems with iCloud and using NSMetaDataQuery. To be honest with you the best source as of now for all your iCloud related questions is the Apple Developer Forums. Today Apple released iOS 5.1 beta, and the release notes STILL say that NSMetaDataQuery isn't functioning properly. It's extremely frustrating that iCloud still isn't working properly, but sadly there's nothing we can do.

This problem still persists. I have been able to trace it to the following divergence:
If you limit your search predicate on the query to the name key,
for example
[NSPredicate predicateWithFormat:#"%K like[cd] %#", NSMetadataItemFSNameKey, #"*"]
then it will work as expected (posting all four query lifecycle notifications).
If, however, you try either a compound predicate or try to work with the path,
as in
[NSPredicate predicateWithFormat:#"%K BEGINSWITH %#", NSMetadataItemPathKey, [self cloudDocumentsURL].path]
OR
[NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:namePred, pathPred, nil]];
Then only the initial notification will be posted.
I have tried literally hundreds of combinations of these configurable variables in multiple test and intended-for-production apps over the last year and have yet to find a counterexample to this hypothesis.
Unfortunately, NSMetadataQuery just doesn't work for ubiquitous stores (as of 10.8).
My workaround is to get the raw results from the query and work mostly on a bound NSArrayController which can have its results filtered. This will mean refactoring away from query.results for most existing code and there is a performance hit (presumably) but it is the only way I have found. I would love an alternative.

Related

How to force iCloud with core data to do sync?

I'm using iCloud with core data to sync data of my App.
I tried everything and finally It works perfectly.
but I have one question.
Is there any way that saying to iCloud to sync?
It sync when app begin, but the other time. It seems like that it sync randomly. I can't find the way to handle it myself.
any help?
Thanks.
I figured out how to force a core data store to sync to iCloud. You just need to "touch" any receipt files in the Data folder in the Ubiquity filesystem. The code below will do this. To force a sync, call the syncCloud method. For every "receipt" file it finds, it will update the files modification time stamp to the current time, essentially doing a "touch" on the file. As soon as this happens, iCloud will check and sync any core data files that need synchronization in iCloud. Near as I can tell, this is how the simulator or Xcode does it when you select the "Trigger iCloud Sync" option.
#property (strong, nonatomic) NSMetadataQuery *query;
-(void) init
{
self.query = [[NSMetadataQuery alloc] init];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(didFinishMetadataQuery)
name: NSMetadataQueryDidFinishGatheringNotification
object: self.query];
}
-(void) didFinishMetadataQuery
{
// Query completed so mark it as completed
[self.query stopQuery];
// For each receipt, set the modification date to now to force an iCloud sync
for(NSMetadataItem *item in self.query.results)
{
[[NSFileManager defaultManager] setAttributes: #{NSFileModificationDate:[NSDate date]}
ofItemAtPath: [[item valueForAttribute: NSMetadataItemURLKey] path]
error: nil];
}
}
-(void) syncCloud
{
// Look for files in the ubiquity container data folders
[self.query setSearchScopes:#[NSMetadataQueryUbiquitousDataScope]];
// Look for any files named "receipt*"
[self.query setPredicate:[NSPredicate predicateWithFormat:#"%K like 'receipt*'", NSMetadataItemFSNameKey]];
// Start the query on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
BOOL startedQuery = [self.query startQuery];
if (!startedQuery)
{
NSLog(#"Failed to start query.\n");
return;
}
});
}
You have to force any NSURL item to be cached in iCloud with this method:
- (BOOL)addBackupAttributeToItemAtURL:(NSURL *)URL
{
NSLog(#"URL: %#", URL);
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: NO]
forKey: NSURLIsExcludedFromBackupKey error: &error];
return success;
}
You're need to launch this method on every directory and file you're downloading/creating in Documents or Library directory.
I don't believe that you have any control over when the sync will occur when using iCloud and core-data. With UIDocument and iCloud I have used startDownloadingUbiquitousItemAtURL with varying degrees of success but for core-data I don't think there is any equivalent -- I would love to hear someone tell me otherwise however...

Is it possible to use NSMetadataQuery when iCloud is disabled?

I can't seem to get NSMetadataQuery to work when I disable iCloud. I put in a valid search URL, but it never registers as finished:
//Check for iCloud
NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSLog(#"iCloud access at %#", ubiq);
self.query = [[[NSMetadataQuery alloc] init] autorelease];
[self.query setSearchScopes:[NSArray arrayWithObject:
NSMetadataQueryUbiquitousDataScope]];
_isiCloudEnabled = YES;
} else {
NSLog(#"No iCloud access");
//Get the doc directory
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
self.query = [[[NSMetadataQuery alloc] init] autorelease];
[self.query setSearchScopes:[NSArray arrayWithObjects:
[NSURL fileURLWithPath:path],nil]];
_isiCloudEnabled = NO;
}
NSPredicate *pred = [NSPredicate predicateWithFormat:
#"%K like %#", NSMetadataItemFSNameKey, #"*.adoc"];
[self.query setPredicate:pred];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(queryDidFinishGathering:)
name:NSMetadataQueryDidFinishGatheringNotification
object:self.query];
[self.query startQuery];
queryDidFinishGathering: never gets called. When iCloud is enabled, it always gets called. Any idea why?
I was facing the same problem but I am using the ARC in project. This is solved by setting the ivar to self.query variable.
#property (nonatomic, strong) NSMetadataQuery *query;
I think for your problem as you are not using the ARC, you may need to do following things:
remove the autorelease and manually release when finish the work.
you need to set the property
#property (nonatomic, retain) NSMetadataQuery *query;
As of iOS5, NSMetadataQuery 's search scope can only be set to ubiquitous things (NSMetadataQueryUbiquitousDocumentsScope and NSMetadataQueryUbiquitousDataScope) so using it with iCloud disabled would be unuseful.
As you are probably guessing the reason queryDidFinishGathering is never called is because of your query scope, local directories are not supported yet (but suspiciously not throwing exceptions or errors :) )
In my opinion NSMetadataQuery class is not fully ported to iOS, in OSX more scopes can be set , more kinds of NSPredicate can be set, NSSortDescriptors work, etc.

iCloud: How to read in directories created by the user

I would like to read in a list of all directories that are created either by the user or the app in iCloud's Mobile Documents directory (the one found in Lion under ~/Library/Mobile Documents). Here is an example of how this directory could look like:
I tried the following code, but the query I run will not contain any objects representing my folders (using the NSPredicate predicateWithFormat:#"%K.pathExtension = ''", NSMetadataItemFSNameKey). If I run a query for txt files (using #"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey), I will get 5 objects returned for the txt files respectively. Looking for txt files thus works, but not for directories. Reading through the docs, I noticed that Apple suggests to use NSFileWrapper (File Packages) instead of directories. Is iCloud not able to handle/detect directories created by the user or the app?
Here is my code:
-(void)loadDocument {
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
//Search all files in the Documents directories of the application’s iCloud container directories:
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"%K.pathExtension = ''", NSMetadataItemFSNameKey];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
}
- (void)queryDidFinishGathering:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
[query disableUpdates]; // You should invoke this method before iterating over query results that could change due to live updates.
[query stopQuery]; // You would call this function to stop a query that is generating too many results to be useful but still want to access the available results.
[self loadData:query];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
_query = nil; // we're done with it
}
- (void)loadData:(NSMetadataQuery *)query {
NSLog(#"Query count %i", [query resultCount]);
for (int i=0; i < [query resultCount]; i++) {
NSMetadataItem *item = [query resultAtIndex:i];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
NSLog(#"%i.URL: %#", i, url);
}
}
I had a look at "Manage Storage" in the iClouds Settings in Mac OS X Lion. When I click my application, it will only show the different txt files (plus conflicting versions) and no directories whatsoever. So I have to assume that you can only work with wrappers / file packages but not with directories.

How to search for uploaded files in iCloud

I have succeeded uploading a file to iCloud, which can be seen within icloud space manager(but,what odd is the file name is 'unknown').
I also found a piece of code from apple's document
_query = [[NSMetadataQuery alloc] init];
[_query setSearchScopes:[NSArray arrayWithObjects:NSMetadataQueryUbiquitousDataScope, nil]];
[_query setPredicate:[NSPredicate predicateWithFormat:#"%K == '*.*'", NSMetadataItemFSNameKey]];
NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:#selector(fileListReceived)
name:NSMetadataQueryDidFinishGatheringNotification object:nil];
[notificationCenter addObserver:self selector:#selector(fileListReceived)
name:NSMetadataQueryDidUpdateNotification object:nil];
[_query startQuery];
//
[super viewDidLoad];
-(void)fileListReceived
{
NSArray* queryResults = [_query results];
for (NSMetadataItem* result in queryResults) {
NSString* fileName = [result valueForAttribute:NSMetadataItemFSNameKey];
NSLog(#"fileName = %#", fileName);
}
}
but the result is always 0, no matter NSMetadataQueryUbiquitousDataScope or NSMetadataQueryUbiquitousDocumentsScope.
I also know that icloud has backup function, so does backup has any relation with uploading file by app itself?
In your predicate try to use "like" instead of "==".

NSTask waitUntilExit hanging app on jailbroken iOS

So I've got NSTask to run a script which generates a list of something, into a txt, which I read from. But if I use my current code (below), the alert pops up before the NSTask is finished, thus resulting in a blank alert. I've tried waitUntilExit but that makes the button that invokes this action freeze, but the UI doesn't lock up itself.
- (void) runSupported {
stask = [[NSTask alloc] init];
[stask setLaunchPath:#"/bin/bash"];
NSString *script;
script = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:#"/apps.sh"];
NSArray *sargs = [NSArray arrayWithObjects:script, #"-txt", nil];
[stask setArguments: sargs];
[stask launch];
NSString *apps;
apps = [NSString stringWithContentsOfFile:#"/var/mobile/supported.txt" encoding:NSUTF8StringEncoding error:nil];
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:apps]) {
UIAlertView *supported = [[UIAlertView alloc] initWithTitle:#"App List" message:apps delegate:self cancelButtonTitle:#"Ok!" otherButtonTitles:nil];
[supported show];
[supported release];
} else {
UIAlertView *supported = [[UIAlertView alloc] initWithTitle:#"App List" message:#"Error generating list." delegate:self cancelButtonTitle:#"Ok!" otherButtonTitles:nil];
[supported show];
[supported release];
}
}
Any idea how I'd have the NSTask finish before invoking the alert? Thanks.
Edit: Code with NSNotification:
-(IBAction) supported {
stask = [[NSTask alloc] init];
[stask setLaunchPath:#"/bin/bash"];
NSString *script;
script = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:#"/apps.sh"];
NSArray *sargs = [NSArray arrayWithObjects:script, #"-txt", nil];
[stask setArguments: sargs];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(taskEnded:)
name: NSTaskDidTerminateNotification
object: nil];
[stask launch];
}
- (void)taskEnded:(NSNotification *)notification {
if (stask == [[notification object] terminationStatus]) {
NSString *apps;
apps = [NSString stringWithContentsOfFile:#"/var/mobile/supported.txt" encoding:NSUTF8StringEncoding error:nil];
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:apps]) {
UIAlertView *supported = [[UIAlertView alloc] initWithTitle:#"Apps" message:apps delegate:self cancelButtonTitle:#"Ok!" otherButtonTitles:nil];
[supported show];
[supported release];
} else {
UIAlertView *supported = [[UIAlertView alloc] initWithTitle:#"Apps" message:#"Error generating list." delegate:self cancelButtonTitle:#"Ok!" otherButtonTitles:nil];
[supported show];
[supported release];
}
} else {
NSLog(#"Task failed.");
}
}
Don't use waitUntilExit.
The problem is how to do something after the task finishes without blocking the UI (or freezing that one button). The solution, as for all similar problems, is to be notified when the task finishes, and proceed further (show the alert) in response to that notification.
The notification, in this case, is an NSNotification named NSTaskDidTerminateNotification. When the task exits, for any reason, the NSTask object will post this notification on the default NSNotificationCenter. You can ask the task what its termination status was to determine whether it succeeded, failed, or crashed.
See also: Notification Programming Topics.
Have a look at AMShellWrapper which is based on Apple's Moriarity sample code.
"Connect your own methods to stdout and stderr, get notified on process termination and more."
See: http://www.cocoadev.com/index.pl?NSTask
Don't use waitUntilExit on your main thread. That will block your UI, and freeze your app.
You need to subscribe to the notification NSTaskDidTerminateNotification, which is posted when the task has stopped execution:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(taskDidTerminate:)
name:NSTaskDidTerminateNotification
object:nil];
Note that the notification can be posted if the task completed normally, or as the result of a terminate message:
- (void) taskDidTerminate:(NSNotification *)notification {
if (YOUR_TASK_SUCCESS_VALUE == [[notification object] terminationStatus]) {
NSLog(#"Task succeeded.");
// Here you can add your checks on the creation on the files and user alerts confirmation
} else {
NSLog(#"Task failed.");
}
}
Don't forget to unsubscribe to the notification ; depending on where you subscribe to the notification, a good place would be in your dealloc method:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Update: You expect something that is documented on Mac to work the same on iOS, where it is not documented. Not really surprised it doesn't work.
Have you tried to execute the task - and use the waitUntilExit method - in a background thread?
If you are lucky and it works, don't forget to switch back to the main thread when showing your UIAlert.