2
0

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.
This commit is contained in:
Maarten Billemont 2013-05-15 22:42:21 -04:00
parent cb860cec96
commit 0df322f648
8 changed files with 189 additions and 195 deletions

View File

@ -24,14 +24,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user { - (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )]; NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )];
if (keyData) if (!keyData) {
inf(@"Found key in keychain for: %@", user.userID);
else {
user.saveKey = NO;
inf(@"No key found in keychain for: %@", user.userID); inf(@"No key found in keychain for: %@", user.userID);
return nil;
} }
inf(@"Found key in keychain for: %@", user.userID);
return [MPAlgorithmDefault keyFromKeyData:keyData]; return [MPAlgorithmDefault keyFromKeyData:keyData];
} }
@ -57,14 +55,10 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
- (void)forgetSavedKeyFor:(MPUserEntity *)user { - (void)forgetSavedKeyFor:(MPUserEntity *)user {
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery( user )]; OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery( user )];
if (result == noErr || result == errSecItemNotFound) { if (result == noErr) {
user.saveKey = NO; inf(@"Removed key from keychain for: %@", user.userID);
if (result == noErr) { [[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self];
inf(@"Removed key from keychain for: %@", user.userID);
[[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 { - (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password {
if (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; MPKey *tryKey = nil;
@ -90,7 +84,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
user.keyID = tryKey.keyID; user.keyID = tryKey.keyID;
// Migrate existing elements. // 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) { else if (!tryKey) {
// Key should be saved in keychain. Load it. // 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. // Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
inf(@"Saved password doesn't match keyID for: %@", user.userID); inf(@"Saved password doesn't match keyID for: %@", user.userID);
@ -164,7 +158,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
return YES; 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]) if (![user.elements count])
// Nothing to migrate. // Nothing to migrate.

View File

@ -21,8 +21,8 @@ typedef enum {
@interface MPAppDelegate_Shared(Store)<UbiquityStoreManagerDelegate> @interface MPAppDelegate_Shared(Store)<UbiquityStoreManagerDelegate>
+ (NSManagedObjectContext *)managedObjectContextForThreadIfReady; + (NSManagedObjectContext *)managedObjectContextForThreadIfReady;
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *moc))mocBlock; + (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock;
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *))mocBlock; + (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock;
- (UbiquityStoreManager *)storeManager; - (UbiquityStoreManager *)storeManager;

View File

@ -54,7 +54,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
return threadManagedObjectContext; return threadManagedObjectContext;
} }
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *))mocBlock { + (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock {
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext) if (!mainManagedObjectContext)
@ -69,7 +69,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
return YES; return YES;
} }
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *))mocBlock { + (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock {
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext) if (!mainManagedObjectContext)
@ -430,6 +430,26 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
askImportPassword:(NSString *(^)(NSString *userName))importPassword askImportPassword:(NSString *(^)(NSString *userName))importPassword
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword { 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. // Compile patterns.
static NSRegularExpression *headerPattern, *sitePattern; static NSRegularExpression *headerPattern, *sitePattern;
NSError *error = nil; 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. // Parse import data.
inf(@"Importing sites."); inf(@"Importing sites.");
__block MPUserEntity *user = nil; __block MPUserEntity *user = nil;
@ -499,7 +513,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
NSArray *users = [moc executeFetchRequest:userFetchRequest error:&error]; NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
if (!users) { if (!users) {
err(@"While looking for user: %@, error: %@", importUserName, error); err(@"While looking for user: %@, error: %@", importUserName, error);
return MPImportResultInternalError; return MPImportResultInternalError;
@ -552,7 +566,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
// Find existing site. // Find existing site.
if (user) { if (user) {
elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, 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) { if (!existingSites) {
err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error); err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error);
return MPImportResultInternalError; return MPImportResultInternalError;
@ -585,13 +599,13 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
if (elementsToDelete.count) if (elementsToDelete.count)
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]); 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. // Make sure there is a user.
if (!user) { if (!user) {
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] ) user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
inManagedObjectContext:moc]; inManagedObjectContext:context];
user.name = importUserName; user.name = importUserName;
user.keyID = importKeyID; user.keyID = importKeyID;
dbg(@"Created User: %@", [user debugDescription]); dbg(@"Created User: %@", [user debugDescription]);
@ -609,7 +623,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
// Create new site. // Create new site.
MPElementEntity MPElementEntity
*element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion( version ) classNameOfType:type] *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion( version ) classNameOfType:type]
inManagedObjectContext:moc]; inManagedObjectContext:context];
element.name = name; element.name = name;
element.user = user; element.user = user;
element.type = type; element.type = type;
@ -632,10 +646,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
dbg(@"Created Element: %@", [element debugDescription]); dbg(@"Created Element: %@", [element debugDescription]);
} }
if (![moc save:&error]) { if (![context saveToStore])
err(@"While saving imported sites: %@", error);
return MPImportResultInternalError; return MPImportResultInternalError;
}
inf(@"Import completed successfully."); inf(@"Import completed successfully.");
MPCheckpoint( MPCheckpointSitesImported, nil ); MPCheckpoint( MPCheckpointSitesImported, nil );

