lhunath / UbiquityStoreManager

Implements Core Data + iCloud, deals with all the nasty stuff and gives you a clean API.
http://lhunath.github.io/UbiquityStoreManager
Apache License 2.0
391 stars 37 forks source link

Need to create (local) snapshots #46

Closed NorbNorb closed 10 years ago

NorbNorb commented 10 years ago

Is this possible:

  1. snapshot the local store and then upload it to a save place
  2. download it back, swap the local store with it and then rebuild iCloud from the local store?

I need a way to enable my users to keep snapshots over a long time. I have a complex data model and therefor exporting and importing the data using a custom file format is no solution to me. The complexity would be to high, esp. with schema changes.

lhunath commented 10 years ago

Sure, you can save the sqlite store. Then you can import it into the local or the cloud store using USM's

- (BOOL)migrateStore:(NSURL *)migrationStoreURL withOptions:(NSDictionary *)migrationStoreOptions
             toStore:(NSURL *)targetStoreURL withOptions:(NSDictionary *)targetStoreOptions
            strategy:(UbiquityStoreMigrationStrategy)migrationStrategy
               error:(__autoreleasing NSError **)outError cause:(UbiquityStoreErrorCause *)cause context:(__autoreleasing id *)context;
NorbNorb commented 10 years ago

Dear Maarten, sorry to ask again, but could you please comment on my approach of creating and restoring snapshots? I want to delete both locally and in iCloud if iCloud is enabled, locally only otherwise. Is this the way your API is intended to be used? I had to do a little workaround for a restoration after a deletion (see below).

Creation:

- (void)snapshotPersistentStoreToPath:(NSString *)localCopyPath
{
    [appDelegate.managedObjectContext.persistentStoreCoordinator lock];

    NSArray *stores = appDelegate.managedObjectContext.persistentStoreCoordinator.persistentStores;
    for (NSPersistentStore *store in stores)
    {
        NSURL *storeURL = [appDelegate.managedObjectContext.persistentStoreCoordinator URLForPersistentStore:store];
        NSString *storeFileName = [storeURL lastPathComponent];
        NSURL *dstFileURL = [[NSURL fileURLWithPath:localCopyPath] URLByAppendingPathComponent:storeFileName];
        // Remove Write Ahead Log (wal)
        NSDictionary *options = @{
                                  NSPersistentStoreRemoveUbiquitousMetadataOption: @YES,
                                  NSSQLitePragmasOption : @{ @"journal_mode" : @"DELETE" }
                                  };
        NSError *error;
        NSPersistentStore *storeCopy = [appDelegate.managedObjectContext.persistentStoreCoordinator migratePersistentStore:store
                                                                                                                     toURL:dstFileURL
                                                                                                                   options:options
                                                                                                                  withType:NSSQLiteStoreType
                                                                                                                     error:&error];
        if (error)
        {
            NSLog(@"%@", [error localizedDescription]);
        }

        if (storeCopy)
        {
            [appDelegate.managedObjectContext.persistentStoreCoordinator removePersistentStore:storeCopy error:&error];
        }
    }

    [appDelegate.managedObjectContext.persistentStoreCoordinator unlock];
}

Restoring: I have had the problem that right after a call to deleteCloudContainerLocalOnly: or deleteLocalStore, I could not directly start the migration from the backup store. ubiquityStoreManager:willLoadStoreIsCloud: gets called twice and I had to make sure the recovering migration happens not until the second run (that's what the variable migrateStoreAfterDeletion is for).

- (void)restoreFromPersistentStore:(NSURL *)url
{
    if (!url)
    {
        NSLog(@"Didn't get an URL to restore from.");
        return;
    }

    storeURLToMigrateFrom = url;
    migrateStoreAfterDeletion = NO;

   if (self.ubiquityStoreManager.cloudEnabled)
   {
       [self.ubiquityStoreManager deleteCloudContainerLocalOnly:NO];
   }
   else
   {
       [self.ubiquityStoreManager deleteLocalStore];
   }
}

- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore
{
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    [managedObjectContext performBlockAndWait:^{
        NSError *error = nil;
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
            NSLog( @"Unresolved error: %@\n%@", error, [error userInfo] );

        [managedObjectContext reset];
    }];

    _managedObjectContext = nil;

    if (storeURLToMigrateFrom)
    {
        if (!migrateStoreAfterDeletion)
        {
            migrateStoreAfterDeletion = YES;
        }
        else
        {
            NSURL *dstStoreURL = manager.cloudEnabled ? manager.URLForCloudStore : manager.URLForLocalStore;
            NSError *error;
            UbiquityStoreErrorCause cause;
            id context;
            [manager migrateStore:storeURLToMigrateFrom withOptions:nil
                          toStore:dstStoreURL withOptions:nil
                         strategy:UbiquityStoreMigrationStrategyIOS
                            error:&error
                            cause:&cause
                          context:&context];
            storeURLToMigrateFrom = nil;
            migrateStoreAfterDeletion = NO;
        }
    }
}
lhunath commented 10 years ago

Why do you delete the store first?

NorbNorb commented 10 years ago

I want to overwrite the local store or cloud container with the Core Data snapshot. And I need to get the delegate method ubiquityStoreManager:willLoadStoreIsCloud: called somehow. What's your recommendation?

NorbNorb commented 10 years ago

Hi lhunath, do you have a recommendation on how to handle the restoration locally / to iCloud correctly?

lhunath commented 10 years ago

It seems to me the best way would be to migrate the snapshot to USM's local store and then do a -migrateLocalToCloud if you want a cloud store too, or just a -deleteCloudStoreLocalOnly:NO and let USM migrate the cloud store from the local store automatically when the user turns on cloud.

NorbNorb commented 10 years ago

Ok, thanks, I'll do it that way. So you've asked why I delete the store first. Is there a better way than mine? As I said, I need USM to call ubiquityStoreManager:willLoadStoreIsCloud:, so I can start the migration.

lhunath commented 10 years ago

It will do that whenever you change the store. Eg. just a -reloadStore will trigger that.

On 23 January 2014 08:56, NorbNorb notifications@github.com wrote:

Ok, thanks, I'll do it that way. So you've asked why I delete the store first. Is there a better way than mine? As I said, I need USM to call ubiquityStoreManager:willLoadStoreIsCloud:, so I can start the migration.

— Reply to this email directly or view it on GitHubhttps://github.com/lhunath/UbiquityStoreManager/issues/46#issuecomment-33125312 .

Maarten Billemont (lhunath) me: http://www.lhunath.com – business: http://www.lyndir.comhttp://masterpasswordapp.com

ChristianRo commented 10 years ago

Hi,

i want to achieve something similar. i´ve tried it as Ihunath described it:

but in this line i get the following error: "UbiquityStoreManager: Error (cause:UbiquityStoreErrorCauseSeedStore): Error Domain=NSCocoaErrorDomain Code=134080 "The operation couldn’t be completed. (Cocoa error 134080.)" UserInfo=0xd382770 {NSUnderlyingException=Can't add the same store twice} "

So what i´m doing wrong here? Thank you

ChristianRo commented 10 years ago

Hi lhunath,

could you please post a small example which shows how to use the migrateStorewithOptions Method to replace the actual store with another backup store.

Thank you for your effords.