From 0df322f64856699c7a93683ca944dc3e51a66441 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Wed, 15 May 2013 22:42:21 -0400 Subject: [PATCH] Fixed saveKey getting unset and some MOC threading issues. [FIXED] Don't unset saveKey when loading the key fails. This causes saveKey to fail as soon as it's synced to a device that hasn't saved the key yet. [FIXED] A bunch of threading violation issues with thread-confined MOCs. --- MasterPassword/ObjC/MPAppDelegate_Key.m | 26 ++-- MasterPassword/ObjC/MPAppDelegate_Store.h | 4 +- MasterPassword/ObjC/MPAppDelegate_Store.m | 44 +++--- MasterPassword/ObjC/Mac/MPMacAppDelegate.m | 15 ++- .../ObjC/Mac/MPPasswordWindowController.m | 92 +++++++------ .../ObjC/iOS/MPElementListController.m | 22 +-- .../ObjC/iOS/MPPreferencesViewController.m | 54 ++++---- .../ObjC/iOS/MPUnlockViewController.m | 127 +++++++++--------- 8 files changed, 189 insertions(+), 195 deletions(-) diff --git a/MasterPassword/ObjC/MPAppDelegate_Key.m b/MasterPassword/ObjC/MPAppDelegate_Key.m index 0eb812de..74d2abd0 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Key.m +++ b/MasterPassword/ObjC/MPAppDelegate_Key.m @@ -24,14 +24,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) { - (MPKey *)loadSavedKeyFor:(MPUserEntity *)user { NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )]; - if (keyData) - inf(@"Found key in keychain for: %@", user.userID); - - else { - user.saveKey = NO; + if (!keyData) { inf(@"No key found in keychain for: %@", user.userID); + return nil; } + inf(@"Found key in keychain for: %@", user.userID); return [MPAlgorithmDefault keyFromKeyData:keyData]; } @@ -57,14 +55,10 @@ static NSDictionary *keyQuery(MPUserEntity *user) { - (void)forgetSavedKeyFor:(MPUserEntity *)user { OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery( user )]; - if (result == noErr || result == errSecItemNotFound) { - user.saveKey = NO; + if (result == noErr) { + inf(@"Removed key from keychain for: %@", user.userID); - if (result == noErr) { - inf(@"Removed key from keychain for: %@", user.userID); - - [[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self]; - } + [[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self]; } } @@ -80,7 +74,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { - (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password { if (password) - NSAssert(![NSThread isMainThread], @"Computing key may not happen from the main thread."); + NSAssert(![NSThread isMainThread], @"Computing key must not happen from the main thread."); MPKey *tryKey = nil; @@ -90,7 +84,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { user.keyID = tryKey.keyID; // Migrate existing elements. - [self migrateElementsForUser:user inContext:moc toKey:tryKey]; + [self migrateElementsForUser:user saveInContext:moc toKey:tryKey]; } } @@ -101,7 +95,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { else if (!tryKey) { // Key should be saved in keychain. Load it. - if ((tryKey = [self loadSavedKeyFor:user])) if (![user.keyID isEqual:tryKey.keyID]) { + if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:tryKey.keyID]) { // Loaded password doesn't match user's keyID. Forget saved password: it is incorrect. inf(@"Saved password doesn't match keyID for: %@", user.userID); @@ -164,7 +158,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { return YES; } -- (void)migrateElementsForUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey { +- (void)migrateElementsForUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey { if (![user.elements count]) // Nothing to migrate. diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.h b/MasterPassword/ObjC/MPAppDelegate_Store.h index 8b6ee409..d5a56ab9 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.h +++ b/MasterPassword/ObjC/MPAppDelegate_Store.h @@ -21,8 +21,8 @@ typedef enum { @interface MPAppDelegate_Shared(Store) + (NSManagedObjectContext *)managedObjectContextForThreadIfReady; -+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *moc))mocBlock; -+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *))mocBlock; ++ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock; ++ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock; - (UbiquityStoreManager *)storeManager; diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index 71038350..7c9299bb 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -54,7 +54,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, return threadManagedObjectContext; } -+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *))mocBlock { ++ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock { NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; if (!mainManagedObjectContext) @@ -69,7 +69,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, return YES; } -+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *))mocBlock { ++ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock { NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; if (!mainManagedObjectContext) @@ -430,6 +430,26 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, askImportPassword:(NSString *(^)(NSString *userName))importPassword askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword { + NSAssert(![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread."); + + __block MPImportResult result = MPImportResultCancelled; + do { + if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { + result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword + saveInContext:context]; + }]) + break; + usleep( (useconds_t)(USEC_PER_SEC * 0.2) ); + } while (YES); + + return result; +} + +- (MPImportResult)importSites:(NSString *)importedSitesString + askImportPassword:(NSString *(^)(NSString *userName))importPassword + askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword + saveInContext:(NSManagedObjectContext *)context { + // Compile patterns. static NSRegularExpression *headerPattern, *sitePattern; NSError *error = nil; @@ -452,12 +472,6 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, } } - // Get a MOC. - NSAssert(![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread."); - NSManagedObjectContext *moc; - while (!(moc = [MPAppDelegate_Shared managedObjectContextForThreadIfReady])) - usleep( (useconds_t)(USEC_PER_SEC * 0.2) ); - // Parse import data. inf(@"Importing sites."); __block MPUserEntity *user = nil; @@ -499,7 +513,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName]; - NSArray *users = [moc executeFetchRequest:userFetchRequest error:&error]; + NSArray *users = [context executeFetchRequest:userFetchRequest error:&error]; if (!users) { err(@"While looking for user: %@, error: %@", importUserName, error); return MPImportResultInternalError; @@ -552,7 +566,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, // Find existing site. if (user) { elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user]; - NSArray *existingSites = [moc executeFetchRequest:elementFetchRequest error:&error]; + NSArray *existingSites = [context executeFetchRequest:elementFetchRequest error:&error]; if (!existingSites) { err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error); return MPImportResultInternalError; @@ -585,13 +599,13 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, if (elementsToDelete.count) [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]); - [moc deleteObject:obj]; + [context deleteObject:obj]; }]; // Make sure there is a user. if (!user) { user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] ) - inManagedObjectContext:moc]; + inManagedObjectContext:context]; user.name = importUserName; user.keyID = importKeyID; dbg(@"Created User: %@", [user debugDescription]); @@ -609,7 +623,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, // Create new site. MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion( version ) classNameOfType:type] - inManagedObjectContext:moc]; + inManagedObjectContext:context]; element.name = name; element.user = user; element.type = type; @@ -632,10 +646,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, dbg(@"Created Element: %@", [element debugDescription]); } - if (![moc save:&error]) { - err(@"While saving imported sites: %@", error); + if (![context saveToStore]) return MPImportResultInternalError; - } inf(@"Import completed successfully."); MPCheckpoint( MPCheckpointSitesImported, nil ); diff --git a/MasterPassword/ObjC/Mac/MPMacAppDelegate.m b/MasterPassword/ObjC/Mac/MPMacAppDelegate.m index 56889e41..2c5401a0 100644 --- a/MasterPassword/ObjC/Mac/MPMacAppDelegate.m +++ b/MasterPassword/ObjC/Mac/MPMacAppDelegate.m @@ -126,13 +126,14 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven if (sender == self.rememberPasswordItem) [MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]]; if (sender == self.savePasswordItem) { - NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady]; - MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; - if ((activeUser.saveKey = !activeUser.saveKey)) - [[MPMacAppDelegate get] storeSavedKeyFor:activeUser]; - else - [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; - [moc saveToStore]; + [MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { + MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context]; + if ((activeUser.saveKey = !activeUser.saveKey)) + [[MPMacAppDelegate get] storeSavedKeyFor:activeUser]; + else + [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; + [context saveToStore]; + }]; } if (sender == self.dialogStyleRegular) [MPMacConfig get].dialogStyleHUD = @NO; diff --git a/MasterPassword/ObjC/Mac/MPPasswordWindowController.m b/MasterPassword/ObjC/Mac/MPPasswordWindowController.m index fba82d9f..d95db01a 100644 --- a/MasterPassword/ObjC/Mac/MPPasswordWindowController.m +++ b/MasterPassword/ObjC/Mac/MPPasswordWindowController.m @@ -81,39 +81,40 @@ - (void)unlock { - NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady]; - MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; - if (!activeUser) - // No user to sign in with. - return; - if ([MPMacAppDelegate get].key) - // Already logged in. - return; - if ([[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:nil]) - // Load the key from the keychain. - return; + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; + if (!activeUser) + // No user to sign in with. + return; + if ([MPMacAppDelegate get].key) + // Already logged in. + return; + if ([[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:nil]) + // Load the key from the keychain. + return; - if (![MPMacAppDelegate get].key) - // Ask the user to set the key through his master password. - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - if ([MPMacAppDelegate get].key) - return; + if (![MPMacAppDelegate get].key) + // Ask the user to set the key through his master password. + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + if ([MPMacAppDelegate get].key) + return; - self.content = @""; - [self.siteField setStringValue:@""]; - [self.tipField setStringValue:@""]; + self.content = @""; + [self.siteField setStringValue:@""]; + [self.tipField setStringValue:@""]; - NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked." - defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel" - informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@", - activeUser.name]; - NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; - [alert setAccessoryView:passwordField]; - [alert layout]; - [passwordField becomeFirstResponder]; - [alert beginSheetModalForWindow:self.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP]; - }]; + NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked." + defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel" + informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@", + activeUser.name]; + NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; + [alert setAccessoryView:passwordField]; + [alert layout]; + [passwordField becomeFirstResponder]; + [alert beginSheetModalForWindow:self.window modalDelegate:self + didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP]; + }]; + }]; } - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { @@ -123,8 +124,6 @@ return; } if (contextInfo == MPAlertUnlockMP) { - NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady]; - MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; switch (returnCode) { case NSAlertAlternateReturn: { // "Change" button. @@ -139,10 +138,13 @@ runModal]; if (returnCode_ == NSAlertDefaultReturn) { - activeUser.keyID = nil; - [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; - [[MPMacAppDelegate get] signOutAnimated:YES]; - [moc saveToStore]; + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; + activeUser.keyID = nil; + [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; + [[MPMacAppDelegate get] signOutAnimated:YES]; + [moc saveToStore]; + }]; } break; } @@ -160,13 +162,9 @@ self.inProgress = YES; NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue]; - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc_) { - NSError *error = nil; - MPUserEntity *activeUser_ = (MPUserEntity *)[moc_ existingObjectWithID:activeUser.objectID error:&error]; - if (!activeUser_) - err(@"Failed to retrieve active use while logging in: %@", error); - - BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc_ + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; + BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:password]; self.inProgress = NO; @@ -384,10 +382,10 @@ return; } - NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady]; - MPElementEntity *activeElement = [self activeElementInContext:moc]; - [activeElement use]; - [moc saveToStore]; + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + [[self activeElementInContext:moc] use]; + [moc saveToStore]; + }]; } - (void)createNewSite:(NSString *)siteName { diff --git a/MasterPassword/ObjC/iOS/MPElementListController.m b/MasterPassword/ObjC/iOS/MPElementListController.m index aa9a8b5a..ee661e80 100644 --- a/MasterPassword/ObjC/iOS/MPElementListController.m +++ b/MasterPassword/ObjC/iOS/MPElementListController.m @@ -25,17 +25,18 @@ - (NSFetchedResultsController *)fetchedResultsControllerByLastUsed { + NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must be accessed from the main thread."); + if (!_fetchedResultsControllerByLastUsed) { - NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must be accessed from the main thread."); - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) + NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; + if (!mainContext) return nil; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )]; fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector(lastUsed) ) ascending:NO] ]; [self configureFetchRequest:fetchRequest]; - _fetchedResultsControllerByLastUsed = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc - sectionNameKeyPath:nil cacheName:nil]; + _fetchedResultsControllerByLastUsed = [[NSFetchedResultsController alloc] + initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil]; _fetchedResultsControllerByLastUsed.delegate = self; } @@ -44,17 +45,18 @@ - (NSFetchedResultsController *)fetchedResultsControllerByUses { + NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must be accessed from the main thread."); + if (!_fetchedResultsControllerByUses) { - NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must be accessed from the main thread."); - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) + NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; + if (!mainContext) return nil; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )]; fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector(uses_) ) ascending:NO] ]; [self configureFetchRequest:fetchRequest]; - _fetchedResultsControllerByUses = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc - sectionNameKeyPath:nil cacheName:nil]; + _fetchedResultsControllerByUses = [[NSFetchedResultsController alloc] + initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil]; _fetchedResultsControllerByUses.delegate = self; } diff --git a/MasterPassword/ObjC/iOS/MPPreferencesViewController.m b/MasterPassword/ObjC/iOS/MPPreferencesViewController.m index 45543809..481de820 100644 --- a/MasterPassword/ObjC/iOS/MPPreferencesViewController.m +++ b/MasterPassword/ObjC/iOS/MPPreferencesViewController.m @@ -47,13 +47,10 @@ } options:0]; [avatar onSelect:^(BOOL selected) { if (selected) { - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) - return; - - MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; - activeUser.avatar = (unsigned)avatar.tag; - [moc saveToStore]; + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + [[MPiOSAppDelegate get] activeUserInContext:moc].avatar = (unsigned)avatar.tag; + [moc saveToStore]; + }]; } } options:0]; avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForThread].avatar); @@ -133,12 +130,10 @@ [[MPiOSAppDelegate get] export]; else if (cell == self.changeMPCell) { - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) - return; - - MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; - [[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil]; + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; + [[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil]; + }]; } [tableView deselectRowAtIndexPath:indexPath animated:YES]; @@ -148,15 +143,13 @@ - (void)didSelectType:(MPElementType)type { - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) - return; + self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:type]; - MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; - activeUser.defaultType = type; - [moc saveToStore]; - - self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType]; + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context]; + activeUser.defaultType = type; + [context saveToStore]; + }]; } - (MPElementType)selectedType { @@ -168,16 +161,15 @@ - (IBAction)didToggleSwitch:(UISwitch *)sender { - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) - return; - - MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; - if ((activeUser.saveKey = sender.on)) - [[MPiOSAppDelegate get] storeSavedKeyFor:activeUser]; - else - [[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser]; - [moc saveToStore]; + if (sender == self.savePasswordSwitch) + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; + if ((activeUser.saveKey = sender.on)) + [[MPiOSAppDelegate get] storeSavedKeyFor:activeUser]; + else + [[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser]; + [moc saveToStore]; + }]; } @end diff --git a/MasterPassword/ObjC/iOS/MPUnlockViewController.m b/MasterPassword/ObjC/iOS/MPUnlockViewController.m index af3d0499..ec08639a 100644 --- a/MasterPassword/ObjC/iOS/MPUnlockViewController.m +++ b/MasterPassword/ObjC/iOS/MPUnlockViewController.m @@ -298,29 +298,22 @@ - (void)updateUsers { - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - if (!moc) - return; - - __block NSArray *users = nil; - [moc performBlockAndWait:^{ + [MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { NSError *error = nil; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ]; - users = [moc executeFetchRequest:fetchRequest error:&error]; + NSArray *users = [context executeFetchRequest:fetchRequest error:&error]; if (!users) err(@"Failed to load users: %@", error); - }]; - // Clean up avatars. - for (UIView *subview in [self.avatarsView subviews]) - if ([[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) - // This subview is a former avatar. - [subview removeFromSuperview]; - [self.avatarToUserOID removeAllObjects]; + // Clean up avatars. + for (UIView *subview in [self.avatarsView subviews]) + if ([[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) + // This subview is a former avatar. + [subview removeFromSuperview]; + [self.avatarToUserOID removeAllObjects]; - // Create avatars. - [moc performBlockAndWait:^{ + // Create avatars. for (MPUserEntity *user in users) [self setupAvatar:[self.avatarTemplate clone] forUser:user]; [self setupAvatar:[self.avatarTemplate clone] forUser:nil]; @@ -374,11 +367,13 @@ - (void)didToggleUserSelection { - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - MPUserEntity *selectedUser = [self selectedUserInContext:moc]; + NSAssert([[NSThread currentThread] isMainThread], @"User selection should only be toggled from the main thread."); + + NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; + MPUserEntity *selectedUser = [self selectedUserInContext:mainContext]; if (!selectedUser) [self.passwordField resignFirstResponder]; - else if ([[MPiOSAppDelegate get] signInAsUser:selectedUser saveInContext:moc usingMasterPassword:nil]) { + else if ([[MPiOSAppDelegate get] signInAsUser:selectedUser saveInContext:mainContext usingMasterPassword:nil]) { [self performSegueWithIdentifier:@"MP_Unlock" sender:self]; return; } @@ -391,20 +386,18 @@ - (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar { - if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { - MPUserEntity - *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] ) - inManagedObjectContext:moc]; + if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] ) + inManagedObjectContext:context]; - [self showNewUserNameAlertFor:newUser inContext:moc completion:^(BOOL finished) { + [self showNewUserNameAlertFor:newUser saveInContext:context completion:^(BOOL finished) { newUserAvatar.selected = NO; - self.selectedUser = newUser; }]; }]) newUserAvatar.selected = NO; } -- (void)showNewUserNameAlertFor:(MPUserEntity *)newUser inContext:(NSManagedObjectContext *)moc +- (void)showNewUserNameAlertFor:(MPUserEntity *)newUser saveInContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL finished))completion { [PearlAlert showAlertWithTitle:@"Enter Your Name" @@ -425,36 +418,36 @@ if (!name.length) { [PearlAlert showAlertWithTitle:@"Name Is Required" message:nil viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { - [self showNewUserNameAlertFor:newUser inContext:moc completion:completion]; + [self showNewUserNameAlertFor:newUser saveInContext:context completion:completion]; } cancelTitle:@"Try Again" otherTitles:nil]; return; } // Save - [moc performBlockAndWait:^{ + [context performBlockAndWait:^{ newUser.name = name; }]; - [self showNewUserAvatarAlertFor:newUser inContext:moc completion:completion]; + [self showNewUserAvatarAlertFor:newUser saveInContext:context completion:completion]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil]; } -- (void)showNewUserAvatarAlertFor:(MPUserEntity *)newUser inContext:(NSManagedObjectContext *)moc +- (void)showNewUserAvatarAlertFor:(MPUserEntity *)newUser saveInContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL finished))completion { [PearlAlert showAlertWithTitle:@"Choose Your Avatar" message:@"\n\n\n\n\n\n" viewStyle:UIAlertViewStyleDefault initAlert:^(UIAlertView *_alert, UITextField *_firstField) { - [self initializeAvatarAlert:_alert forUser:newUser inContext:moc]; + [self initializeAvatarAlert:_alert forUser:newUser inContext:context]; } tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) { // Okay - [self showNewUserConfirmationAlertFor:newUser inContext:moc completion:completion]; + [self showNewUserConfirmationAlertFor:newUser saveInContext:context completion:completion]; } cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil]; } -- (void)showNewUserConfirmationAlertFor:(MPUserEntity *)newUser inContext:(NSManagedObjectContext *)moc +- (void)showNewUserConfirmationAlertFor:(MPUserEntity *)newUser saveInContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL finished))completion { [PearlAlert showAlertWithTitle:@"Is this correct?" @@ -467,16 +460,17 @@ } tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) { if (__buttonIndex == [__alert cancelButtonIndex]) { - [self showNewUserNameAlertFor:newUser inContext:moc completion:completion]; + [self showNewUserNameAlertFor:newUser saveInContext:context completion:completion]; return; } // Confirm - [moc performBlockAndWait:^{ - [moc saveToStore]; + [context performBlockAndWait:^{ + [context saveToStore]; NSError *error = nil; - if (![moc obtainPermanentIDsForObjects:@[ newUser ] error:&error]) + if (![context obtainPermanentIDsForObjects:@[ newUser ] error:&error]) err(@"Failed to obtain permanent object ID for new user: %@", error); + self.selectedUser = newUser; }]; completion( YES ); @@ -684,14 +678,14 @@ return avatar; } -- (MPUserEntity *)userForAvatar:(UIButton *)avatar inContext:(NSManagedObjectContext *)moc { +- (MPUserEntity *)userForAvatar:(UIButton *)avatar inContext:(NSManagedObjectContext *)context { NSManagedObjectID *userOID = NSNullToNil([self.avatarToUserOID objectForKey:[NSValue valueWithNonretainedObject:avatar]]); if (!userOID) return nil; NSError *error; - MPUserEntity *user = (MPUserEntity *)[moc existingObjectWithID:userOID error:&error]; + MPUserEntity *user = (MPUserEntity *)[context existingObjectWithID:userOID error:&error]; if (!user) err(@"Failed retrieving user for avatar: %@", error); @@ -1008,37 +1002,38 @@ if ([self selectedUserForThread]) return; - NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; - MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar] inContext:moc]; - if (!targetedUser) - return; - - [PearlSheet showSheetWithTitle:targetedUser.name - viewStyle:UIActionSheetStyleBlackTranslucent - initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { - if (buttonIndex == [sheet cancelButtonIndex]) + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar] inContext:context]; + if (!targetedUser) return; - if (buttonIndex == [sheet destructiveButtonIndex]) { - [moc performBlock:^{ - [moc deleteObject:targetedUser]; - [moc saveToStore]; + [PearlSheet showSheetWithTitle:targetedUser.name + viewStyle:UIActionSheetStyleBlackTranslucent + initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { + if (buttonIndex == [sheet cancelButtonIndex]) + return; - dispatch_async( dispatch_get_main_queue(), ^{ - [self updateUsers]; - } ); - }]; - return; - } + if (buttonIndex == [sheet destructiveButtonIndex]) { + [context performBlock:^{ + [context deleteObject:targetedUser]; + [context saveToStore]; - if (buttonIndex == [sheet firstOtherButtonIndex]) - [[MPiOSAppDelegate get] changeMasterPasswordFor:targetedUser saveInContext:moc didResetBlock:^{ - dispatch_async( dispatch_get_main_queue(), ^{ - [[self avatarForUser:targetedUser] setSelected:YES]; - } ); - }]; - } cancelTitle:[PearlStrings get].commonButtonCancel - destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil]; + dispatch_async( dispatch_get_main_queue(), ^{ + [self updateUsers]; + } ); + }]; + return; + } + + if (buttonIndex == [sheet firstOtherButtonIndex]) + [[MPiOSAppDelegate get] changeMasterPasswordFor:targetedUser saveInContext:context didResetBlock:^{ + dispatch_async( dispatch_get_main_queue(), ^{ + [[self avatarForUser:targetedUser] setSelected:YES]; + } ); + }]; + } cancelTitle:[PearlStrings get].commonButtonCancel + destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil]; + }]; } - (IBAction)facebook:(UIButton *)sender {