nico9T
nico9T

Reputation: 2706

UIManagedDocument and iCloud: "The provided ubiquity name is already in use."

I am writing an app targeting iOS7.

My testing devices are:

1) an iPhone 5 16GB

2) an iPad 3rd generation 16GB Wi-fi+Cellular.

I am getting mad with UIManagedDocument and iCloud.

I read all the documentation I found, I watched the Stanford CS193P videos and I am following some code by Erica Sadun.

The problem I am not able to solve is that after I create the local CoreData file I can't reopen it or save it because it is in [Closed | SavingError] state.

It is very important to me to have it open and ready because:

1) The first time the user launches the app, it fills the DB with demo data.

2) After the demo data creation the user can create some new data but if the file is in this state it can't save the data created.

3) If the document is in SavingError state the demo data won't be saved.

The first TableViewController in the app calls fetchDataWithBlock:.The block is a simply, standard NSFetchRequest.

This is the code I'm using to set the PSC options for the UIManagedDocument, and to create and open it as suggested by Erica (create, close, reopen). There is a lot of logging in it, to better understand what's going on.

#define DB_LOCAL_FILE_NAME @"CoreDataLocalFile"
#define DB_TRANSACTIONS_LOG_FILE_NAME @"TransactionsLog"


-(NSURL *) dbLocalDirectory
{
    //Returns the application's document directory
    _dbLocalDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                                inDomains:NSUserDomainMask] lastObject];
    return  _dbLocalDirectory;
}


-(NSURL*) iCloudURL
{
    _iCloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    return  _iCloudURL;
}

-(NSURL*) iCloudDataLogFilesURL
{
    _iCloudDataLogFilesURL = [self.iCloudURL URLByAppendingPathComponent:@"CoreDataTransactionsLog"];

    //If the folder is not present, create it.
    if(![[NSFileManager defaultManager] fileExistsAtPath:_iCloudDataLogFilesURL.path]){

        NSLog(@"Creating the iCloudDataLogFilesURL: %@", _iCloudDataLogFilesURL);

        NSError *error;
        if(![[NSFileManager defaultManager] createDirectoryAtPath:_iCloudDataLogFilesURL.path withIntermediateDirectories:YES attributes:nil error:&error]){
            NSLog(@"ERROR creating iCloud folder: %@", error.localizedFailureReason);
        }
    }

    return _iCloudDataLogFilesURL;
}

-(UIManagedDocument *) managedDocument
{
    //Returns the database ManagedDocument

    if (!_managedDocument){

        //Init the document
        _managedDocument = [[UIManagedDocument alloc] initWithFileURL:
            [self.dbLocalDirectory URLByAppendingPathComponent:DB_LOCAL_FILE_NAME]];

    }
    return _managedDocument;
}


-(void) setPersistentStoreOptionsInDocument: (UIManagedDocument*) document
{

    if(self.iCloudDataLogFilesURL){

        NSMutableDictionary *options = [NSMutableDictionary dictionary];

        [options setObject:DB_TRANSACTIONS_LOG_FILE_NAME
                    forKey:NSPersistentStoreUbiquitousContentNameKey];

        [options setObject:self.iCloudDataLogFilesURL
                    forKey:NSPersistentStoreUbiquitousContentURLKey];

        [options setObject:[NSNumber numberWithBool:YES]
                    forKey:NSMigratePersistentStoresAutomaticallyOption];

        [options setObject:[NSNumber numberWithBool:YES]
                    forKey:NSInferMappingModelAutomaticallyOption];

        document.persistentStoreOptions = options;

        //if file exists, use contents of document.fileURL/@"DocumentsMetadata.plist" instead

        NSLog(@"Using iCLoud with PSC Options: %@", document.persistentStoreOptions);
    }else{
        NSLog(@"ERROR. Can't add iCloud options because I don't have a valid iCloudDataLogFilesURL.");
    }
}

