Monday, November 21, 2011

iCloud – file backup with no UIDocument whistles. Part I.

I’m on a quest to add a backup feature to one of the iOS apps of mine (blocnote). App is only provided for iOS 5+ and my first option to use would be iCloud over Dropbox et al. I don’t want to use a UIDocument as well, as here it is only about simple file backup, no intent to keep this file in sync.

After reading what I could (as Apple docs on iCloud can’t really be taken as a good reference):

http://www.raywenderlich.com/6015/beginning-icloud-in-ios-5-tutorial-part-1
http://www.raywenderlich.com/6031/beginning-icloud-in-ios-5-tutorial-part-2
http://stackoverflow.com/questions/7795629/icloud-basics-and-code-sample

First stop would be to satisfy a requirement to create a single backup file from a sqlite db and put into the iCloud.

Providing some very raw bits of code here, only building proof of concept at this stage!

Checking for a iCloud availability:

+ (bool) isiCloudAvailable
{
    NSURL *ubiq = [[NSFileManager defaultManager] 
                   URLForUbiquityContainerIdentifier:nil];
    if (ubiq) {
        NSLog(@"iCloud access at %@", ubiq);
        return true;
    } else {
        NSLog(@"No iCloud access");
        return false;
    }
}

Getting db file copied inside the same documents directory:

NSString *bName = [NSString stringWithFormat:@"%@_%@%@",backupName,[Formatter dateToBackupNameString:[NSDate date]],@".db"];
    
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentFolderPath = [searchPaths objectAtIndex: 0];
    
    NSString *backupPath = [documentFolderPath stringByAppendingPathComponent:bName];
    NSURL *backupUrl = [NSURL URLWithString:backupPath];
    
    NSError *copyError = nil;
    NSError *removeBckError = nil;
 
    if ([[NSFileManager defaultManager] fileExistsAtPath:backupPath]) {
        bool success = [[NSFileManager defaultManager] removeItemAtPath: backupPath error:&removeBckError];
        if (!success) {
            NSLog(@"Bck cleanup error: %@", [removeBckError localizedDescription]);
        }
    }
    BOOL copiedBackupDb = [[NSFileManager defaultManager] copyItemAtPath:dbFilePath toPath:backupPath error:&copyError];
    if (!copiedBackupDb)
    {
        NSLog(@"bck db copy error: %@", [copyError localizedDescription]);
        return NO;
    }

And moving a copy to iCloud:

    NSFileManager*  fm = [NSFileManager defaultManager];
    
    NSURL *ubiq = [[NSFileManager defaultManager] 
                   URLForUbiquityContainerIdentifier:nil];
    
    if (ubiq == nil) {
        return NO;
    }
    
    NSError *theError = nil;
    
    
    [fm setUbiquitous:true itemAtURL:backupUrl destinationURL:[[ubiq URLByAppendingPathComponent:@"Documents" isDirectory:true] URLByAppendingPathComponent:bName] error:&theError];
    
    if (theError != nil) {
        NSLog(@"iCloud error: %@", [theError localizedDescription]);
    }

Note that apple docs say you should not be using setUbiquitous on the main thread to avoid deadlocks. Plus there is no indication of progress is provided by this code yet to user. That will come…

Attention should be paid where exactly you move your file in iCloud. Without that destinationURL:[[ubiq URLByAppendingPathComponent:@"Documents" isDirectory:true] URLByAppendingPathComponent:bName] your file will end up in the common app documents&data container and user will have no ability to delete a separate backup file:

image

When put inside Documents in iCloud, files can be seen like:

image

And can be deleted by user out of iCloud nicely one by one.

This is it for today’s part, going for more exploration and will share as it goes.

Yours, Stan.

3 comments:

Jackson said...

Thanks for the code. This is working fine while creating a new document in the iCloud and retrieving. But if I would like to upload a document from document sandbox to iCloud, I am unable to do. Can you please help.

stan said...

Hey Jackson,

In the post, i do upload a copy of a db from the sandbox (copied to a Documents sandbox folder as well).
I do observe that that "copy" of uploaded db stays in some hidden to me place on the device. Though after app deletion, it is not on the device anymore and can be restored from iCloud.
Where are you stack?

Mark said...

This was very helpful and I have it working with my app.

I am wondering though how I can overwrite the same file in iCloud instead of creating multiple new files (appended with the date) each time?

So in other words, I just want to backup to and overwrite the same file in iCloud each time without generating multiple files. Any ideas would be greatly appreciated.

Thanks so much.