iCloud: How to read in directories created by the user - iphone

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.

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...

NSMetadataQuery doesn't finish gathering (no notification)

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.

iCloud: getting an array of files present in the cloud

I'd like to get a list of all txt files stored in a user's iCloud account. After checking if iCloud is enabled, I tried the following:
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"self ENDSWITH '.txt'"];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
But I get the following console output and a crash:
2011-10-23 21:58:19.587 iCloudText[9922:707] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSComparisonPredicate with left expression which is not NSKeyPathExpressionType given to NSMetadataQuery (SELF ENDSWITH ".txt")'
*** First throw call stack:
(0x30ef58bf 0x3809a1e5 0x30ef57b9 0x30ef57db 0x37f4e725 0x37f4b0b5 0x29b3 0x2c4b 0x32ec77eb 0x32ec13bd 0x32e8f921 0x32e8f3bf 0x32e8ed2d 0x33a7ae13 0x30ec9553 0x30ec94f5 0x30ec8343 0x30e4b4dd 0x30e4b3a5 0x32ec0457 0x32ebd743 0x2437 0x23dc)
terminate called throwing an exception(gdb)
What am I doing wrong when defining the predicate? I've read the docs but can't figure out where I went wrong. Any help would be very much appreciated!
Try using [NSPredicate predicateWithFormat:#"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey] as your predicate. SELF doesn't refer to the filename in this case.

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 "==".

MPMoviePlayerViewController play movie from Documents directory - objective-c

I have subfolder called "Video" in Documents folder. And there is somevideo.mp4 file. I have full path to this file but MPMoviePlayerViewController doesn't want to play video from local. Here is code:
NSString *path = [[self getVideoFolderPath] stringByAppendingPathComponent:#"somevideo.mp4"];
NSURL *fURL = [NSURL fileURLWithPath:path];
MPMoviePlayerViewController *videoPlayerView = [[MPMoviePlayerViewController alloc] initWithContentURL:fURL];
[self presentMoviePlayerViewControllerAnimated:videoPlayerView];
[videoPlayerView.moviePlayer play];
It doesn't play. Appears white screen and nothing else. This code works with remote video, when i put URL to website with video file, but local doesn't play. How to get correct file path to video file?
Are you using iOS prior to 4.3? If so, you need to do this
[videoPlayerView setContentURL: fURL];
Here is a snippet of code from where I play video from the documents folder. One of the things I would recommend is to avoid some of the autorelease objects. Especially on NSURL when you have viewcontrollers. I know it should be safe but I've never found it to be 100%
if ( [[NSFileManager defaultManager] fileExistsAtPath:fullpathVideoFile] == NO )
{
NSLog(#"No video file found");
return self;
}
playbackURL = [[NSURL alloc] initFileURLWithPath:fullpathVideoFile isDirectory:NO];
if ( playbackURL == nil )
{
NSLog(#"playbackURL == nil");
return self;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerLoadedMovie:) name:#"MPMoviePlayerLoadStateDidChangeNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerStartedMovie:) name:#"MPMoviePlayerNowPlayingMovieDidChangeNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerFinishedMovie:) name:#"MPMoviePlayerPlaybackDidFinishNotification" object:nil];
[self setContentURL:playbackURL];
In my case, I created a subclass of MPMoviePlayerController (as I generally do with all view controllers) so the references to 'self' would be to the MPMoviePlayerController object instance
Strange. The code you put seems to be correct. Make sure the path to the audio you are trying to retrieve isn't broken.