2
0

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.
This commit is contained in:
Maarten Billemont 2013-04-28 00:33:28 -04:00
parent 07e44a46ae
commit d27a0fdbad
9 changed files with 147 additions and 180 deletions

@ -1 +1 @@
Subproject commit 25a30fadcb80e337a664127c6c86c4fc46e96e5f
Subproject commit 9a4d52c382ae01f81cc9d94982b58222b3240f73

View File

@ -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;

View File

@ -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];

View File

@ -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 {

View File

@ -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

View File

@ -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];

View File

@ -63,11 +63,6 @@
}
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[self.delegate didSelectElement:nil];
}
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.text = @"";

View File

@ -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];

View File

@ -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)