diff --git a/Common/Collection View/GameCollectionViewDataSource.swift b/Common/Collection View/GameCollectionViewDataSource.swift index cbfcd64..2670a03 100644 --- a/Common/Collection View/GameCollectionViewDataSource.swift +++ b/Common/Collection View/GameCollectionViewDataSource.swift @@ -64,7 +64,7 @@ class GameCollectionViewDataSource: NSObject fetchRequest.sortDescriptors = [NSSortDescriptor(key: Game.Attributes.type.rawValue, ascending: true), NSSortDescriptor(key: Game.Attributes.name.rawValue, ascending: true)] - self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: Game.Attributes.type.rawValue, cacheName: nil) + self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: Game.Attributes.type.rawValue, cacheName: nil) self.fetchedResultsController.delegate = previousDelegate self.update() diff --git a/Common/Database/DatabaseManager.swift b/Common/Database/DatabaseManager.swift index 3593831..eab085d 100644 --- a/Common/Database/DatabaseManager.swift +++ b/Common/Database/DatabaseManager.swift @@ -6,114 +6,54 @@ // Copyright © 2015 Riley Testut. All rights reserved. // +import Foundation import CoreData // Workspace -import Roxas import DeltaCore // Pods import FileMD5Hash -class DatabaseManager +final class DatabaseManager: NSPersistentContainer { - static let sharedManager = DatabaseManager() - - let managedObjectContext: NSManagedObjectContext - - private let privateManagedObjectContext: NSManagedObjectContext - private let validationManagedObjectContext: NSManagedObjectContext - - // MARK: - Initialization - - /// Initialization + static let shared = DatabaseManager() private init() { - let modelURL = Bundle.main.url(forResource: "Model", withExtension: "momd") - let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL!) - let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!) + guard + let modelURL = Bundle(for: DatabaseManager.self).url(forResource: "Model", withExtension: "momd"), + let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) + else { fatalError("Core Data model cannot be found. Aborting.") } - self.privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - self.privateManagedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator - self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + super.init(name: "Delta", managedObjectModel: managedObjectModel) - self.managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) - self.managedObjectContext.parent = self.privateManagedObjectContext - self.managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - self.validationManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - self.validationManagedObjectContext.parent = self.managedObjectContext - self.validationManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - NotificationCenter.default.addObserver(self, selector: #selector(DatabaseManager.managedObjectContextWillSave(_:)), name: NSNotification.Name.NSManagedObjectContextWillSave, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(DatabaseManager.managedObjectContextDidSave(_:)), name: NSNotification.Name.NSManagedObjectContextDidSave, object: nil) - + self.viewContext.automaticallyMergesChangesFromParent = true } - - func startWithCompletion(_ completionBlock: ((performingMigration: Bool) -> Void)?) - { - DispatchQueue.global(qos: .userInitiated).async { - - let storeURL = DatabaseManager.databaseDirectoryURL.appendingPathComponent("Delta.sqlite") +} - let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true] - - var performingMigration = false - - if - let sourceMetadata = try? NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: options), - let managedObjectModel = self.privateManagedObjectContext.persistentStoreCoordinator?.managedObjectModel - { - performingMigration = !managedObjectModel.isConfiguration(withName: nil, compatibleWithStoreMetadata: sourceMetadata) - } - - do - { - try self.privateManagedObjectContext.persistentStoreCoordinator?.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options) - } - catch let error as NSError - { - if error.code == NSMigrationMissingSourceModelError - { - print("Migration failed. Try deleting \(storeURL)") - } - else - { - print(error) - } - - abort() - } - - if let completionBlock = completionBlock - { - completionBlock(performingMigration: performingMigration) - } - } - } - - // MARK: - Importing - - /// Importing - - func importGamesAtURLs(_ URLs: [URL], withCompletion completion: (([String]) -> Void)?) +//MARK: - Importing - +/// Importing +extension DatabaseManager +{ + func importGames(at urls: [URL], completion: (([String]) -> Void)?) { - let managedObjectContext = self.backgroundManagedObjectContext() - managedObjectContext.perform() { + self.performBackgroundTask { (context) in var identifiers: [String] = [] - for URL in URLs + for url in urls { - let identifier = FileHash.sha1HashOfFile(atPath: URL.path) as String + let identifier = FileHash.sha1HashOfFile(atPath: url.path) as String - let filename = identifier + "." + URL.pathExtension + let filename = identifier + "." + url.pathExtension - let game = Game.insertIntoManagedObjectContext(managedObjectContext) - game.name = URL.deletingPathExtension().lastPathComponent ?? NSLocalizedString("Game", comment: "") + let game = Game.insertIntoManagedObjectContext(context) + game.name = url.deletingPathExtension().lastPathComponent ?? NSLocalizedString("Game", comment: "") game.identifier = identifier game.filename = filename - let gameCollection = GameCollection.gameSystemCollectionForPathExtension(URL.pathExtension, inManagedObjectContext: managedObjectContext) + let gameCollection = GameCollection.gameSystemCollectionForPathExtension(url.pathExtension, inManagedObjectContext: context) game.type = GameType(rawValue: gameCollection.identifier) game.gameCollections.insert(gameCollection) @@ -123,11 +63,11 @@ class DatabaseManager if FileManager.default.fileExists(atPath: destinationURL.path) { - try FileManager.default.removeItem(at: URL) + try FileManager.default.removeItem(at: url) } else { - try FileManager.default.moveItem(at: URL, to: destinationURL) + try FileManager.default.moveItem(at: url, to: destinationURL) } identifiers.append(game.identifier) @@ -141,9 +81,9 @@ class DatabaseManager do { - try managedObjectContext.save() + try context.save() } - catch let error as NSError + catch { print("Failed to save import context:", error) @@ -154,27 +94,16 @@ class DatabaseManager { completion(identifiers) } + } - - - } - - // MARK: - Background Contexts - - /// Background Contexts - - func backgroundManagedObjectContext() -> NSManagedObjectContext - { - let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - managedObjectContext.parent = self.validationManagedObjectContext - managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - return managedObjectContext } } +//MARK: - File URLs - +/// File URLs extension DatabaseManager { - class var databaseDirectoryURL: URL + override class func defaultDirectoryURL() -> URL { let documentsDirectoryURL: URL @@ -188,23 +117,23 @@ extension DatabaseManager } let databaseDirectoryURL = documentsDirectoryURL.appendingPathComponent("Database") - self.createDirectoryAtURLIfNeeded(databaseDirectoryURL) + self.createDirectory(at: databaseDirectoryURL) return databaseDirectoryURL } - + class var gamesDirectoryURL: URL { - let gamesDirectoryURL = DatabaseManager.databaseDirectoryURL.appendingPathComponent("Games") - self.createDirectoryAtURLIfNeeded(gamesDirectoryURL) + let gamesDirectoryURL = DatabaseManager.defaultDirectoryURL().appendingPathComponent("Games") + self.createDirectory(at: gamesDirectoryURL) return gamesDirectoryURL } class var saveStatesDirectoryURL: URL { - let saveStatesDirectoryURL = DatabaseManager.databaseDirectoryURL.appendingPathComponent("Save States") - self.createDirectoryAtURLIfNeeded(saveStatesDirectoryURL) + let saveStatesDirectoryURL = DatabaseManager.defaultDirectoryURL().appendingPathComponent("Save States") + self.createDirectory(at: saveStatesDirectoryURL) return saveStatesDirectoryURL } @@ -212,141 +141,20 @@ extension DatabaseManager class func saveStatesDirectoryURLForGame(_ game: Game) -> URL { let gameDirectoryURL = DatabaseManager.saveStatesDirectoryURL.appendingPathComponent(game.identifier) - self.createDirectoryAtURLIfNeeded(gameDirectoryURL) + self.createDirectory(at: gameDirectoryURL) return gameDirectoryURL } } +//MARK: - Private - private extension DatabaseManager { - // MARK: - Saving - - - func save() - { - let backgroundTaskIdentifier = RSTBeginBackgroundTask("Save Database Task") - - self.validationManagedObjectContext.performAndWait { - - do - { - try self.validationManagedObjectContext.save() - } - catch let error as NSError - { - print("Failed to save validation context:", error) - } - - - // Update main managed object context - self.managedObjectContext.performAndWait() { - - do - { - try self.managedObjectContext.save() - } - catch let error as NSError - { - print("Failed to save main context:", error) - } - - - // Save to disk - self.privateManagedObjectContext.perform() { - - do - { - try self.privateManagedObjectContext.save() - } - catch let error as NSError - { - print("Failed to save private context to disk:", error) - } - - RSTEndBackgroundTask(backgroundTaskIdentifier) - - } - - } - - } - } - - // MARK: - Validation - - - func validateManagedObjectContextSave(_ managedObjectContext: NSManagedObjectContext) - { - // Remove deleted files from disk - for object in managedObjectContext.deletedObjects - { - var fileURLs = Set() - - let temporaryObject = self.validationManagedObjectContext.object(with: object.objectID) - switch temporaryObject - { - case let game as Game: - fileURLs.insert(game.fileURL as URL) - - case let saveState as SaveState: - fileURLs.insert(saveState.fileURL as URL) - fileURLs.insert(saveState.imageFileURL as URL) - - default: break - } - - for URL in fileURLs - { - do - { - try FileManager.default.removeItem(at: URL) - } - catch let error as NSError - { - print(error) - } - } - } - - // Remove empty collections - let collections = GameCollection.instancesWithPredicate(NSPredicate(format: "%K.@count == 0", GameCollection.Attributes.games.rawValue), inManagedObjectContext: self.validationManagedObjectContext, type: GameCollection.self) - - for collection in collections - { - self.validationManagedObjectContext.delete(collection) - } - } - - // MARK: - Notifications - - - @objc func managedObjectContextWillSave(_ notification: Notification) - { - guard - let managedObjectContext = notification.object as? NSManagedObjectContext, - managedObjectContext.parent == self.validationManagedObjectContext - else { return } - - self.validationManagedObjectContext.performAndWait { - self.validateManagedObjectContextSave(managedObjectContext) - } - } - - @objc func managedObjectContextDidSave(_ notification: Notification) - { - guard - let managedObjectContext = notification.object as? NSManagedObjectContext, - managedObjectContext.parent == self.validationManagedObjectContext - else { return } - - self.save() - } - - // MARK: - File Management - - - class func createDirectoryAtURLIfNeeded(_ URL: Foundation.URL) + class func createDirectory(at url: URL) { do { - try FileManager.default.createDirectory(at: URL, withIntermediateDirectories: true, attributes: nil) + try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) } catch { diff --git a/Common/Database/Model/Game.swift b/Common/Database/Model/Game.swift index 1b88e05..77a52e7 100644 --- a/Common/Database/Model/Game.swift +++ b/Common/Database/Model/Game.swift @@ -62,6 +62,39 @@ class Game: NSManagedObject, GameProtocol } } +extension Game +{ + override public func prepareForDeletion() + { + super.prepareForDeletion() + + guard FileManager.default.fileExists(atPath: self.fileURL.path) else { return } + + do + { + try FileManager.default.removeItem(at: self.fileURL) + } + catch + { + print(error) + } + + if let managedObjectContext = self.managedObjectContext + { + for collection in self.gameCollections where collection.games.count == 1 + { + // Once this game is deleted, collection will have 0 games, so we should delete it + managedObjectContext.delete(collection) + } + + if managedObjectContext.hasChanges + { + managedObjectContext.saveWithErrorLogging() + } + } + } +} + extension Game { class func supportedTypeIdentifiers() -> Set diff --git a/Common/Database/Model/SaveState.swift b/Common/Database/Model/SaveState.swift index 965c61f..f7eaab8 100644 --- a/Common/Database/Model/SaveState.swift +++ b/Common/Database/Model/SaveState.swift @@ -70,6 +70,25 @@ class SaveState: NSManagedObject, SaveStateProtocol } } +extension SaveState +{ + override public func prepareForDeletion() + { + super.prepareForDeletion() + + guard FileManager.default.fileExists(atPath: self.fileURL.path) else { return } + + do + { + try FileManager.default.removeItem(at: self.fileURL) + } + catch + { + print(error) + } + } +} + extension SaveState { @NSManaged private var primitiveFilename: String diff --git a/Common/Importing/GamePickerController.swift b/Common/Importing/GamePickerController.swift index 04b8381..865a18d 100644 --- a/Common/Importing/GamePickerController.swift +++ b/Common/Importing/GamePickerController.swift @@ -53,15 +53,14 @@ class GamePickerController: NSObject let importAction = UIAlertAction(title: NSLocalizedString("Import", comment: ""), style: .default) { action in - let documentsDirectoryURL = DatabaseManager.databaseDirectoryURL.deletingLastPathComponent() + let documentsDirectoryURL = DatabaseManager.defaultDirectoryURL().deletingLastPathComponent() do { let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectoryURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) - let managedObjectContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() - managedObjectContext.perform() { - let gameURLs = contents.filter({ GameCollection.gameSystemCollectionForPathExtension($0.pathExtension, inManagedObjectContext: managedObjectContext).identifier != GameType.delta.rawValue }) + DatabaseManager.shared.performBackgroundTask { (context) in + let gameURLs = contents.filter({ GameCollection.gameSystemCollectionForPathExtension($0.pathExtension, inManagedObjectContext: context).identifier != GameType.delta.rawValue }) self.importGamesAtURLs(gameURLs) } @@ -87,12 +86,12 @@ class GamePickerController: NSObject private func importGamesAtURLs(_ URLs: [URL]) { - DatabaseManager.sharedManager.importGamesAtURLs(URLs) { identifiers in + DatabaseManager.shared.importGames(at: URLs) { identifiers in - DatabaseManager.sharedManager.managedObjectContext.perform() { + DatabaseManager.shared.viewContext.perform() { let predicate = NSPredicate(format: "%K IN (%@)", Game.Attributes.identifier.rawValue, identifiers) - let games = Game.instancesWithPredicate(predicate, inManagedObjectContext: DatabaseManager.sharedManager.managedObjectContext, type: Game.self) + let games = Game.instancesWithPredicate(predicate, inManagedObjectContext: DatabaseManager.shared.viewContext, type: Game.self) self.delegate?.gamePickerController(self, didImportGames: games) diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index df6e0bd..284c004 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -35,7 +35,6 @@ BF65E8631CEE5C6A00CD3247 /* Cheat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF65E8621CEE5C6A00CD3247 /* Cheat.swift */; }; BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; }; BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - BF762E9E1BC19D31002C8866 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF762E9D1BC19D31002C8866 /* DatabaseManager.swift */; }; BF762EAB1BC1B076002C8866 /* NSManagedObject+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF762EAA1BC1B076002C8866 /* NSManagedObject+Conveniences.swift */; }; BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; }; BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */; }; @@ -51,6 +50,7 @@ BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.swift */; }; BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */; }; BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */; }; + BFBAA86A1D5A483900A29C1B /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAA8691D5A483900A29C1B /* DatabaseManager.swift */; }; BFC2731A1BE6152200D22B05 /* GameCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC273171BE6152200D22B05 /* GameCollection.swift */; }; BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; }; BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */; }; @@ -126,7 +126,6 @@ BF65E8621CEE5C6A00CD3247 /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cheat.swift; sourceTree = ""; }; BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BF762E9D1BC19D31002C8866 /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; }; BF762EAA1BC1B076002C8866 /* NSManagedObject+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Conveniences.swift"; sourceTree = ""; }; BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = ""; }; BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseMenuViewController.swift; path = "Pause Menu/PauseMenuViewController.swift"; sourceTree = ""; }; @@ -138,6 +137,7 @@ BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllersSettingsViewController.swift; sourceTree = ""; }; BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewDataSource.swift; path = "Collection View/GameCollectionViewDataSource.swift"; sourceTree = ""; }; + BFBAA8691D5A483900A29C1B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; }; BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SNESDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BFC273171BE6152200D22B05 /* GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollection.swift; sourceTree = ""; }; BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.swift"; sourceTree = ""; }; @@ -229,7 +229,7 @@ BF4566E41BC0902E007BFA1A /* Database */ = { isa = PBXGroup; children = ( - BF762E9D1BC19D31002C8866 /* DatabaseManager.swift */, + BFBAA8691D5A483900A29C1B /* DatabaseManager.swift */, BF4566E51BC09033007BFA1A /* Model */, ); path = Database; @@ -583,6 +583,7 @@ BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */, BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */, BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */, + BFBAA86A1D5A483900A29C1B /* DatabaseManager.swift in Sources */, BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */, BF7AE81E1C2E984300B1B5BC /* GridCollectionViewCell.swift in Sources */, BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */, @@ -600,7 +601,6 @@ BF02BD001D361BD1000892F2 /* NSFetchedResultsController+Conveniences.m in Sources */, BFFC464C1D5998D600AF2CC6 /* CheatTableViewCell.swift in Sources */, BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */, - BF762E9E1BC19D31002C8866 /* DatabaseManager.swift in Sources */, BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */, BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */, BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */, diff --git a/Delta/AppDelegate.swift b/Delta/AppDelegate.swift index b508382..883c6dc 100644 --- a/Delta/AppDelegate.swift +++ b/Delta/AppDelegate.swift @@ -26,14 +26,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate // Database - let semaphore = DispatchSemaphore(value: 0) - - DatabaseManager.sharedManager.startWithCompletion { performingMigration in - semaphore.signal() + DatabaseManager.shared.loadPersistentStores { (description, error) in } - semaphore.wait() - // Controllers ExternalControllerManager.shared.startMonitoringExternalControllers() diff --git a/Delta/Emulation/GameViewController.swift b/Delta/Emulation/GameViewController.swift index 6f68305..c146bb5 100644 --- a/Delta/Emulation/GameViewController.swift +++ b/Delta/Emulation/GameViewController.swift @@ -453,7 +453,7 @@ extension GameViewController: CheatsViewControllerDelegate self.pauseEmulation() } - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + let backgroundContext = DatabaseManager.shared.newBackgroundContext() backgroundContext.performAndWait { let predicate = NSPredicate(format: "%K == %@", Cheat.Attributes.game.rawValue, game) diff --git a/Delta/Game Selection/GamesViewController.swift b/Delta/Game Selection/GamesViewController.swift index 81ecc86..0f22a4d 100644 --- a/Delta/Game Selection/GamesViewController.swift +++ b/Delta/Game Selection/GamesViewController.swift @@ -45,7 +45,7 @@ class GamesViewController: UIViewController let fetchRequest = GameCollection.rst_fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(key: GameCollection.Attributes.index.rawValue, ascending: true)] - self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) + self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: nil, cacheName: nil) super.init(coder: aDecoder) diff --git a/Delta/Pause Menu/Cheats/CheatsViewController.swift b/Delta/Pause Menu/Cheats/CheatsViewController.swift index 9fb1704..b9f77e1 100644 --- a/Delta/Pause Menu/Cheats/CheatsViewController.swift +++ b/Delta/Pause Menu/Cheats/CheatsViewController.swift @@ -97,7 +97,7 @@ private extension CheatsViewController fetchRequest.predicate = NSPredicate(format: "%K == %@", Cheat.Attributes.game.rawValue, self.game) fetchRequest.sortDescriptors = [NSSortDescriptor(key: Cheat.Attributes.name.rawValue, ascending: true)] - self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) + self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: nil, cacheName: nil) self.fetchedResultsController.delegate = self } @@ -130,11 +130,10 @@ private extension CheatsViewController { self.delegate?.cheatsViewController(self, deactivateCheat: cheat) - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() - backgroundContext.perform { - let temporaryCheat = backgroundContext.object(with: cheat.objectID) - backgroundContext.delete(temporaryCheat) - backgroundContext.saveWithErrorLogging() + DatabaseManager.shared.performBackgroundTask { (context) in + let temporaryCheat = context.object(with: cheat.objectID) + context.delete(temporaryCheat) + context.saveWithErrorLogging() } } } @@ -193,7 +192,7 @@ extension CheatsViewController { let cheat = self.fetchedResultsController.object(at: indexPath) as! Cheat - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + let backgroundContext = DatabaseManager.shared.newBackgroundContext() backgroundContext.performAndWait { let temporaryCheat = backgroundContext.object(with: cheat.objectID) as! Cheat temporaryCheat.enabled = !temporaryCheat.enabled diff --git a/Delta/Pause Menu/Cheats/EditCheatViewController.swift b/Delta/Pause Menu/Cheats/EditCheatViewController.swift index 1386537..be85568 100644 --- a/Delta/Pause Menu/Cheats/EditCheatViewController.swift +++ b/Delta/Pause Menu/Cheats/EditCheatViewController.swift @@ -49,7 +49,7 @@ class EditCheatViewController: UITableViewController } private var mutableCheat: Cheat! - private var managedObjectContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + private var managedObjectContext = DatabaseManager.shared.newBackgroundContext() @IBOutlet private var nameTextField: UITextField! @IBOutlet private var typeSegmentedControl: UISegmentedControl! @@ -76,11 +76,10 @@ class EditCheatViewController: UITableViewController let deleteAction = UIPreviewAction(title: NSLocalizedString("Delete", comment: ""), style: .destructive) { [unowned self] (action, viewController) in self.delegate?.editCheatViewController(self, deactivateCheat: cheat) - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() - backgroundContext.perform { - let temporaryCheat = backgroundContext.object(with: cheat.objectID) - backgroundContext.delete(temporaryCheat) - backgroundContext.saveWithErrorLogging() + DatabaseManager.shared.performBackgroundTask { (context) in + let temporaryCheat = context.object(with: cheat.objectID) + context.delete(temporaryCheat) + context.saveWithErrorLogging() } } diff --git a/Delta/Pause Menu/Save States/SaveStatesViewController.swift b/Delta/Pause Menu/Save States/SaveStatesViewController.swift index dda0fed..45a4c33 100644 --- a/Delta/Pause Menu/Save States/SaveStatesViewController.swift +++ b/Delta/Pause Menu/Save States/SaveStatesViewController.swift @@ -168,7 +168,7 @@ private extension SaveStatesViewController fetchRequest.predicate = NSPredicate(format: "%K == %@", SaveState.Attributes.game.rawValue, self.game) fetchRequest.sortDescriptors = [NSSortDescriptor(key: SaveState.Attributes.type.rawValue, ascending: true), NSSortDescriptor(key: SaveState.Attributes.creationDate.rawValue, ascending: true)] - self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: SaveState.Attributes.type.rawValue, cacheName: nil) + self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: SaveState.Attributes.type.rawValue, cacheName: nil) self.fetchedResultsController.delegate = self } @@ -275,7 +275,7 @@ private extension SaveStatesViewController { var saveState: SaveState! - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + let backgroundContext = DatabaseManager.shared.newBackgroundContext() backgroundContext.performAndWait { let game = backgroundContext.object(with: self.game.objectID) as! Game @@ -324,14 +324,12 @@ private extension SaveStatesViewController let confirmationAlertController = UIAlertController(title: NSLocalizedString("Delete Save State?", comment: ""), message: NSLocalizedString("Are you sure you want to delete this save state? This cannot be undone.", comment: ""), preferredStyle: .alert) confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Delete", comment: ""), style: .default, handler: { action in - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() - backgroundContext.perform { - let temporarySaveState = backgroundContext.object(with: saveState.objectID) - backgroundContext.delete(temporarySaveState) - backgroundContext.saveWithErrorLogging() + DatabaseManager.shared.performBackgroundTask { (context) in + let temporarySaveState = context.object(with: saveState.objectID) + context.delete(temporarySaveState) + context.saveWithErrorLogging() } - })) confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)) @@ -370,13 +368,11 @@ private extension SaveStatesViewController text = nil } - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() - backgroundContext.perform { - - let saveState = backgroundContext.object(with: selectedSaveState.objectID) as! SaveState + DatabaseManager.shared.performBackgroundTask { (context) in + let saveState = context.object(with: selectedSaveState.objectID) as! SaveState saveState.name = text - backgroundContext.saveWithErrorLogging() + context.saveWithErrorLogging() } self.selectedSaveState = nil @@ -388,14 +384,12 @@ private extension SaveStatesViewController alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)) alertController.addAction(UIAlertAction(title: NSLocalizedString("Change", comment: ""), style: .default, handler: { (action) in - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() - backgroundContext.perform { - - let game = backgroundContext.object(with: self.game.objectID) as! Game + DatabaseManager.shared.performBackgroundTask { (context) in + let game = context.object(with: self.game.objectID) as! Game if let saveState = saveState { - let previewSaveState = backgroundContext.object(with: saveState.objectID) as! SaveState + let previewSaveState = context.object(with: saveState.objectID) as! SaveState game.previewSaveState = previewSaveState } else @@ -403,9 +397,8 @@ private extension SaveStatesViewController game.previewSaveState = nil } - backgroundContext.saveWithErrorLogging() + context.saveWithErrorLogging() } - })) self.present(alertController, animated: true, completion: nil) @@ -413,7 +406,7 @@ private extension SaveStatesViewController func lockSaveState(_ saveState: SaveState) { - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + let backgroundContext = DatabaseManager.shared.newBackgroundContext() backgroundContext.performAndWait() { let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState temporarySaveState.type = .locked @@ -423,7 +416,7 @@ private extension SaveStatesViewController func unlockSaveState(_ saveState: SaveState) { - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + let backgroundContext = DatabaseManager.shared.newBackgroundContext() backgroundContext.performAndWait() { let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState temporarySaveState.type = .general @@ -692,7 +685,7 @@ extension SaveStatesViewController { case .auto: break case .general: - let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + let backgroundContext = DatabaseManager.shared.newBackgroundContext() backgroundContext.performAndWait() { let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState self.updateSaveState(temporarySaveState)