diff --git a/MasterPassword/ObjC/MPAppDelegate_Shared.h b/MasterPassword/ObjC/MPAppDelegate_Shared.h index 1e5f75d2..24213eba 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Shared.h +++ b/MasterPassword/ObjC/MPAppDelegate_Shared.h @@ -22,5 +22,6 @@ - (MPUserEntity *)activeUserForMainThread; - (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context; - (void)setActiveUser:(MPUserEntity *)activeUser; +- (void)handleCoordinatorError:(NSError *)error; @end diff --git a/MasterPassword/ObjC/MPAppDelegate_Shared.m b/MasterPassword/ObjC/MPAppDelegate_Shared.m index 3c075cd5..6f7836b5 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Shared.m +++ b/MasterPassword/ObjC/MPAppDelegate_Shared.m @@ -57,4 +57,8 @@ self.activeUserOID = activeUser.objectID; } +- (void)handleCoordinatorError:(NSError *)error { + +} + @end diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.h b/MasterPassword/ObjC/MPAppDelegate_Store.h index 41005bd6..9f5a7b04 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.h +++ b/MasterPassword/ObjC/MPAppDelegate_Store.h @@ -27,6 +27,7 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) { + (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock; - (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context; +- (void)deleteAndResetStore; /** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */ - (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion; diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index 497e7d5c..ed3b42e9 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -157,12 +157,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext err( @"Couldn't create our application support directory: %@", [error fullDescription] ); return; } - [self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:localStoreURL - options:@{ - NSMigratePersistentStoresAutomaticallyOption : @YES, - NSInferMappingModelAutomaticallyOption : @YES, - STORE_OPTIONS - } error:&error]; + if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL] + options:@{ + NSMigratePersistentStoresAutomaticallyOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES, + STORE_OPTIONS + } error:&error]) { + err( @"Failed to open store: %@", error ); + [self handleCoordinatorError:error]; + return; + } // Create our contexts and observer. self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; @@ -184,16 +188,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext }]; #if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - [self.mainManagedObjectContext saveToStore]; - }]; - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - [self.mainManagedObjectContext saveToStore]; - }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp + queue:[NSOperationQueue mainQueue] usingBlock: + ^(NSNotification *note) { + [self.mainManagedObjectContext saveToStore]; + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp + queue:[NSOperationQueue mainQueue] usingBlock: + ^(NSNotification *note) { + [self.mainManagedObjectContext saveToStore]; + }]; #else [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp queue:[NSOperationQueue mainQueue] usingBlock: @@ -210,6 +214,33 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext } } +- (void)deleteAndResetStore { + + @synchronized (self) { + // Unregister any existing observers and contexts. + if (self.saveObserver) + [[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver]; + [self.mainManagedObjectContext performBlockAndWait:^{ + [self.mainManagedObjectContext reset]; + self.mainManagedObjectContext = nil; + }]; + [self.privateManagedObjectContext performBlockAndWait:^{ + [self.privateManagedObjectContext reset]; + self.privateManagedObjectContext = nil; + }]; + NSError *error = nil; + for (NSPersistentStore *store in self.persistentStoreCoordinator.persistentStores) { + if (![self.persistentStoreCoordinator removePersistentStore:store error:&error]) + err( @"Couldn't remove persistence store from coordinator: %@", error ); + } + self.persistentStoreCoordinator = nil; + if (![[NSFileManager defaultManager] removeItemAtURL:self.localStoreURL error:&error]) + err( @"Couldn't remove persistence store at URL %@: %@", self.localStoreURL, error ); + + [self loadStore]; + } +} + - (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context { NSError *error = nil; diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index 820a52d4..6d02c472 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -341,6 +341,26 @@ showComposerForVC:viewController]; } +- (void)handleCoordinatorError:(NSError *)error { + + static dispatch_once_t once = 0; + dispatch_once( &once, ^{ + [PearlAlert showAlertWithTitle:@"Failed To Load Sites" message: + @"Master Password was unable to open your sites history.\n" + @"This may be due to corruption. You can either reset Master Password and " + @"recreate your user, or E-Mail us your logs and leave your corrupt store as-is for now." + viewStyle:UIAlertViewStyleDefault initAlert:nil + tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { + if (buttonIndex == [alert cancelButtonIndex]) + return; + if (buttonIndex == [alert firstOtherButtonIndex]) + [self openFeedbackWithLogs:YES forVC:nil]; + if (buttonIndex == [alert firstOtherButtonIndex] + 1) + [self deleteAndResetStore]; + } cancelTitle:@"Ignore" otherTitles:@"E-Mail Logs", @"Reset", nil]; + } ); +} + - (void)showExportForVC:(UIViewController *)viewController { [PearlAlert showAlertWithTitle:@"Exporting Your Sites"