View File

@ -126,13 +126,14 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
if (sender == self.rememberPasswordItem) if (sender == self.rememberPasswordItem)
[MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]]; [MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]];
if (sender == self.savePasswordItem) { if (sender == self.savePasswordItem) {
NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady]; [MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
if ((activeUser.saveKey = !activeUser.saveKey)) if ((activeUser.saveKey = !activeUser.saveKey))
[[MPMacAppDelegate get] storeSavedKeyFor:activeUser]; [[MPMacAppDelegate get] storeSavedKeyFor:activeUser];
else else
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[moc saveToStore]; [context saveToStore];
}];
} }
if (sender == self.dialogStyleRegular) if (sender == self.dialogStyleRegular)
[MPMacConfig get].dialogStyleHUD = @NO; [MPMacConfig get].dialogStyleHUD = @NO;

View File

@ -81,39 +81,40 @@
- (void)unlock { - (void)unlock {
NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady]; [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
if (!activeUser) if (!activeUser)
// No user to sign in with. // No user to sign in with.
return; return;
if ([MPMacAppDelegate get].key) if ([MPMacAppDelegate get].key)
// Already logged in. // Already logged in.
return; return;
if ([[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:nil]) if ([[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:nil])
// Load the key from the keychain. // Load the key from the keychain.
return; return;
if (![MPMacAppDelegate get].key) if (![MPMacAppDelegate get].key)
// Ask the user to set the key through his master password. // Ask the user to set the key through his master password.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
if ([MPMacAppDelegate get].key) if ([MPMacAppDelegate get].key)
return; return;
self.content = @""; self.content = @"";
[self.siteField setStringValue:@""]; [self.siteField setStringValue:@""];
[self.tipField setStringValue:@""]; [self.tipField setStringValue:@""];
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked." NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel" defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel"
informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@", informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@",
activeUser.name]; activeUser.name];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:passwordField]; [alert setAccessoryView:passwordField];
[alert layout]; [alert layout];
[passwordField becomeFirstResponder]; [passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.window modalDelegate:self [alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP]; didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP];
}]; }];
}];
} }
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
@ -123,8 +124,6 @@
return; return;
} }
if (contextInfo == MPAlertUnlockMP) { if (contextInfo == MPAlertUnlockMP) {
NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady];
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
switch (returnCode) { switch (returnCode) {
case NSAlertAlternateReturn: { case NSAlertAlternateReturn: {
// "Change" button. // "Change" button.
@ -139,10 +138,13 @@
runModal]; runModal];
if (returnCode_ == NSAlertDefaultReturn) { if (returnCode_ == NSAlertDefaultReturn) {
activeUser.keyID = nil; [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
[[MPMacAppDelegate get] signOutAnimated:YES]; activeUser.keyID = nil;
[moc saveToStore]; [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[[MPMacAppDelegate get] signOutAnimated:YES];
[moc saveToStore];
}];
} }
break; break;
} }
@ -160,13 +162,9 @@
self.inProgress = YES; self.inProgress = YES;
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue]; NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc_) { [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
NSError *error = nil; MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
MPUserEntity *activeUser_ = (MPUserEntity *)[moc_ existingObjectWithID:activeUser.objectID error:&error]; BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc
if (!activeUser_)
err(@"Failed to retrieve active use while logging in: %@", error);
BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc_
usingMasterPassword:password]; usingMasterPassword:password];
self.inProgress = NO; self.inProgress = NO;
@ -384,10 +382,10 @@
return; return;
} }
NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady]; [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPElementEntity *activeElement = [self activeElementInContext:moc]; [[self activeElementInContext:moc] use];
[activeElement use]; [moc saveToStore];
[moc saveToStore]; }];
} }
- (void)createNewSite:(NSString *)siteName { - (void)createNewSite:(NSString *)siteName {

View File

@ -25,17 +25,18 @@
- (NSFetchedResultsController *)fetchedResultsControllerByLastUsed { - (NSFetchedResultsController *)fetchedResultsControllerByLastUsed {
NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must be accessed from the main thread.");
if (!_fetchedResultsControllerByLastUsed) { if (!_fetchedResultsControllerByLastUsed) {
NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must be accessed from the main thread."); NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; if (!mainContext)
if (!moc)
return nil; return nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector(lastUsed) ) ascending:NO] ]; fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector(lastUsed) ) ascending:NO] ];
[self configureFetchRequest:fetchRequest]; [self configureFetchRequest:fetchRequest];
_fetchedResultsControllerByLastUsed = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc _fetchedResultsControllerByLastUsed = [[NSFetchedResultsController alloc]
sectionNameKeyPath:nil cacheName:nil]; initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsControllerByLastUsed.delegate = self; _fetchedResultsControllerByLastUsed.delegate = self;
} }
@ -44,17 +45,18 @@
- (NSFetchedResultsController *)fetchedResultsControllerByUses { - (NSFetchedResultsController *)fetchedResultsControllerByUses {
NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must be accessed from the main thread.");
if (!_fetchedResultsControllerByUses) { if (!_fetchedResultsControllerByUses) {
NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must be accessed from the main thread."); NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; if (!mainContext)
if (!moc)
return nil; return nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector(uses_) ) ascending:NO] ]; fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector(uses_) ) ascending:NO] ];
[self configureFetchRequest:fetchRequest]; [self configureFetchRequest:fetchRequest];
_fetchedResultsControllerByUses = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc _fetchedResultsControllerByUses = [[NSFetchedResultsController alloc]
sectionNameKeyPath:nil cacheName:nil]; initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsControllerByUses.delegate = self; _fetchedResultsControllerByUses.delegate = self;
} }