-(void) fetchDataWithBlock: (void (^) (void)) fetchingDataBlock
{

    //If the CoreData local file exists then open it and perform the query
    if([[NSFileManager defaultManager] fileExistsAtPath:[self.managedDocument.fileURL path]]){
        NSLog(@"The CoreData local file in the application sandbox already exists. I am opening it.");
        [self.managedDocument openWithCompletionHandler:^(BOOL success) {
            if(success){
                NSLog(@"SUCCESS: The CoreData local file has been opened succesfully. Fetching data.");
                fetchingDataBlock();
            }else{
                NSLog(@"ERROR: Can't open the CoreData local file. Can't fetch the data.");
                NSLog(@"%@", self.managedDocument);
                return;
            }
        }];


    }else{
        NSLog(@"The CoreData local file in the application sandbox did not exist.");
        //1. Create the Core Data local File

            //----> Set the iCloud options
        [self setPersistentStoreOptionsInDocument:_managedDocument];


        [self.managedDocument saveToURL:self.managedDocument.fileURL
                       forSaveOperation:UIDocumentSaveForCreating
                      completionHandler:^(BOOL success) {

            if(success){
                //2. Close the Core Data local File
                NSLog(@"SUCCESS: I created the CoreData local file in the application sandbox. Now I am closing it.");
                [self.managedDocument closeWithCompletionHandler:^(BOOL success) {

                    if(success){
                        //3. Reopen the Core Data local File
                        NSLog(@"SUCCESS: I closed the CoreData local file just created. Now I am reopening it.");

                        [self.managedDocument openWithCompletionHandler:^(BOOL success) {

                            if(success){
                                NSLog(@"SUCCESS: I reopened the CoreData local file just created. Fetching data.");
                                fetchingDataBlock();
                            }else{
                                NSLog(@"ERROR: Can't reopen the CoreData local file just created and then closed. Can't fetch the data.");
                                NSLog(@"%@", self.managedDocument);
                                return;
                            }
                        }];

                    }else{
                        NSLog(@"ERROR: Can't close the CoreData local file just created. Can't fetch the data.");
                        NSLog(@"%@", self.managedDocument);
                        return;
                    }

                }];

            }else{
                NSLog(@"ERROR: Can't create the CoreData local file in the application sandbox. Can't fetch the data.");
                NSLog(@"%@", self.managedDocument);
                return;
            }

        }];

    }
}

These are the logs:

2013-11-16 18:19:18.731 Current iCloud Token:

2013-11-16 18:19:19.001 Using iCLoud with PSC Options: { NSInferMappingModelAutomaticallyOption = 1; NSMigratePersistentStoresAutomaticallyOption = 1; NSPersistentStoreUbiquitousContentNameKey = "TransactionsLog"; NSPersistentStoreUbiquitousContentURLKey = "file:///private/var/mobile/Library/Mobile%20Documents/XXX/CoreDataTransactionsLog/"; }

2013-11-16 18:19:19.003 The CoreData local file in the application sandbox did not exist.

2013-11-16 18:19:19.032 ApplicationDidBecomeActive

2013-11-16 18:19:19.313 -PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:: CoreData: Ubiquity: mobile~D5AEDBB6-EEFC-455C-A52C-91ADDC1BE081:TransactionsLog Using local storage: 1

2013-11-16 18:19:19.771 SUCCESS: I created the CoreData local file in the application sandbox. Now I am closing it.

2013-11-16 18:19:19.778 SUCCESS: I closed the CoreData local file just created. Now I am reopening it.

2013-11-16 18:19:20.073 ERROR: Can't reopen the CoreData local file just created and then closed. Can't fetch the data.

2013-11-16 18:19:20.074 fileURL: file:///var/mobile/Applications/07702036-765D-414C-9E8B-D4C2B4055CB8/Documents/CoreDataLocalFile documentState: [Closed | SavingError]

As you can see the documentState is: [Closed | SavingError]

If I restart the app, it opens the DB without problems, the document state is NORMAL. The problem is that I need it open and ready the first time I run the app and I create the demo data. Then the file has to be immediately saved witht the UIDocumentsaveForOverwriting option.

I can't ask the user to quit the app and then restart it or losing all the dem data because the file won't be saved!

To better understand what's going on I subclassed UIManagedDocument to override handleError:userInteractionPermitted:.

It tells me the document has this error state because "Error Domain=NSCocoaErrorDomain Code=134311 "The provided ubiquity name is already in use.".

