2
0

Load store synchronously to not return racy or invalid contexts.

This commit is contained in:
Maarten Billemont 2020-09-03 13:57:16 -04:00
parent e58b9ef34f
commit f665aeccc4

View File

@ -51,7 +51,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady {
NSAssert( [[NSThread currentThread] isMainThread], @"Can only access main MOC from the main thread." );
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext || ![[NSThread currentThread] isMainThread])
return nil;
@ -155,97 +154,11 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
- (NSManagedObjectContext *)mainManagedObjectContextIfReady {
[self loadStore];
return self.mainManagedObjectContext;
}
- (NSManagedObjectContext *)privateManagedObjectContextIfReady {
NSAssert( [[NSThread currentThread] isMainThread], @"Can only access main MOC from the main thread." );
[self loadStore];
return self.privateManagedObjectContext;
}
- (NSURL *)localStoreURL {
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
return [[[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO]
URLByAppendingPathExtension:@"sqlite"];
}
- (void)loadStore {
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
(self.storeQueue = [NSOperationQueue new]).maxConcurrentOperationCount = 1;
} );
// Do nothing if already fully set up, otherwise (re-)load the store.
if (self.mainManagedObjectContext && self.privateManagedObjectContext)
return;
[self.storeQueue addOperationWithBlock:^{
// Do nothing if already fully set up, otherwise (re-)load the store.
if (self.mainManagedObjectContext && self.privateManagedObjectContext)
return;
// Unregister any existing observers and contexts.
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
[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.storeCoordinator.persistentStores)
if (![self.storeCoordinator removePersistentStore:store error:&error] || error) {
MPError( error, @"Couldn't remove persistence store from coordinator." );
return;
}
// Don't load when the store is corrupted.
if ([self.storeCorrupted boolValue])
return;
// Check if migration is necessary.
[self migrateStore];
// Create a new store coordinator.
NSURL *localStoreURL = [self localStoreURL];
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
MPError( error, @"Couldn't create our application support directory." );
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
self.mainManagedObjectContext = nil;
self.privateManagedObjectContext = nil;
return;
}
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:localStoreURL
options:@{
NSMigratePersistentStoresAutomaticallyOption: @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
MPError( error, @"Failed to open store." );
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
self.mainManagedObjectContext = nil;
self.privateManagedObjectContext = nil;
self.storeCorrupted = @YES;
[self handleCoordinatorError:error];
return;
}
self.storeCorrupted = @NO;
// Install managed object contexts and observers.
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.privateManagedObjectContext.persistentStoreCoordinator = self.storeCoordinator;
if (!self.mainManagedObjectContext && self.privateManagedObjectContext.persistentStoreCoordinator) {
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
if (@available( iOS 10.0, macOS 10.12, * ))
@ -275,6 +188,86 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
[self.mainManagedObjectContext saveToStore];
} );
#endif
}
return self.mainManagedObjectContext;
}
- (NSManagedObjectContext *)privateManagedObjectContextIfReady {
[self loadStore];
return self.privateManagedObjectContext;
}
- (NSURL *)localStoreURL {
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
return [[[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO]
URLByAppendingPathExtension:@"sqlite"];
}
- (void)loadStore {
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
(self.storeQueue = [NSOperationQueue new]).maxConcurrentOperationCount = 1;
} );
// Do nothing if already fully set up, otherwise (re-)load the store.
if ([self.storeCoordinator.persistentStores count])
return;
NSOperation *storeOperation = [NSBlockOperation blockOperationWithBlock:^{
// Do nothing if already fully set up, otherwise (re-)load the store.
if ([self.storeCoordinator.persistentStores count])
return;
// Unregister any existing observers and contexts.
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
[self.mainManagedObjectContext performBlockAndWait:^{
[self.mainManagedObjectContext reset];
self.mainManagedObjectContext = nil;
}];
[self.privateManagedObjectContext performBlockAndWait:^{
[self.privateManagedObjectContext reset];
self.privateManagedObjectContext = nil;
}];
// Don't load when the store is corrupted.
if ([self.storeCorrupted boolValue])
return;
// Check if migration is necessary.
[self migrateStore];
// Create a new store coordinator.
NSURL *localStoreURL = [self localStoreURL];
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
MPError( error, @"Couldn't create our application support directory." );
return;
}
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:localStoreURL
options:@{
NSMigratePersistentStoresAutomaticallyOption: @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
MPError( error, @"Failed to open store." );
self.storeCorrupted = @YES;
[self handleCoordinatorError:error];
return;
}
self.storeCorrupted = @NO;
// Install managed object contexts and observers.
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.privateManagedObjectContext.persistentStoreCoordinator = self.storeCoordinator;
// Perform a data sanity check on the newly loaded store to find and fix any issues.
if ([[MPConfig get].checkInconsistency boolValue])
@ -282,6 +275,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
[self findAndFixInconsistenciesSaveInContext:context];
}];
}];
[self.storeQueue addOperations:@[ storeOperation ] waitUntilFinished:YES];
}
- (void)retryCorruptStore {