From d27a0fdbade0b96c14e228ac616a17a7c91bc5e4 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sun, 28 Apr 2013 00:33:28 -0400 Subject: [PATCH] Core Data context fixes and migration fixes. [FIXED] More careful Core Data threading and context usage. [FIXED] iOS bug when migrating, now using USM's copy migration. [ADDED] Better handling of store changes while in Main VC and Element List VCs. --- External/UbiquityStoreManager | 2 +- MasterPassword/ObjC/MPAppDelegate_Key.h | 2 +- MasterPassword/ObjC/MPAppDelegate_Key.m | 12 +- MasterPassword/ObjC/MPAppDelegate_Shared.m | 14 +- MasterPassword/ObjC/MPAppDelegate_Store.m | 58 ++------ .../ObjC/iOS/MPElementListController.m | 16 ++- .../ObjC/iOS/MPElementListSearchController.m | 5 - .../ObjC/iOS/MPMainViewController.m | 129 ++++++++++-------- .../ObjC/iOS/MPUnlockViewController.m | 89 ++++++------ 9 files changed, 147 insertions(+), 180 deletions(-) diff --git a/External/UbiquityStoreManager b/External/UbiquityStoreManager index 25a30fad..9a4d52c3 160000 --- a/External/UbiquityStoreManager +++ b/External/UbiquityStoreManager @@ -1 +1 @@ -Subproject commit 25a30fadcb80e337a664127c6c86c4fc46e96e5f +Subproject commit 9a4d52c382ae01f81cc9d94982b58222b3240f73 diff --git a/MasterPassword/ObjC/MPAppDelegate_Key.h b/MasterPassword/ObjC/MPAppDelegate_Key.h index a9c0050a..b675b470 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Key.h +++ b/MasterPassword/ObjC/MPAppDelegate_Key.h @@ -10,7 +10,7 @@ @interface MPAppDelegate_Shared(Key) -- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password; +- (BOOL)signInAsUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password; - (void)signOutAnimated:(BOOL)animated; - (void)storeSavedKeyFor:(MPUserEntity *)user; diff --git a/MasterPassword/ObjC/MPAppDelegate_Key.m b/MasterPassword/ObjC/MPAppDelegate_Key.m index 842d9ac8..f94639f3 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Key.m +++ b/MasterPassword/ObjC/MPAppDelegate_Key.m @@ -77,18 +77,20 @@ static NSDictionary *keyQuery(MPUserEntity *user) { [[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self userInfo:@{ @"animated" : @(animated) }]; } -- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password { +- (BOOL)signInAsUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password { + + if (password) + NSAssert(![NSThread isMainThread], @"Computing key may not happen from the main thread."); - assert(!password || ![NSThread isMainThread]); // If we need to computing a key, this operation shouldn't be on the main thread. MPKey *tryKey = nil; // Method 1: When the user has no keyID set, set a new key from the given master password. if (!user.keyID) { - if ([password length]) if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) { + if ([password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) { user.keyID = tryKey.keyID; // Migrate existing elements. - [self migrateElementsForUser:user inContext:user.managedObjectContext toKey:tryKey]; + [self migrateElementsForUser:user inContext:moc toKey:tryKey]; } } @@ -153,7 +155,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { } user.lastUsed = [NSDate date]; - [user.managedObjectContext saveToStore]; + [moc saveToStore]; self.activeUser = user; [[NSNotificationCenter defaultCenter] postNotificationName:MPSignedInNotification object:self]; diff --git a/MasterPassword/ObjC/MPAppDelegate_Shared.m b/MasterPassword/ObjC/MPAppDelegate_Shared.m index 0a902585..0b6f9b54 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Shared.m +++ b/MasterPassword/ObjC/MPAppDelegate_Shared.m @@ -26,19 +26,7 @@ - (MPUserEntity *)activeUserForThread { - if (!_activeUserOID) - return nil; - - NSManagedObjectContext *moc = [MPAppDelegate_Shared managedObjectContextForThreadIfReady]; - if (!moc) - return nil; - - NSError *error; - MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:_activeUserOID error:&error]; - if (!activeUser) - err(@"Failed to retrieve active user: %@", error); - - return activeUser; + return [self activeUserInContext:[MPAppDelegate_Shared managedObjectContextForThreadIfReady]]; } - (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc { diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index 567a5502..4d067a2d 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -17,7 +17,7 @@ @implementation MPAppDelegate_Shared(Store) #if TARGET_OS_IPHONE -PearlAssociatedObjectProperty(PearlAlert*, HandleCloudContentAlert, handleCloudContentAlert); + PearlAssociatedObjectProperty(PearlAlert*, HandleCloudContentAlert, handleCloudContentAlert); PearlAssociatedObjectProperty(PearlAlert*, FixCloudContentAlert, fixCloudContentAlert); PearlAssociatedObjectProperty(PearlOverlay*, StoreLoading, storeLoading); #endif @@ -157,8 +157,6 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, 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:[manager URLForLocalStoreDirectory].path @@ -168,31 +166,13 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, return; } - // Open the old local store. - NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil 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:newLocalStoreOptions withType:NSSQLiteStoreType - error:&error]) { - err(@"While migrating local store from %@ -> %@: %@", oldLocalStoreURL, newLocalStoreURL, error); + if (![manager copyMigrateStore:oldLocalStoreURL withOptions:oldLocalStoreOptions + toStore:newLocalStoreURL withOptions:newLocalStoreOptions + error:nil cause:nil context:nil]) { manager.localStoreURL = oldLocalStoreURL; return; } - // Clean-up. - for (NSPersistentStore *store in psc.persistentStores) - if (![psc removePersistentStore:store error:&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); - } - inf(@"Successfully migrated old to new local store."); } @@ -260,32 +240,10 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, 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); + if (![manager copyMigrateStore:oldCloudStoreURL withOptions:oldCloudStoreOptions + toStore:newCloudStoreURL withOptions:newCloudStoreOptions + error:nil cause:nil context:nil]) 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. - for (NSPersistentStore *store in psc.persistentStores) - if (![psc removePersistentStore:store 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] removeObjectForKey:@"LocalUUIDKey"]; inf(@"Successfully migrated old to new cloud store."); @@ -322,7 +280,6 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore { - // FIXME self.privateManagedObjectContext = nil; self.mainManagedObjectContext = nil; @@ -413,6 +370,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, cancelTitle:[PearlStrings get].commonButtonBack otherTitles:@"Fix Anyway", nil]; }; } + #endif #pragma mark - Import / Export diff --git a/MasterPassword/ObjC/iOS/MPElementListController.m b/MasterPassword/ObjC/iOS/MPElementListController.m index 3b76f759..6377c543 100644 --- a/MasterPassword/ObjC/iOS/MPElementListController.m +++ b/MasterPassword/ObjC/iOS/MPElementListController.m @@ -13,6 +13,16 @@ NSDateFormatter *_dateFormatter; } +- (void)viewDidLoad { + + [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification object:nil queue:nil usingBlock: + ^(NSNotification *note) { + [self updateData]; + }]; + + [super viewDidLoad]; +} + - (void)addElementNamed:(NSString *)siteName completion:(void (^)(BOOL success))completion { if (![siteName length]) { @@ -105,8 +115,12 @@ - (void)updateData { MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForThread]; - if (!activeUser) + if (!activeUser) { + _fetchedResultsControllerByLastUsed = nil; + _fetchedResultsControllerByUses = nil; + [self.tableView reloadData]; return; + } // Build predicate. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@", activeUser]; diff --git a/MasterPassword/ObjC/iOS/MPElementListSearchController.m b/MasterPassword/ObjC/iOS/MPElementListSearchController.m index 156ff17a..963948ab 100644 --- a/MasterPassword/ObjC/iOS/MPElementListSearchController.m +++ b/MasterPassword/ObjC/iOS/MPElementListSearchController.m @@ -63,11 +63,6 @@ } } -- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { - - [self.delegate didSelectElement:nil]; -} - - (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller { controller.searchBar.text = @""; diff --git a/MasterPassword/ObjC/iOS/MPMainViewController.m b/MasterPassword/ObjC/iOS/MPMainViewController.m index b5268a46..f4d042e1 100644 --- a/MasterPassword/ObjC/iOS/MPMainViewController.m +++ b/MasterPassword/ObjC/iOS/MPMainViewController.m @@ -77,10 +77,10 @@ self.alertBody.text = nil; self.toolTipEditIcon.hidden = YES; - [[NSNotificationCenter defaultCenter] - addObserverForName:UIApplicationDidEnterBackgroundNotification object:self queue:nil usingBlock:^(NSNotification *note) { - self.suppressOutdatedAlert = NO; - }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:self queue:nil usingBlock: + ^(NSNotification *note) { + self.suppressOutdatedAlert = NO; + }]; [[NSNotificationCenter defaultCenter] addObserverForName:MPElementUpdatedNotification object:nil queue:nil usingBlock: ^void(NSNotification *note) { MPElementEntity *activeElement = [self activeElementForThread]; @@ -107,6 +107,11 @@ [self.navigationController popToRootViewControllerAnimated:animated]; }]; }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification object:nil queue:nil usingBlock: + ^(NSNotification *note) { + if (!self.activeElementForThread) + [self didSelectElement:nil]; + }]; [super viewDidLoad]; } @@ -584,37 +589,43 @@ - (void)changeActiveElementWithoutWarningDo:(BOOL (^)(MPElementEntity *activeElement))task; { - MPElementEntity *activeElement = [self activeElementForThread]; - NSString *oldPassword = [activeElement.content description]; - if (!task( activeElement )) - return; - NSString *newPassword = [activeElement.content description]; + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPElementEntity *activeElement = [self activeElementInContext:context]; + if (!activeElement) + return; - // Save. - [activeElement.managedObjectContext saveToStore]; + NSString *oldPassword = [activeElement.content description]; + if (!task( activeElement )) + return; + NSString *newPassword = [activeElement.content description]; - // Update the UI. - dispatch_async( dispatch_get_main_queue(), ^{ - [self updateAnimated:YES]; + // Save. + [context saveToStore]; - // Show new and old password. - if ([oldPassword length] && ![oldPassword isEqualToString:newPassword]) - [self showAlertWithTitle:@"Password Changed!" - message:PearlString( @"The password for %@ has changed.\n\n" - @"IMPORTANT:\n" - @"Don't forget to update the site with your new password! " - @"Your old password was:\n" - @"%@", activeElement.name, oldPassword )]; - } ); + // Update the UI. + dispatch_async( dispatch_get_main_queue(), ^{ + [self updateAnimated:YES]; + + // Show new and old password. + if ([oldPassword length] && ![oldPassword isEqualToString:newPassword]) + [self showAlertWithTitle:@"Password Changed!" + message:PearlString( @"The password for %@ has changed.\n\n" + @"IMPORTANT:\n" + @"Don't forget to update the site with your new password! " + @"Your old password was:\n" + @"%@", activeElement.name, oldPassword )]; + } ); + }]; } - (MPElementEntity *)activeElementForThread { - if (!_activeElementOID) - return nil; + return [self activeElementInContext:[MPiOSAppDelegate managedObjectContextForThreadIfReady]]; +} - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) +- (MPElementEntity *)activeElementInContext:(NSManagedObjectContext *)moc { + + if (!_activeElementOID) return nil; NSError *error; @@ -801,47 +812,45 @@ - (void)didSelectElement:(MPElementEntity *)element { - if (!element) - return; - + inf(@"Selected: %@", element.name); _activeElementOID = element.objectID; [self closeAlert]; - [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement) { - if ([activeElement use] == 1) - [self showAlertWithTitle:@"New Site" message: - PearlString( @"You've just created a password for %@.\n\n" - @"IMPORTANT:\n" - @"Go to %@ and set or change the password for your account to the password above.\n" - @"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.", - activeElement.name, activeElement.name )]; - return YES; - }]; - MPElementEntity *activeElement = [self activeElementForThread]; - inf(@"Selected: %@", activeElement.name); - - if (![[MPiOSConfig get].typeTipShown boolValue]) - [UIView animateWithDuration:0.5f animations:^{ - self.typeTipContainer.alpha = 1; - } completion:^(BOOL finished) { - if (finished) { - [MPiOSConfig get].typeTipShown = PearlBool(YES); - - dispatch_after( - dispatch_time( DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC) ), dispatch_get_main_queue(), ^{ - [UIView animateWithDuration:0.2f animations:^{ - self.typeTipContainer.alpha = 0; - }]; - } ); - } + if (element) { + [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement) { + if ([activeElement use] == 1) + [self showAlertWithTitle:@"New Site" message: + PearlString( @"You've just created a password for %@.\n\n" + @"IMPORTANT:\n" + @"Go to %@ and set or change the password for your account to the password above.\n" + @"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.", + activeElement.name, activeElement.name )]; + return YES; }]; + if (![[MPiOSConfig get].typeTipShown boolValue]) + [UIView animateWithDuration:0.5f animations:^{ + self.typeTipContainer.alpha = 1; + } completion:^(BOOL finished) { + if (finished) { + [MPiOSConfig get].typeTipShown = PearlBool(YES); + + dispatch_after( + dispatch_time( DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC) ), dispatch_get_main_queue(), ^{ + [UIView animateWithDuration:0.2f animations:^{ + self.typeTipContainer.alpha = 0; + }]; + } ); + } + }]; + } + [self.searchDisplayController setActive:NO animated:YES]; - self.searchDisplayController.searchBar.text = activeElement.name; + self.searchDisplayController.searchBar.text = element.name; MPCheckpoint( MPCheckpointUseType, @{ - @"type" : activeElement.typeName, - @"version" : @(activeElement.version) + @"type" : element.typeName, + @"version" : @(element.version) } ); [self updateAnimated:YES]; diff --git a/MasterPassword/ObjC/iOS/MPUnlockViewController.m b/MasterPassword/ObjC/iOS/MPUnlockViewController.m index 13f576c9..1b8911c5 100644 --- a/MasterPassword/ObjC/iOS/MPUnlockViewController.m +++ b/MasterPassword/ObjC/iOS/MPUnlockViewController.m @@ -14,8 +14,6 @@ #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" -#import "GPPSignIn.h" - @interface MPUnlockViewController() @property(strong, nonatomic) NSMutableDictionary *avatarToUserOID; @@ -163,26 +161,26 @@ [self initializeWordLabel:wordLabel]; } recurse:NO]; - [[NSNotificationCenter defaultCenter] - addObserverForName:UbiquityManagedStoreDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification *note) { - [self updateUsers]; - }]; - [[NSNotificationCenter defaultCenter] - addObserverForName:UbiquityManagedStoreDidImportChangesNotification object:nil queue:nil usingBlock:^(NSNotification *note) { - [self updateUsers]; - }]; - [[NSNotificationCenter defaultCenter] - addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { - [self emergencyCloseAnimated:NO]; - self.uiContainer.alpha = 0; - }]; - [[NSNotificationCenter defaultCenter] - addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { - [self updateLayoutAnimated:NO allowScroll:NO completion:nil]; - [UIView animateWithDuration:1 animations:^{ - self.uiContainer.alpha = 1; - }]; - }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification object:nil queue:nil usingBlock: + ^(NSNotification *note) { + [self updateUsers]; + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidImportChangesNotification object:nil queue:nil + usingBlock:^(NSNotification *note) { + [self updateUsers]; + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil usingBlock: + ^(NSNotification *note) { + [self emergencyCloseAnimated:NO]; + self.uiContainer.alpha = 0; + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock: + ^(NSNotification *note) { + [self updateLayoutAnimated:NO allowScroll:NO completion:nil]; + [UIView animateWithDuration:1 animations:^{ + self.uiContainer.alpha = 1; + }]; + }]; [self updateLayoutAnimated:NO allowScroll:YES completion:nil]; @@ -356,16 +354,16 @@ - (void)didToggleUserSelection { - MPUserEntity *selectedUser = self.selectedUser; + MPUserEntity *selectedUser = [self selectedUserForThread]; if (!selectedUser) [self.passwordField resignFirstResponder]; - else if ([[MPiOSAppDelegate get] signInAsUser:selectedUser usingMasterPassword:nil]) { + else if ([[MPiOSAppDelegate get] signInAsUser:selectedUser inContext:selectedUser.managedObjectContext usingMasterPassword:nil]) { [self performSegueWithIdentifier:@"MP_Unlock" sender:self]; return; } [self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) { - if (self.selectedUser) + if ([self selectedUserForThread]) [self.passwordField becomeFirstResponder]; }]; } @@ -479,7 +477,8 @@ } // Lay out password entry and user selection views. - if (self.selectedUser && !self.passwordView.alpha) { + MPUserEntity *selectedUser = [self selectedUserForThread]; + if (selectedUser && !self.passwordView.alpha) { // User was just selected. self.passwordView.alpha = 1; self.avatarsView.center = CGPointMake( 160, 180 ); @@ -489,7 +488,7 @@ self.oldNameLabel.center = self.nameLabel.center; self.avatarShadowColor = [UIColor whiteColor]; } - else if (!self.selectedUser && self.passwordView.alpha == 1) { + else if (!selectedUser && self.passwordView.alpha == 1) { // User was just deselected. self.passwordField.text = nil; self.passwordView.alpha = 0; @@ -502,7 +501,7 @@ } // Lay out the word wall. - if (!self.selectedUser || self.selectedUser.keyID) { + if (!selectedUser || selectedUser.keyID) { self.passwordFieldLabel.text = @"Enter your master password:"; self.wordWall.alpha = 0; @@ -530,8 +529,8 @@ } // Lay out user targeting. - MPUserEntity *targetedUser = self.selectedUser; - UIButton *selectedAvatar = [self avatarForUser:self.selectedUser]; + UIButton *selectedAvatar = [self avatarForUser:selectedUser]; + MPUserEntity *targetedUser = selectedUser; UIButton *targetedAvatar = selectedAvatar; if (!targetedAvatar) { targetedAvatar = [self findTargetedAvatar]; @@ -547,7 +546,7 @@ BOOL isTargeted = avatar == targetedAvatar; avatar.userInteractionEnabled = isTargeted; - avatar.alpha = isTargeted? 1: self.selectedUser? 0.1: 0.4; + avatar.alpha = isTargeted? 1: [self selectedUserForThread]? 0.1: 0.4; [self updateAvatarShadowColor:avatar isTargeted:isTargeted]; } recurse:NO]; @@ -612,14 +611,15 @@ - (void)tryMasterPassword { - if (!self.selectedUser) + if (![self selectedUserForThread]) // No user selected, can't try sign-in. return; [self setSpinnerActive:YES]; - dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ - BOOL unlocked = [[MPiOSAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text]; + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + BOOL unlocked = [[MPiOSAppDelegate get] signInAsUser:[self selectedUserInContext:moc] inContext:moc + usingMasterPassword:self.passwordField.text]; dispatch_async( dispatch_get_main_queue(), ^{ if (unlocked) @@ -632,7 +632,7 @@ [self setSpinnerActive:NO]; } } ); - } ); + }]; } - (UIButton *)findTargetedAvatar { @@ -697,9 +697,9 @@ self.spinner.alpha = active? 1: 0; if (active) - [self avatarForUser:self.selectedUser].backgroundColor = [UIColor clearColor]; + [self avatarForUser:[self selectedUserForThread]].backgroundColor = [UIColor clearColor]; else - [self avatarForUser:self.selectedUser].backgroundColor = self.avatarTemplate.backgroundColor; + [self avatarForUser:[self selectedUserForThread]].backgroundColor = self.avatarTemplate.backgroundColor; }]; }); } @@ -779,7 +779,7 @@ else if (textField == self.passwordField) { [self setSpinnerActive:YES]; - if (self.selectedUser.keyID) + if ([self selectedUserForThread].keyID) [self tryMasterPassword]; else @@ -980,7 +980,7 @@ if (sender.state != UIGestureRecognizerStateBegan) return; - if (self.selectedUser) + if ([self selectedUserForThread]) return; MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]]; @@ -1118,15 +1118,16 @@ #pragma mark - Core Data -- (MPUserEntity *)selectedUser { +- (MPUserEntity *)selectedUserForThread { + + return [self selectedUserInContext:[MPiOSAppDelegate managedObjectContextForThreadIfReady]]; +} + +- (MPUserEntity *)selectedUserInContext:(NSManagedObjectContext *)moc { if (!_selectedUserOID) return nil; - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) - return nil; - NSError *error; MPUserEntity *selectedUser = (MPUserEntity *)[moc existingObjectWithID:_selectedUserOID error:&error]; if (!selectedUser)