2013-11-17 18:33:50.214 [370:1803] ERROR in UIManagedDocument: Error Domain=NSCocoaErrorDomain Code=134311 "The provided ubiquity name is already in use." UserInfo=0x14edadf0 {NSLocalizedDescription=The provided ubiquity name is already in use., NSURL=file:///var/mobile/Applications/A0B371E0-C992-4D57-895A-E2CCB8A35367/Documents/CoreDataLocalFile/StoreContent.nosync/CoreDataUbiquitySupport/mobile~0EDD3A67-63F4-439F-A055-A13808949BBD/TransactionsLog/A9728F87-0F79-4FE3-9B76-AABD3950BB67/store/persistentStore, NSPersistentStoreUbiquitousContentNameKey=TransactionsLog}

Additional Infos:

if I move:

//Set the iCloud options

[self setPersistentStoreOptionsInDocument:_managedDocument];

from before to after the local file creation with the method saveToURL:forSaveOperation:completionHandler: then the UIManagedDocument reopens correctly but it triggers an error code 134030 whenever I try to save it using the same method used for creation but with the value UIDocumentSaveForOverwriting for the forSaveOperation option. The same happens also if instead to overwrite the file with saveToURL:forSaveOperation:completionHandler: I use [self.managedDocument.managedObjectContext save:nil]

Note 1: I commented out all the demo data creation code to be sure it was not the one to blame. As soon as the blank UIManagedDocument is reopened succesfully after its creation I try to save it overwriting.

Note 2: Getting the options from the UIManagedDocument (local file) DocumentMetaData.plist shows that the only option set is NSPersistentStoreUbiquitousContentNameKey. This is weird because I set 4 different options after the file creation.

Any hints on how to fix this?

Thanks

Nicola

Upvotes: 0

Views: 843

Answers (3)

nico9T
nico9T

Reputation: 2706

Finally I fixed it! Maybe something internal in CoreData/UIManagedDocument has changed in the last two years, since Erica's demo code. I simplified the creation/opening/fetching of data not using the close document and subsequent reopening routines. Now the code works.

-(void) fetchDataWithBlock: (void (^) (void)) fetchingDataBlock
{

    //If the CoreData local file exists then open it and perform the query
    if([[NSFileManager defaultManager] fileExistsAtPath:[self.managedDocument.fileURL path]]){
        NSLog(@"The CoreData local file in the application sandbox already exists.");

        if (self.managedDocument.documentState == UIDocumentStateNormal){
            NSLog(@"The CoreData local file it's in Normal state. Fetching data.");
            fetchingDataBlock();
        }else if (self.managedDocument.documentState == UIDocumentStateClosed){
            NSLog(@"The CoreData local file it's in Closed state. I am opening it.");

            [self.managedDocument openWithCompletionHandler:^(BOOL success) {
                if(success){
                    NSLog(@"SUCCESS: The CoreData local file has been opened succesfully. Fetching data.");
                    fetchingDataBlock();
                }else{
                    NSLog(@"ERROR: Can't open the CoreData local file. Can't fetch the data.");
                    NSLog(@"%@", self.managedDocument);
                    return;
                }
            }];
        }else{
            NSLog(@"ERROR: The CoreData local file has an unexpected documentState: %@", self.managedDocument);
        }
    }else{
        NSLog(@"The CoreData local file in the application sandbox did not exist.");
             NSLog(@"Setting the UIManagedDocument PSC options.");
        [self setPersistentStoreOptionsInDocument:self.managedDocument];

        //Create the Core Data local File
        [self.managedDocument saveToURL:self.managedDocument.fileURL
                       forSaveOperation:UIDocumentSaveForCreating
                      completionHandler:^(BOOL success) {

            if(success){

                NSLog(@"SUCCESS: The CoreData local file has been created. Fetching data.");
                fetchingDataBlock();

            }else{
                NSLog(@"ERROR: Can't create the CoreData local file in the application sandbox. Can't fetch the data.");
                NSLog(@"%@", self.managedDocument);
                return;
            }

        }];

    }
}

Upvotes: 0

Duncan Groenewald
Duncan Groenewald

Reputation: 8988

I think I did post this once before but here it is again. A couple of points to note:

  1. This app allows the user to create multiple documents hence the need for the UUID to ensure uniqueness of each iCloud document name.
  2. There are two scenarios for opening files, one when the user initiates it on the devices and the other when the metadata scan picks up a new file in iCloud that does not exist locally.
  3. I don't create anything in the iCloud container, everything gets setup by Core Data for me. I do have to use the metadata scan to compare files in iCloud with files in the local directory and to trigger the creation of a local store if one does not exist.
  4. Only if its a new file created by the user do I add initial setup data to the store, in every other case this setup data will already have been created by the device that created the first instance of the file
  5. The user can't do anything in the app until this initial data has been imported from iCloud, but usually that's not a problem because this will only ever be an issue for files that are being synced from iCloud anyway.
  6. There are situations where the user can cause things to go awry so a number of file management options need to be made available, including recover from the backup (my docs are stored in /Documents which should be backed up), or rebuild from iCloud. Not for the faint hearted!
  7. This is not currently in a production app, its being tested. We have a Mac version of the app which has the sophisticated file management functions and won't be suggesting use of the iPad or iPhone for managing files.

// This gets called when the user has done one of the following: // 1. Created a new file and entered a new file name. We have then created the fileURL // using the /Documents directory, the filename and appending 'UUID'+uuid to ensure that // avoid duplicate file names in case the user used the same file name on another device. // 2. Selected an existing file from the file browser // - (void)createNewFile:(NSURL*)fileURL {

//FLOG(@"createNewFile called with url %@", fileURL);

_creatingNewFile = YES; // Ignore any file metadata scan events coming in while we do this because some iCloud
                        // files get created by Core Data before the local files are created and our scanning
                        // picks up new iCloud files and attempts to create local copies and we don't want this
                        // if this devices is busy creating the new iCloud file

_document = [[OSManagedDocument alloc] initWithFileURL:fileURL];

// Set oberving on this file to monitor the state (we don't use it for anything other than debugging)
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
           selector:@selector(documentStateChanged:)
               name:UIDocumentStateChangedNotification
             object:_document];

