From b07298e203d9cfc137cfc7d2a30fa514795984d9 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Fri, 25 Jan 2013 01:05:31 -0500 Subject: [PATCH] Fixed migration of old store to new store + don't hold references to CoreData objects. [FIXED] Working migration of old store to new store. [FIXED] We shouldn't be holding references to CoreData objects anywhere. In that light, the user NSMenuItems have been fixed. --- External/iCloudStoreManager | 2 +- MasterPassword/MPAppDelegate_Shared.h | 5 +- MasterPassword/MPAppDelegate_Shared.m | 12 +- MasterPassword/MPAppDelegate_Store.m | 164 ++++++++++++++++---------- MasterPassword/Mac/MPAppDelegate.m | 10 +- 5 files changed, 111 insertions(+), 82 deletions(-) diff --git a/External/iCloudStoreManager b/External/iCloudStoreManager index 20e2a1de..8faba0d3 160000 --- a/External/iCloudStoreManager +++ b/External/iCloudStoreManager @@ -1 +1 @@ -Subproject commit 20e2a1dec08fef1e99433f9a8b3a2310bb00717d +Subproject commit 8faba0d3c35d471005c45280dc2670bc811e08c6 diff --git a/MasterPassword/MPAppDelegate_Shared.h b/MasterPassword/MPAppDelegate_Shared.h index 055cffdb..dfad1a10 100644 --- a/MasterPassword/MPAppDelegate_Shared.h +++ b/MasterPassword/MPAppDelegate_Shared.h @@ -15,8 +15,9 @@ @interface MPAppDelegate_Shared : NSObject #endif -@property (strong, nonatomic) MPUserEntity *activeUser; -@property (strong, nonatomic) MPKey *key; +@property (strong, nonatomic) MPUserEntity *activeUser; +@property (strong, nonatomic) NSManagedObjectID *activeUserObjectID; +@property (strong, nonatomic) MPKey *key; + (MPAppDelegate_Shared *)get; diff --git a/MasterPassword/MPAppDelegate_Shared.m b/MasterPassword/MPAppDelegate_Shared.m index 643389a2..00c9621c 100644 --- a/MasterPassword/MPAppDelegate_Shared.m +++ b/MasterPassword/MPAppDelegate_Shared.m @@ -9,12 +9,6 @@ #import "MPAppDelegate_Shared.h" #import "MPAppDelegate_Store.h" -@interface MPAppDelegate_Shared () - -@property (strong, nonatomic) NSManagedObjectID *activeUserID; - -@end - @implementation MPAppDelegate_Shared + (MPAppDelegate_Shared *)get { @@ -30,15 +24,15 @@ - (MPUserEntity *)activeUser { - if (!self.activeUserID) + if (!self.activeUserObjectID) return nil; - return (MPUserEntity *)[self.managedObjectContextIfReady objectWithID:self.activeUserID]; + return (MPUserEntity *)[self.managedObjectContextIfReady objectWithID:self.activeUserObjectID]; } - (void)setActiveUser:(MPUserEntity *)activeUser { - self.activeUserID = activeUser.objectID; + self.activeUserObjectID = activeUser.objectID; } @end diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m index 718f08ab..7e19f304 100644 --- a/MasterPassword/MPAppDelegate_Store.m +++ b/MasterPassword/MPAppDelegate_Store.m @@ -8,8 +8,6 @@ #import #import "MPAppDelegate_Store.h" -#import -#import @implementation MPAppDelegate_Shared (Store) @@ -42,6 +40,105 @@ static char managedObjectContextKey; return managedObjectContext; } +- (void)migrateStoreForManager:(UbiquityStoreManager *)storeManager { + + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"iCloudEnabledKey"]; + NSNumber *cloudEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:@"iCloudEnabledKey"]; + if (!cloudEnabled) + return; + + if ([cloudEnabled boolValue]) { + NSURL *newCloudContentURL = [storeManager URLForCloudContent]; + NSURL *newCloudStoreURL = [storeManager URLForCloudStore]; + if ([[NSFileManager defaultManager] fileExistsAtPath:newCloudStoreURL.path isDirectory:NO]) + // New store already exists, migration has already been done. + return; + + NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"]; + NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"]; + //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}; + + // 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); + + NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; + NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; + 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; + } + + if (![psc migratePersistentStore:oldStore toURL:newCloudStoreURL options:newCloudStoreOptions withType:NSSQLiteStoreType + error:&error]) { + err(@"While migrating cloud store from %@ -> %@: %@", oldCloudStoreURL.path, newCloudStoreURL.path, error); + return; + } + 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); + } else { + NSURL *applicationFilesDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory + inDomains:NSUserDomainMask] lastObject]; + NSURL *oldLocalStoreURL = [[applicationFilesDirectory URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] + URLByAppendingPathExtension:@"sqlite"]; + NSURL *newLocalStoreURL = [storeManager URLForLocalStore]; + if ([[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NO] && + ![[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) { + NSError *error = nil; + NSDictionary *options = @{ + NSMigratePersistentStoresAutomaticallyOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES}; + NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; + NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; + NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil + URL:oldLocalStoreURL options:options error:&error]; + if (oldStore) + [psc migratePersistentStore:oldStore toURL:newLocalStoreURL options:options withType:NSSQLiteStoreType error:&error]; + if (error) + err(@"While migrating local store from %@ -> %@: %@", oldLocalStoreURL, newLocalStoreURL, error); + else { + [psc removePersistentStore:[psc.persistentStores lastObject] error:nil]; + [[NSFileManager defaultManager] removeItemAtURL:oldLocalStoreURL error:nil]; + } + } + } + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"iCloudEnabledKey"]; +} + - (UbiquityStoreManager *)storeManager { static UbiquityStoreManager *storeManager = nil; @@ -60,68 +157,7 @@ static char managedObjectContextKey; storeManager.delegate = self; // Migrate old store to new store location. - NSNumber *cloudEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:@"iCloudEnabledKey"]; - if (cloudEnabled) { - if ([cloudEnabled boolValue]) { - NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"]; - NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"]; - NSURL *oldCloudContentURL = [[cloudContainerURL URLByAppendingPathComponent:@"Data" isDirectory:YES] - URLByAppendingPathComponent:uuid isDirectory:YES]; - NSURL *oldCloudStoreURL = [[[cloudContainerURL URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES] - URLByAppendingPathComponent:uuid isDirectory:NO] - URLByAppendingPathExtension:@"sqlite"]; - NSURL *newCloudContentURL = [storeManager URLForCloudContent]; - NSURL *newCloudStoreURL = [storeManager URLForCloudStore]; - - if ([[NSFileManager defaultManager] fileExistsAtPath:oldCloudStoreURL.path isDirectory:NO] && - ![[NSFileManager defaultManager] fileExistsAtPath:newCloudStoreURL.path isDirectory:NO]) { - NSError *error = nil; - NSDictionary *options = @{ - NSPersistentStoreUbiquitousContentNameKey : uuid, - NSPersistentStoreUbiquitousContentURLKey : oldCloudContentURL, - NSMigratePersistentStoresAutomaticallyOption : @YES, - NSInferMappingModelAutomaticallyOption : @YES}; - NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; - NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; - NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil - URL:oldCloudStoreURL options:options error:&error]; - if (oldStore) - [psc migratePersistentStore:oldStore toURL:newCloudStoreURL options:options withType:NSSQLiteStoreType error:&error]; - if (error) - err(@"While migrating cloud store from %@ -> %@: %@", oldCloudStoreURL, newCloudStoreURL, error); - else { - [psc removePersistentStore:[psc.persistentStores lastObject] error:nil]; - [[NSFileManager defaultManager] removeItemAtURL:oldCloudStoreURL error:nil]; - } - } - } else { - NSURL *applicationFilesDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory - inDomains:NSUserDomainMask] lastObject]; - NSURL *oldLocalStoreURL = [[applicationFilesDirectory URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] - URLByAppendingPathExtension:@"sqlite"]; - NSURL *newLocalStoreURL = [storeManager URLForLocalStore]; - if ([[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NO] && - ![[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) { - NSError *error = nil; - NSDictionary *options = @{ - NSMigratePersistentStoresAutomaticallyOption : @YES, - NSInferMappingModelAutomaticallyOption : @YES}; - NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; - NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; - NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil - URL:oldLocalStoreURL options:options error:&error]; - if (oldStore) - [psc migratePersistentStore:oldStore toURL:newLocalStoreURL options:options withType:NSSQLiteStoreType error:&error]; - if (error) - err(@"While migrating local store from %@ -> %@: %@", oldLocalStoreURL, newLocalStoreURL, error); - else { - [psc removePersistentStore:[psc.persistentStores lastObject] error:nil]; - [[NSFileManager defaultManager] removeItemAtURL:oldLocalStoreURL error:nil]; - } - } - } - } - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"iCloudEnabledKey"]; + [self migrateStoreForManager:storeManager]; [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification object:storeManager queue:nil diff --git a/MasterPassword/Mac/MPAppDelegate.m b/MasterPassword/Mac/MPAppDelegate.m index 0466c2af..db9a9231 100644 --- a/MasterPassword/Mac/MPAppDelegate.m +++ b/MasterPassword/Mac/MPAppDelegate.m @@ -107,7 +107,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven for (MPUserEntity *user in users) { NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector(selectUser:) keyEquivalent:@""]; [userItem setTarget:self]; - [userItem setRepresentedObject:user]; + [userItem setRepresentedObject:user.objectID]; [[self.usersItem submenu] addItem:userItem]; if ([user.name isEqualToString:[MPMacConfig get].usedUserName]) @@ -118,9 +118,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (void)selectUser:(NSMenuItem *)item { - NSAssert1([[item representedObject] isKindOfClass:[MPUserEntity class]], @"Not a user: %@", item.representedObject); - - self.activeUser = item.representedObject; + self.activeUserObjectID = item.representedObject; } - (void)showMenu { @@ -200,14 +198,14 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { [[[self.usersItem submenu] itemArray] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - if ([obj representedObject] && [obj representedObject] == self.activeUser) + if ([[obj representedObject] isEqual:self.activeUserObjectID]) [obj setState:NSOnState]; else [obj setState:NSOffState]; }]; [MPMacConfig get].usedUserName = self.activeUser.name; - } forKeyPath:@"activeUser" options:0 context:nil]; + } forKeyPath:@"activeUserObjectID" options:0 context:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification object:nil queue:nil usingBlock: ^(NSNotification *note) { [self updateUsers];