View File

@ -47,13 +47,10 @@
} options:0]; } options:0];
[avatar onSelect:^(BOOL selected) { [avatar onSelect:^(BOOL selected) {
if (selected) { if (selected) {
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
if (!moc) [[MPiOSAppDelegate get] activeUserInContext:moc].avatar = (unsigned)avatar.tag;
return; [moc saveToStore];
}];
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
activeUser.avatar = (unsigned)avatar.tag;
[moc saveToStore];
} }
} options:0]; } options:0];
avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForThread].avatar); avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForThread].avatar);
@ -133,12 +130,10 @@
[[MPiOSAppDelegate get] export]; [[MPiOSAppDelegate get] export];
else if (cell == self.changeMPCell) { else if (cell == self.changeMPCell) {
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
if (!moc) MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
return; [[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil];
}];
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
[[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil];
} }
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
@ -148,15 +143,13 @@
- (void)didSelectType:(MPElementType)type { - (void)didSelectType:(MPElementType)type {
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:type];
if (!moc)
return;
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
activeUser.defaultType = type; MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
[moc saveToStore]; activeUser.defaultType = type;
[context saveToStore];
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType]; }];
} }
- (MPElementType)selectedType { - (MPElementType)selectedType {
@ -168,16 +161,15 @@
- (IBAction)didToggleSwitch:(UISwitch *)sender { - (IBAction)didToggleSwitch:(UISwitch *)sender {
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; if (sender == self.savePasswordSwitch)
if (!moc) [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
return; MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
if ((activeUser.saveKey = sender.on))
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; [[MPiOSAppDelegate get] storeSavedKeyFor:activeUser];
if ((activeUser.saveKey = sender.on)) else
[[MPiOSAppDelegate get] storeSavedKeyFor:activeUser]; [[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser];
else [moc saveToStore];
[[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser]; }];
[moc saveToStore];
} }
@end @end

View File

@ -298,29 +298,22 @@
- (void)updateUsers { - (void)updateUsers {
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; [MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
if (!moc)
return;
__block NSArray *users = nil;
[moc performBlockAndWait:^{
NSError *error = nil; NSError *error = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ]; fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
users = [moc executeFetchRequest:fetchRequest error:&error]; NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
if (!users) if (!users)
err(@"Failed to load users: %@", error); err(@"Failed to load users: %@", error);
}];
// Clean up avatars. // Clean up avatars.
for (UIView *subview in [self.avatarsView subviews]) for (UIView *subview in [self.avatarsView subviews])
if ([[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) if ([[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
// This subview is a former avatar. // This subview is a former avatar.
[subview removeFromSuperview]; [subview removeFromSuperview];
[self.avatarToUserOID removeAllObjects]; [self.avatarToUserOID removeAllObjects];
// Create avatars. // Create avatars.
[moc performBlockAndWait:^{
for (MPUserEntity *user in users) for (MPUserEntity *user in users)
[self setupAvatar:[self.avatarTemplate clone] forUser:user]; [self setupAvatar:[self.avatarTemplate clone] forUser:user];
[self setupAvatar:[self.avatarTemplate clone] forUser:nil]; [self setupAvatar:[self.avatarTemplate clone] forUser:nil];
@ -374,11 +367,13 @@
- (void)didToggleUserSelection { - (void)didToggleUserSelection {
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; NSAssert([[NSThread currentThread] isMainThread], @"User selection should only be toggled from the main thread.");
MPUserEntity *selectedUser = [self selectedUserInContext:moc];
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
MPUserEntity *selectedUser = [self selectedUserInContext:mainContext];
if (!selectedUser) if (!selectedUser)
[self.passwordField resignFirstResponder]; [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]; [self performSegueWithIdentifier:@"MP_Unlock" sender:self];
return; return;
} }
@ -391,20 +386,18 @@
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar { - (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
*newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] ) inManagedObjectContext:context];
inManagedObjectContext:moc];
[self showNewUserNameAlertFor:newUser inContext:moc completion:^(BOOL finished) { [self showNewUserNameAlertFor:newUser saveInContext:context completion:^(BOOL finished) {
newUserAvatar.selected = NO; newUserAvatar.selected = NO;
self.selectedUser = newUser;
}]; }];
}]) }])
newUserAvatar.selected = NO; newUserAvatar.selected = NO;
} }
- (void)showNewUserNameAlertFor:(MPUserEntity *)newUser inContext:(NSManagedObjectContext *)moc - (void)showNewUserNameAlertFor:(MPUserEntity *)newUser saveInContext:(NSManagedObjectContext *)context
completion:(void (^)(BOOL finished))completion { completion:(void (^)(BOOL finished))completion {
[PearlAlert showAlertWithTitle:@"Enter Your Name" [PearlAlert showAlertWithTitle:@"Enter Your Name"
@ -425,36 +418,36 @@
if (!name.length) { if (!name.length) {
[PearlAlert showAlertWithTitle:@"Name Is Required" message:nil viewStyle:UIAlertViewStyleDefault initAlert:nil [PearlAlert showAlertWithTitle:@"Name Is Required" message:nil viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
[self showNewUserNameAlertFor:newUser inContext:moc completion:completion]; [self showNewUserNameAlertFor:newUser saveInContext:context completion:completion];
} cancelTitle:@"Try Again" otherTitles:nil]; } cancelTitle:@"Try Again" otherTitles:nil];
return; return;
} }
// Save // Save
[moc performBlockAndWait:^{ [context performBlockAndWait:^{
newUser.name = name; 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]; 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 { completion:(void (^)(BOOL finished))completion {
[PearlAlert showAlertWithTitle:@"Choose Your Avatar" [PearlAlert showAlertWithTitle:@"Choose Your Avatar"
message:@"\n\n\n\n\n\n" viewStyle:UIAlertViewStyleDefault message:@"\n\n\n\n\n\n" viewStyle:UIAlertViewStyleDefault
initAlert:^(UIAlertView *_alert, UITextField *_firstField) { initAlert:^(UIAlertView *_alert, UITextField *_firstField) {
[self initializeAvatarAlert:_alert forUser:newUser inContext:moc]; [self initializeAvatarAlert:_alert forUser:newUser inContext:context];
} }
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) { tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
// Okay // Okay
[self showNewUserConfirmationAlertFor:newUser inContext:moc completion:completion]; [self showNewUserConfirmationAlertFor:newUser saveInContext:context completion:completion];
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil]; } 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 { completion:(void (^)(BOOL finished))completion {
[PearlAlert showAlertWithTitle:@"Is this correct?" [PearlAlert showAlertWithTitle:@"Is this correct?"
@ -467,16 +460,17 @@
} }
tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) { tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) {
if (__buttonIndex == [__alert cancelButtonIndex]) { if (__buttonIndex == [__alert cancelButtonIndex]) {
[self showNewUserNameAlertFor:newUser inContext:moc completion:completion]; [self showNewUserNameAlertFor:newUser saveInContext:context completion:completion];
return; return;
} }
// Confirm // Confirm
[moc performBlockAndWait:^{ [context performBlockAndWait:^{
[moc saveToStore]; [context saveToStore];
NSError *error = nil; 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); err(@"Failed to obtain permanent object ID for new user: %@", error);
self.selectedUser = newUser;
}]; }];
completion( YES ); completion( YES );
@ -684,14 +678,14 @@
return avatar; 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]]); NSManagedObjectID *userOID = NSNullToNil([self.avatarToUserOID objectForKey:[NSValue valueWithNonretainedObject:avatar]]);
if (!userOID) if (!userOID)
return nil; return nil;
NSError *error; NSError *error;
MPUserEntity *user = (MPUserEntity *)[moc existingObjectWithID:userOID error:&error]; MPUserEntity *user = (MPUserEntity *)[context existingObjectWithID:userOID error:&error];
if (!user) if (!user)
err(@"Failed retrieving user for avatar: %@", error); err(@"Failed retrieving user for avatar: %@", error);
@ -1008,37 +1002,38 @@
if ([self selectedUserForThread]) if ([self selectedUserForThread])
return; return;
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady]; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar] inContext:moc]; MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar] inContext:context];
if (!targetedUser) if (!targetedUser)
return;
[PearlSheet showSheetWithTitle:targetedUser.name
viewStyle:UIActionSheetStyleBlackTranslucent
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return; return;
if (buttonIndex == [sheet destructiveButtonIndex]) { [PearlSheet showSheetWithTitle:targetedUser.name
[moc performBlock:^{ viewStyle:UIActionSheetStyleBlackTranslucent
[moc deleteObject:targetedUser]; initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
[moc saveToStore]; if (buttonIndex == [sheet cancelButtonIndex])
return;
dispatch_async( dispatch_get_main_queue(), ^{ if (buttonIndex == [sheet destructiveButtonIndex]) {
[self updateUsers]; [context performBlock:^{
} ); [context deleteObject:targetedUser];
}]; [context saveToStore];
return;
}
if (buttonIndex == [sheet firstOtherButtonIndex]) dispatch_async( dispatch_get_main_queue(), ^{
[[MPiOSAppDelegate get] changeMasterPasswordFor:targetedUser saveInContext:moc didResetBlock:^{ [self updateUsers];
dispatch_async( dispatch_get_main_queue(), ^{ } );
[[self avatarForUser:targetedUser] setSelected:YES]; }];
} ); return;
}]; }
} cancelTitle:[PearlStrings get].commonButtonCancel
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil]; 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 { - (IBAction)facebook:(UIButton *)sender {