I am unable to write a simple file to the iCloud container from my app on one device and then successfully read that same file the first time my app runs on a second device.
I started this test scenario with an empty Ubiquity Container on a pair of freshly reset devices. Below is a test case code and console logging that demonstrates the problem I’m having. All of the code below gets run on a background thread.
I install and run the test case on the first device. The code looks for a certain file in the ubiquity container and creates it when it can’t find it. Obviously, this is expected the first time it runs on the first device. I was hoping (and expecting) that when the app is freshly installed and runs on a second device, it would be able to find and read the contents of the file created on the first device. It seems that a freshly installed app on a reset device is NEVER able to find and read the existing file and always thinks it needs to create a new one, which unfortunately ends up replacing the file in the ubiquity container if it is already present.
However, after the first attempt (on the second device) fails to find and read the file, if I delete the app, re-install and then re-run it on the same device, it does find the file and is able to read its current contents. It seems like the first run of the app causes the file to be loaded into the ubiquity cache, but not in a state that it can be found by the app. The second run of the app seems to find the file in the cache.
I know you are supposed to start the download of a file on a iOS device, but on the first time for a device, it reports “file not found”. Is there something else that needs to be done here? Or does this seem like a new or known bug?
The following code is used to determine the presence of the token file in the ubiquity container and to make sure it can be read.
NSError *error;
BOOL rc;
NSFileManager *fm = [[NSFileManager alloc] init];
NSURL *ubiquityURL = [fm URLForUbiquityContainerIdentifier:nil];
NSURL *tokenURL = [ubiquityURL URLByAppendingPathComponent:@"iCloudTokenFile"];
NSLog(@"isTokenFilePresentAtUrl: Entered for Device Name: %@", [[UIDevice currentDevice] name]);
error = nil;
rc = [fm startDownloadingUbiquitousItemAtURL:tokenURL error:&error];
NSLog(@"startDownloadingUbiquitousItemAtURL: %d Error: %@(%d)", rc, error.domain, error.code);
rc = [fm isUbiquitousItemAtURL:tokenURL];
NSLog(@"isUbiquitousItemAtURL: %d", rc);
NSNumber *isUbiquitousItem = nil;
error = nil;
rc = [tokenURL getResourceValue:&isUbiquitousItem forKey:NSURLIsUbiquitousItemKey error:&error];
NSLog(@"getResourceValue: %d isUbiquitousItem: %@ Error: %@(%d)", rc, isUbiquitousItem, error.domain, error.code);
NSNumber *isDownloading = nil;
error = nil;
rc = [tokenURL getResourceValue:&isDownloading forKey:NSURLUbiquitousItemIsDownloadingKey error:&error];
NSLog(@"getResourceValue: %d isDownloading: %@ Error: %@(%d)", rc, isDownloading, error.domain, error.code);
NSNumber *isDownloaded = nil;
error = nil;
rc = [tokenURL getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:&error];
NSLog(@"getResourceValue: %d isDownloaded: %@ Error: %@(%d)", rc, isDownloaded, error.domain, error.code);
The following code is used to try to read the token file from ubiquity container.
__block BOOL success = NO;
__block NSError *error = nil;
NSFileCoordinator *fc = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fc coordinateReadingItemAtURL:tokenURL
options:NSFileCoordinatorReadingWithoutChanges
error:&error
byAccessor:^(NSURL *newURL)
{
NSLog(@"readTokenFileFromUrl: Trying to read Token File.");
NSNumber *isDownloaded = nil;
error = nil;
BOOL rc = [tokenURL getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:&error];
NSLog(@"getResourceValue: %d isDownloaded: %@ Error: %@(%d)", rc, isDownloaded, error.domain, error.code);
NSString *uuidString = nil;
NSError *blockerror = nil;
uuidString = [NSString stringWithContentsOfURL:newURL
encoding:NSUTF8StringEncoding
error:&blockerror];
NSLog(@"readTokenFileFromUrl: Token read from file: %@", uuidString);
}];
The following code is used to write a token file to the ubiquity container if unable to read an existing token file. I was expecting that this code would only be run once by the first device.
NSFileCoordinator *fc = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSError *error = nil;
[fc coordinateWritingItemAtURL:tokenURL
options:NSFileCoordinatorWritingForReplacing
error:&error
byAccessor:^(NSURL *newURL)
{
NSUUID *uuid = [[NSUUID alloc] init];
NSString *uuidString = [uuid UUIDString];
NSError *blockerror = nil;
NSLog(@"writeTokenFileToUrl: Writing token to file: %@", uuidString);
[uuidString writeToURL:newURL
atomically:YES
encoding:NSUTF8StringEncoding
error:&blockerror];
}];
The App is installed and run for the first time on “Red iPod touch”.
isTokenFilePresentAtUrl: Entered for Device Name: Red iPod touch
startDownloadingUbiquitousItemAtURL: 0 Error: NSCocoaErrorDomain(4)
isUbiquitousItemAtURL: 0
getResourceValue: 0 isUbiquitousItem: (null) Error: NSPOSIXErrorDomain(2)
getResourceValue: 0 isDownloading: (null) Error: NSPOSIXErrorDomain(2)
getResourceValue: 0 isDownloaded: (null) Error: NSPOSIXErrorDomain(2)
readTokenFileFromUrl: Trying to read Token File.
getResourceValue: 0 isDownloaded: (null) Error: NSPOSIXErrorDomain(2)
readTokenFileFromUrl: Token from File: (null)
readTokenFileFromUrl: Error reading uuid Token from File: NSCocoaErrorDomain(260)
loadiCloudStore: isUbiquitousItemAtURL: Token File actually does not appear to be present.
writeTokenFileToUrl: Writing token to file: 6AAA8D81-4BB5-4BF4-AD31-E152D3BADD13
At this point, the file is verified to be present in the Ubiquity Container via the developer.icloud.com web site. The App is then installed and run for first time on a second device named “Blue iPod touch”.
isTokenFilePresentAtUrl: Entered for Device Name: Blue iPod touch
startDownloadingUbiquitousItemAtURL: 0 Error: NSCocoaErrorDomain(4)
isUbiquitousItemAtURL: 0
getResourceValue: 0 isUbiquitousItem: (null) Error: NSPOSIXErrorDomain(2)
getResourceValue: 0 isDownloading: (null) Error: NSPOSIXErrorDomain(2)
getResourceValue: 0 isDownloaded: (null) Error: NSPOSIXErrorDomain(2)
readTokenFileFromUrl: Trying to read Token File.
getResourceValue: 0 isDownloaded: (null) Error: NSPOSIXErrorDomain(2)
readTokenFileFromUrl: Token from File: (null)
readTokenFileFromUrl: Error reading uuid Token from File: NSCocoaErrorDomain(260)
For this test, the App is stopped after trying to read and not allowed to try to write or replace the token file. The App is then it is killed, deleted and re-installed and run again on Blue iPod touch.
isTokenFilePresentAtUrl: Entered for Device Name: Blue iPod touch
startDownloadingUbiquitousItemAtURL: 1 Error: (null)(0)
isUbiquitousItemAtURL: 1
getResourceValue: 1 isUbiquitousItem: 1 Error: (null)(0)
getResourceValue: 1 isDownloading: 1 Error: (null)(0)
getResourceValue: 1 isDownloaded: 0 Error: (null)(0)
readTokenFileFromUrl: Trying to read Token File.
getResourceValue: 1 isDownloaded: 0 Error: (null)(0)
readTokenFileFromUrl: Token read from file: 6AAA8D81-4BB5-4BF4-AD31-E152D3BADD13
This time it finds and reads the file with the expected contents.
Well, I didn’t give up and I now think I have an acceptable work around for this problem.
The key to the solution is for the second device to be able to successfully detect the token file that was written to the ubiquity container by the first device, and not try to write the file itself.
Here are the steps that work for me:
It seems that the writing of the dummy file to the ubiquity container “kick starts” the meta data query enough to get it to report the existence of the token file on a second device. Without it, the meta data query never reports the existence of the token file on anything other than the first device.