_openedVersion = [NSFileVersion currentVersionOfItemAtURL:fileURL];
_openedVersionDate = _openedVersion.modificationDate;
_openedVersionDevice = _openedVersion.localizedNameOfSavingComputer;
//FLOG(@" file  version date: %@", _openedVersionDate);
//FLOG(@" file  version device: %@", _openedVersionDevice);

NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];

[_document setPersistentStoreOptions:@{NSPersistentStoreUbiquitousContentNameKey:fileName,
                                       NSMigratePersistentStoresAutomaticallyOption:@YES,
                                       NSInferMappingModelAutomaticallyOption:@YES,
                                       NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }}];

_managedObjectContext = _document.managedObjectContext;

if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
    //FLOG(@" file exists so open it: %@",fileURL);
    [_document openWithCompletionHandler:^(BOOL success){
        if (!success) {
            // Handle the error.
            LOG(@" error creating file");
        } else {
            //LOG(@" file opened");
            [self fileOpened];  // Now initialise the UI and let the user continue...
        }
    }];

}
else {
    // File does not exist so that means the user has created a new one and we need to
    // load some initialisation data into the Core Data store (codes tables, etc.)
    //
    // At this stage we have a database in memory so we can just use the _document.managedObjectContext
    // to add objects prior to attempting to write to disk.

    //LOG(@" file DOES NOT exist so add initial data");
    [self addInitialData];

    // Just checking if anything has been written to disk, nothing should not exist on disk yet.
    // Debugging use only
    if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
        LOG(@" file exists but keep going anyway :-(");
    else
        LOG(@" file still does not exist :-)");


    // OK now save a copy to disk using UIManagedDocument
    // NOTE: the iCloud files are written before the UIManagedDocument.fileURL, presumably because Core Data does this setup
    // in response to the [moc save:].  Make sure we don't pick this up in our iCloud metaData scan and attempt to create
    // it as if it were a new iCloud file created by some other device.
    //
    [_document saveToURL:_document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
        if (!success) {
            // Handle the error.
            LOG(@" error saving file :-(");
        }

        // We close the file and wait for it to appear in the file browser and then
        // let the user select it from the browser to open it and start using it.
        // Skip this if you want to open it directly and [self fileopened] but
        // bear in mind the fileListController will not be correctly set up when we return to it.

        [_document closeWithCompletionHandler:^(BOOL success){
            if (!success) {
                // Handle the error.
                LOG(@" error closing file after creation :-(");
                FLOG(@" file URL is %@", [fileURL path]);
            } else {
                FLOG(@" file closed %@", fileName);

                _creatingNewFile = NO;  // OK we are done, so let metaData scanning go ahead as normal

                // Tell our UITableView file list that we are done and trigger scanning of local and iCloud files
                // The fileListController will the add the new file itself and the user will then pick the
                // file from this list in order to open it.

                // To open the file automatically use the callback in the fileListController
                // to select and then open the file so it looks seamless to the user.
                [self.fileListController fileHasBeenCreated:fileURL];


                // Stop observing now
                [center removeObserver:self
                                  name:UIDocumentStateChangedNotification
                                object:_document];

            }
        }];
    }];
}

}

Upvotes: 1

Duncan Groenewald
Duncan Groenewald

Reputation: 8988

It hard to understand what your code is doing but it seems to be a lot more complicated than necessary to just open a new or existing core data file using the UIManagedDocument wrapper.

One strange thing is the following:

NSPersistentStoreUbiquitousContentNameKey=TransactionsLog (see your last line)

You shouldn't be setting this to the transaction log path. It should be a unique iCloud store name.

Also its not clear why you need a URL to the transaction log path - I never use this nor do I need to set the transaction log path because Core Data does this for you. You only need it if you have legacy apps which use custom log paths.

I'll post some of my code for creating the UIManagedDocument here in a few minutes.

Upvotes: 0

Related Questions