From 418411769df6bfe30871adfe937075e836c0c112 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sat, 6 Apr 2013 19:39:54 -0400 Subject: [PATCH] Migration fixes. [FIXED] Moved migration into the persistence queue by performing it on willLoadStore. [FIXED] Re-enabled cloud after migration. [UPDATED] Allow rebuilding the old cloud store if it got deleted locally. --- .gitignore | 1 + External/UbiquityStoreManager | 2 +- MasterPassword/ObjC/MPAppDelegate_Store.m | 204 ++++++++++-------- .../MasterPassword Mac (App Store).xcscheme | 61 +----- .../MasterPassword Mac (Development).xcscheme | 94 ++------ .../project.pbxproj | 2 +- .../MasterPassword iOS (App Store).xcscheme | 61 +----- .../MasterPassword iOS (Development).xcscheme | 94 ++------ 8 files changed, 152 insertions(+), 367 deletions(-) diff --git a/.gitignore b/.gitignore index 40e236da..9b0d501c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ # Xcode IDE xcuserdata/ +/DerivedData/ # Media Press/Background.png diff --git a/External/UbiquityStoreManager b/External/UbiquityStoreManager index c392d8df..f2c303cb 160000 --- a/External/UbiquityStoreManager +++ b/External/UbiquityStoreManager @@ -1 +1 @@ -Subproject commit c392d8df05e29a73250430cd48f5595a559f19f9 +Subproject commit f2c303cbba0e0ce8b3949f30e54de9acc3e0a832 diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index 8a3919ff..2fab36e6 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -75,95 +75,128 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, - (void)migrateStoreForManager:(UbiquityStoreManager *)storeManager { - NSNumber *cloudEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:@"iCloudEnabledKey"]; - if (!cloudEnabled) + NSNumber *oldCloudEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:@"iCloudEnabledKey"]; + dbg(@"iCloudEnabledKey: %@", oldCloudEnabled); + if (!oldCloudEnabled) // No old data to migrate. return; - if ([cloudEnabled boolValue]) { - if ([storeManager cloudSafeForSeeding]) { - NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"]; - NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"]; - NSURL *newCloudStoreURL = [storeManager URLForCloudStore]; - NSURL *newCloudContentURL = [storeManager URLForCloudContent]; - //NSURL *oldCloudContentURL = [[cloudContainerURL URLByAppendingPathComponent:@"Data" isDirectory:YES] - // URLByAppendingPathComponent:uuid isDirectory:YES]; - NSURL *oldCloudStoreDirectoryURL = [cloudContainerURL URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES]; - NSURL *oldCloudStoreURL = [[oldCloudStoreDirectoryURL URLByAppendingPathComponent:uuid isDirectory:NO] - URLByAppendingPathExtension:@"sqlite"]; - if (![[NSFileManager defaultManager] fileExistsAtPath:oldCloudStoreURL.path isDirectory:NO]) { - // No old store to migrate from, cannot migrate. - wrn(@"Cannot migrate cloud store, old store not found at: %@", oldCloudStoreURL.path); - return; - } - - NSError *error = nil; - NSDictionary *oldCloudStoreOptions = @{ - // This is here in an attempt to have iCloud recreate the old store file from - // the baseline and transaction logs from the iCloud account. - // In my tests however only the baseline was used to recreate the store which then ended up being empty. - /*NSPersistentStoreUbiquitousContentNameKey : uuid, - NSPersistentStoreUbiquitousContentURLKey : oldCloudContentURL,*/ - // So instead, we'll just open up the old store as read-only, if it exists. - NSReadOnlyPersistentStoreOption : @YES, - NSMigratePersistentStoresAutomaticallyOption : @YES, - NSInferMappingModelAutomaticallyOption : @YES}; - NSDictionary *newCloudStoreOptions = @{ - NSPersistentStoreUbiquitousContentNameKey : [storeManager valueForKey:@"contentName"], - NSPersistentStoreUbiquitousContentURLKey : newCloudContentURL, - NSMigratePersistentStoresAutomaticallyOption : @YES, - NSInferMappingModelAutomaticallyOption : @YES}; - - // Create the directory to hold the new cloud store. - // This is only necessary if we want to try to rebuild the old store. See comment above about how that failed. - //if (![[NSFileManager defaultManager] createDirectoryAtPath:oldCloudStoreDirectoryURL.path - // withIntermediateDirectories:YES attributes:nil error:&error]) - //err(@"While creating directory for old cloud store: %@", error); - if (![[NSFileManager defaultManager] createDirectoryAtPath:[storeManager URLForCloudStoreDirectory].path - withIntermediateDirectories:YES attributes:nil error:&error]) { - err(@"While creating directory for new cloud store: %@", error); - return; - } - - NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; - NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; - - // Open the old cloud store. - NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldCloudStoreURL - options:oldCloudStoreOptions error:&error]; - if (!oldStore) { - err(@"While opening old store for migration %@: %@", oldCloudStoreURL.path, error); - return; - } - - // Migrate to the new cloud store. - if (![psc migratePersistentStore:oldStore toURL:newCloudStoreURL options:newCloudStoreOptions withType:NSSQLiteStoreType - error:&error]) { - err(@"While migrating cloud store from %@ -> %@: %@", oldCloudStoreURL.path, newCloudStoreURL.path, error); - return; - } - - // Clean-up. - if (![psc removePersistentStore:[psc.persistentStores lastObject] error:&error]) - err(@"While removing the migrated store from the store context: %@", error); - if (![[NSFileManager defaultManager] removeItemAtURL:oldCloudStoreURL error:&error]) - err(@"While deleting the old cloud store: %@", error); + if ([oldCloudEnabled boolValue]) { + if (![storeManager cloudSafeForSeeding]) { + dbg(@"Cloud store already exists, refusing to migrate."); + // TODO: Make this final somehow by doing something with iCloudEnabledKey. + return; } + + NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"]; + NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"]; + NSURL *newCloudStoreURL = [storeManager URLForCloudStore]; + NSURL *newCloudContentURL = [storeManager URLForCloudContent]; + NSURL *oldCloudContentURL = [[cloudContainerURL URLByAppendingPathComponent:@"Data" isDirectory:YES] + URLByAppendingPathComponent:uuid isDirectory:YES]; + NSURL *oldCloudStoreDirectoryURL = [cloudContainerURL URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES]; + NSURL *oldCloudStoreURL = [[oldCloudStoreDirectoryURL URLByAppendingPathComponent:uuid isDirectory:NO] + URLByAppendingPathExtension:@"sqlite"]; + dbg(@"Migrating cloud:\n%@ ->\n%@", oldCloudStoreURL, newCloudStoreURL); + if (![[NSFileManager defaultManager] fileExistsAtPath:oldCloudStoreURL.path isDirectory:NO]) { + // No old store to migrate from, cannot migrate. + wrn(@"Cannot migrate cloud store, old store not found at: %@", oldCloudStoreURL.path); + return; + } + + NSError *error = nil; + NSDictionary *oldCloudStoreOptions = @{ + // This is here in an attempt to have iCloud recreate the old store file from + // the baseline and transaction logs from the iCloud account. + // In my tests however only the baseline was used to recreate the store which then ended up being empty. + NSPersistentStoreUbiquitousContentNameKey : uuid, + NSPersistentStoreUbiquitousContentURLKey : oldCloudContentURL, + // So instead, we'll just open up the old store as read-only, if it exists. + NSReadOnlyPersistentStoreOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES}; + NSDictionary *newCloudStoreOptions = @{ + NSPersistentStoreUbiquitousContentNameKey : [storeManager valueForKey:@"contentName"], + NSPersistentStoreUbiquitousContentURLKey : newCloudContentURL, + NSMigratePersistentStoresAutomaticallyOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES}; + + // Create the directory to hold the new cloud store. + // This is only necessary if we want to try to rebuild the old store. See comment above about how that failed. + if (![[NSFileManager defaultManager] createDirectoryAtPath:oldCloudStoreDirectoryURL.path + withIntermediateDirectories:YES attributes:nil error:&error]) { + err(@"While creating directory for old cloud store: %@", error); + return; + } + if (![[NSFileManager defaultManager] createDirectoryAtPath:[storeManager URLForCloudStoreDirectory].path + withIntermediateDirectories:YES attributes:nil error:&error]) { + err(@"While creating directory for new cloud store: %@", error); + return; + } + + NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; + NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; + + // Open the old cloud store. + NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldCloudStoreURL + options:oldCloudStoreOptions error:&error]; + if (!oldStore) { + err(@"While opening old store for migration %@: %@", oldCloudStoreURL.path, error); + return; + } + + dbg(@"=== Old store ==="); + for (NSEntityDescription *entity in model.entities) { + NSFetchRequest *fetch = [NSFetchRequest new]; + fetch.entity = entity; + NSManagedObjectContext *moc = [NSManagedObjectContext new]; + moc.persistentStoreCoordinator = psc; + dbg(@"%@: %d", entity.name, [[moc executeFetchRequest:fetch error:&error] count]); + } + + // Migrate to the new cloud store. + if (![psc migratePersistentStore:oldStore toURL:newCloudStoreURL options:newCloudStoreOptions withType:NSSQLiteStoreType + error:&error]) { + err(@"While migrating cloud store from %@ -> %@: %@", oldCloudStoreURL.path, newCloudStoreURL.path, error); + return; + } + + dbg(@"=== New store ==="); + for (NSEntityDescription *entity in model.entities) { + NSFetchRequest *fetch = [NSFetchRequest new]; + fetch.entity = entity; + NSManagedObjectContext *moc = [NSManagedObjectContext new]; + moc.persistentStoreCoordinator = psc; + dbg(@"%@: %d", entity.name, [[moc executeFetchRequest:fetch error:&error] count]); + } + + // Clean-up. + if (![psc removePersistentStore:[psc.persistentStores lastObject] error:&error]) + err(@"While removing the migrated store from the store context: %@", error); + if (![[NSFileManager defaultManager] removeItemAtURL:oldCloudStoreURL error:&error]) + err(@"While deleting the old cloud store: %@", error); + + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:CloudEnabledKey]; + inf(@"Successfully migrated old to new cloud store."); } else { NSURL *applicationFilesDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; NSURL *oldLocalStoreURL = [[applicationFilesDirectory URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] - URLByAppendingPathExtension:@"sqlite"]; + URLByAppendingPathExtension:@"sqlite"]; NSURL *newLocalStoreURL = [storeManager URLForLocalStore]; + dbg(@"Migrating local:\n%@ ->\n%@", oldLocalStoreURL, newLocalStoreURL); if ([[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NO] && - ![[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) { + ![[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) { NSError *error = nil; - NSDictionary *options = @{ - NSMigratePersistentStoresAutomaticallyOption : @YES, - NSInferMappingModelAutomaticallyOption : @YES}; + NSDictionary *oldLocalStoreOptions = @{ + NSReadOnlyPersistentStoreOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES + }; + NSDictionary *newLocalStoreOptions = @{ + NSMigratePersistentStoresAutomaticallyOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES}; NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; - + // Create the directory to hold the new local store. if (![[NSFileManager defaultManager] createDirectoryAtPath:[storeManager URLForLocalStoreDirectory].path withIntermediateDirectories:YES attributes:nil error:&error]) { @@ -173,28 +206,30 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, // Open the old local store. NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil - URL:oldLocalStoreURL options:options error:&error]; + URL:oldLocalStoreURL options:oldLocalStoreOptions error:&error]; if (!oldStore) { err(@"While opening old store for migration %@: %@", oldLocalStoreURL.path, error); return; } - + // Migrate to the new local store. - if (![psc migratePersistentStore:oldStore toURL:newLocalStoreURL options:options withType:NSSQLiteStoreType error:&error]) { + if (![psc migratePersistentStore:oldStore toURL:newLocalStoreURL options:newLocalStoreOptions withType:NSSQLiteStoreType error:&error]) { err(@"While migrating local store from %@ -> %@: %@", oldLocalStoreURL, newLocalStoreURL, error); return; } - + // Clean-up. if (![psc removePersistentStore:[psc.persistentStores lastObject] error:&error]) - err(@"While removing the migrated store from the store context: %@", error); + err(@"While removing the migrated store from the store context: %@", error); if (![[NSFileManager defaultManager] removeItemAtURL:oldLocalStoreURL error:&error]) - err(@"While deleting the old local store: %@", error); + err(@"While deleting the old local store: %@", error); + inf(@"Successfully migrated old to new local store."); } } [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"LocalUUIDKey"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"iCloudEnabledKey"]; + dbg(@"Removed old cloud keys."); } - (UbiquityStoreManager *)storeManager { @@ -214,9 +249,6 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, #endif delegate:self]; - // Migrate old store to new store location. - [self migrateStoreForManager:storeManager]; - #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication] queue:nil @@ -270,6 +302,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore { + [self migrateStoreForManager:manager]; + dispatch_async(dispatch_get_main_queue(), ^{ if (![self.storeLoading isVisible]) self.storeLoading = [PearlOverlay showOverlayWithTitle:@"Loading..."]; diff --git a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword Mac (App Store).xcscheme b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword Mac (App Store).xcscheme index 844780ca..265c77db 100644 --- a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword Mac (App Store).xcscheme +++ b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword Mac (App Store).xcscheme @@ -1,17 +1,10 @@ - + + buildForRunning = "YES"> - - - - - - - - + buildConfiguration = "AppStore"> - - - - - - - - - - - - diff --git a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword Mac (Development).xcscheme b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword Mac (Development).xcscheme index eb6986bf..32f6be57 100644 --- a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword Mac (Development).xcscheme +++ b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword Mac (Development).xcscheme @@ -1,17 +1,10 @@ - + + buildForRunning = "YES"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index 2b7ce483..fb750196 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -5540,7 +5540,7 @@ "$(inherit)", "\"$(SRCROOT)/../../../External\"/**", ); - ONLY_ACTIVE_ARCH = NO; + ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "${TARGET_NAME}"; "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword iOS (App Store).xcscheme b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword iOS (App Store).xcscheme index 70ba9774..7351e1c4 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword iOS (App Store).xcscheme +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword iOS (App Store).xcscheme @@ -1,17 +1,10 @@ - + + buildForRunning = "YES"> - - - - - - - - + buildConfiguration = "AppStore"> - - - - - - - - - - - - diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword iOS (Development).xcscheme b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword iOS (Development).xcscheme index e179af1f..7bf83181 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword iOS (Development).xcscheme +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword iOS (Development).xcscheme @@ -1,17 +1,10 @@ - + + buildForRunning = "YES"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + +