Lost data while moving file out of iCloud that was not yet completely downloaded (no error reported)

StackOverflow https://stackoverflow.com/questions/22638379

Question

I have a Mac app with iCloud integration. It's not based on NSDocument and I handle moving files in and out of iCloud myself via [NSFileManager setUbiquitous:…]. Here's what I ran into:

  1. I added a large (15 MB) document to my app while connected to iCloud
  2. Waited for document to be completely uploaded to iCloud
  3. Now I signed out of my iCloud account on my Mac → the file was removed from the Mac
  4. I opened my app, signed back in to iCloud, the file appeared on disk
  5. Quickly, through my app I moved all documents out of iCloud to the local disk (internally using [NSFileManager setUbiquitous:NO…]

The large document was not copied to the local disk (I suspect because it was not yet downloaded 100% from iCloud), but it also disappeared from iCloud. No way to recover the data. There was no error reported by NSFileManager.

Here is the relevant code:

NSArray *files = [fileManager contentsOfDirectoryAtURL:iCloudDataFolderURL
                            includingPropertiesForKeys:nil options:0 error:&error];

for (NSURL *fileURL in files) {
    // figure out URLs […]
    if (![fileManager setUbiquitous:NO 
                          itemAtURL:iCloudFileURL 
                     destinationURL:localDocumentURL 
                              error:&error]) {
        hadError = YES;
        NSLog(@"Error moving file from iCloud: %@ to local storage: %@ Error: %@",
               iCloudFileURL, localDocumentURL, error);
    }
}

I would have expected the call to [NSFileManager setUbiquitous:NO…] to either block or fail if the file is not completely on the local disk. Instead I end up with a file wrapper that shows a file size of 15 MB in Finder, but is actually empty.

What is a safe way to move documents out of iCloud to the local disk?

Was it helpful?

Solution

You can just use NSFileCoordinator and NSFileManager methods to move the files in and out. I don't know if it is safer, but it should give you more control over error conditions and what should happen when something goes wrong.

For an example, take a look at the iCloud backend of the Ensembles framework here (Disclosure: it is my project). Search for uploadLocalFile:... and downloadFromPath:.... They are particularly advanced methods, with timeouts, but the idea is the same.

First, create a file coordinator.

NSFileCoordinator *coordinator = 
     [[NSFileCoordinator alloc] initWithFilePresenter:nil];

Then copy or move the file.

    [coordinator coordinateReadingItemAtURL:fromURL options:0   
        writingItemAtURL:toURL 
        options:NSFileCoordinatorWritingForReplacing 
        error:&fileCoordinatorError 
        byAccessor:^(NSURL *newReadingURL, NSURL *newWritingURL) {
        [fileManager removeItemAtPath:newWritingURL.path error:NULL];
        [fileManager copyItemAtPath:newReadingURL.path 
            toPath:newWritingURL.path error:&fileManagerError];
    }];

If you don't want to program this all yourself, I split off a class that may help (iCloudAccess).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top