Shmidt
Shmidt

Reputation: 16684

Migrating iCloud store to local

Migration works fine on Simulator. However on a device, I see no error messages but migrated store is empty.

NSDictionary *iCloudOptions = @{
    NSPersistentStoreUbiquitousContentNameKey : @"iCloudNimbleStore",
    NSPersistentStoreUbiquitousContentURLKey : @"transactions_logs",
    NSMigratePersistentStoresAutomaticallyOption : @YES,
    NSInferMappingModelAutomaticallyOption : @YES
};
NSDictionary *localOptions = @{NSMigratePersistentStoresAutomaticallyOption : @YES,
    NSInferMappingModelAutomaticallyOption : @YES
};
if (![[NSFileManager defaultManager]fileExistsAtPath:self.storeURL.path]) {
    @synchronized(@"Migration")
    {
        // thread-safe code
        if ([[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]) {
            NSLog(@"iCloud");
            [self migrateStoreFromURL:[self nb_URLToStoreWithFilename:[self nb_appName]]options:iCloudOptions];
        }else{
            [self migrateStoreFromURL:[self nb_URLToStoreWithFilename:[NSString stringWithFormat:@"%@.sqlite", [self nb_appName]]] options:localOptions];
            //
            [self migrateStoreFromURL:[self nb_URLToOldStoreWithFilename] options:localOptions];
        }
    }
}

NSDictionary *options = @{
    NSMigratePersistentStoresAutomaticallyOption:@YES
    ,NSInferMappingModelAutomaticallyOption:@YES
};
NSError *error = nil;
[_coordinator lock];
_store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeURL] options:options error:&error];
[_coordinator unlock];
if (!_store) {
    UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Loading Fail" message:[NSString stringWithFormat:@"Failed to add store. Error: %@", error] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
    [alert show];
    NSLog(@"Failed to add store. Error: %@", error);abort();
} else {
    UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Loading Success" message:[NSString stringWithFormat:@"Successfully added store: %@", _store] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
    [alert show];
    NSLog(@"Successfully added store: %@", _store);
    if (_store && !error) {
        // Encrypt the password database
        NSError *encrError;
        NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
        if (![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:self.storeURL.path error:&encrError]){
            NSLog(@"Unresolved error with password store encryption %@, %@", encrError, [encrError userInfo]); 
            abort();
        }else {NSLog(@"Encrypted");}
    }
}

Here is migration procedure:

- (void)migrateStoreFromURL:(NSURL *)oldStoreURL options:(NSDictionary *)oldOptions{
if (debug==1) {
    TFLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
if (_store)
{
    NSLog(@"NOT NEEDED");
    return;
}

UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Migration" message:[NSString stringWithFormat:@"Found old store at %@",oldStoreURL.path] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
[alert show];

NSFileManager *fileManager = [NSFileManager defaultManager];

if (![fileManager fileExistsAtPath:self.storeURL.path]) {
    NSDictionary *options =
    @{
      NSMigratePersistentStoresAutomaticallyOption:@YES
      ,NSInferMappingModelAutomaticallyOption:@YES
      };
    NSError *error = nil;
    [_coordinator lock];
    NSPersistentStore *srcPS = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                                                  configuration:nil
                                                                                            URL:oldStoreURL
                                                                                        options:oldOptions
                                                                                          error:&error];

    _store = [_coordinator migratePersistentStore:srcPS
                                                                             toURL:self.storeURL
                                                                           options:options
                                                                          withType:NSSQLiteStoreType
                                                                             error:&error];
    [_coordinator unlock];
    if (_store && !error) {
        UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Migration Success" message:[NSString stringWithFormat:@"Old store successfully migrated from %@",oldStoreURL.path] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
        [alert show];
        // Encrypt the password database
        NSError *encrError;
        NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
        if (![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:self.storeURL.path error:&encrError]){
            UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Encryption Error" message:[NSString stringWithFormat:@"Unresolved error with password store encryption %@, %@", encrError, [encrError userInfo]] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
            [alert show];
        }

    }else{
        UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Migration Error" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
        [alert show];
    }

}

Upd.: I checked size of the newly migrated store and it's 0. Most strange is that _store && !error is true. I also tried to add NSPersistentStoreRemoveUbiquitousMetadataOption: @YES to migration options but it doesn't change anything.

Upd. 2 I think that on a device iCloud store url is nil before its loaded. I need some workaround to wait until its finished.

Upvotes: 0

Views: 203

Answers (1)

Drew McCormack
Drew McCormack

Reputation: 3592

I'm not 100% sure I understand what you are trying to do with the migrations. It is common to seed data in an empty store with a migration, but it looks like you are trying to migrate data out of iCloud into your local store. Is that right? You should not need to do that. iCloud should automatically add the data from other devices to your store.

This line also doesn't look right:

NSPersistentStoreUbiquitousContentURLKey : @"transactions_logs",

I think you want to use a URL there that points to the transaction log directory inside the iCloud container. Eg.

NSURL *containerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSURL *url = [containerURL URLByAppendingPathComponent:@"transactions_logs"];

When working with iCloud, it is important to realize that data does not transfer instantaneously. It can take a while to arrive, and your app doesn't really have any way to know for sure if there is data coming. You can monitor metadata with metadata queries, but even that often arrives some time after data on other devices has already been generated.

So simply looking in the ubiquity container for data will not help much, because there may or may not be data available. You just do not know, and you have to develop your approach with that assumption in mind, so that it can handle any delays.

The migrations required to get iCloud sync working with Core Data are messy and unnecessary. You are probably much more likely to get things working well with a framework that does that stuff automatically, such as Core Data Ensembles. (Disclosure: I am the developer of Ensembles.)

Upvotes: 1

Related Questions