Refactors DatabaseManager into NSPersistentStore subclass

This commit is contained in:
Riley Testut 2016-08-09 19:48:21 -05:00
parent 0d562da309
commit da7705aaff
12 changed files with 133 additions and 288 deletions

View File

@ -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)] 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.fetchedResultsController.delegate = previousDelegate
self.update() self.update()

View File

@ -6,114 +6,54 @@
// Copyright © 2015 Riley Testut. All rights reserved. // Copyright © 2015 Riley Testut. All rights reserved.
// //
import Foundation
import CoreData import CoreData
// Workspace // Workspace
import Roxas
import DeltaCore import DeltaCore
// Pods // Pods
import FileMD5Hash import FileMD5Hash
class DatabaseManager final class DatabaseManager: NSPersistentContainer
{ {
static let sharedManager = DatabaseManager() static let shared = DatabaseManager()
let managedObjectContext: NSManagedObjectContext
private let privateManagedObjectContext: NSManagedObjectContext
private let validationManagedObjectContext: NSManagedObjectContext
// MARK: - Initialization -
/// Initialization
private init() private init()
{ {
let modelURL = Bundle.main.url(forResource: "Model", withExtension: "momd") guard
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL!) let modelURL = Bundle(for: DatabaseManager.self).url(forResource: "Model", withExtension: "momd"),
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!) let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
else { fatalError("Core Data model cannot be found. Aborting.") }
self.privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) super.init(name: "Delta", managedObjectModel: managedObjectModel)
self.privateManagedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
self.managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) self.viewContext.automaticallyMergesChangesFromParent = true
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)
} }
}
func startWithCompletion(_ completionBlock: ((performingMigration: Bool) -> Void)?)
{
DispatchQueue.global(qos: .userInitiated).async {
let storeURL = DatabaseManager.databaseDirectoryURL.appendingPathComponent("Delta.sqlite")
let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true] //MARK: - Importing -
/// Importing
var performingMigration = false extension DatabaseManager
{
if func importGames(at urls: [URL], completion: (([String]) -> Void)?)
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)?)
{ {
let managedObjectContext = self.backgroundManagedObjectContext() self.performBackgroundTask { (context) in
managedObjectContext.perform() {
var identifiers: [String] = [] 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) let game = Game.insertIntoManagedObjectContext(context)
game.name = URL.deletingPathExtension().lastPathComponent ?? NSLocalizedString("Game", comment: "") game.name = url.deletingPathExtension().lastPathComponent ?? NSLocalizedString("Game", comment: "")
game.identifier = identifier game.identifier = identifier
game.filename = filename 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.type = GameType(rawValue: gameCollection.identifier)
game.gameCollections.insert(gameCollection) game.gameCollections.insert(gameCollection)
@ -123,11 +63,11 @@ class DatabaseManager
if FileManager.default.fileExists(atPath: destinationURL.path) if FileManager.default.fileExists(atPath: destinationURL.path)
{ {
try FileManager.default.removeItem(at: URL) try FileManager.default.removeItem(at: url)
} }
else else
{ {
try FileManager.default.moveItem(at: URL, to: destinationURL) try FileManager.default.moveItem(at: url, to: destinationURL)
} }
identifiers.append(game.identifier) identifiers.append(game.identifier)
@ -141,9 +81,9 @@ class DatabaseManager
do do
{ {
try managedObjectContext.save() try context.save()
} }
catch let error as NSError catch
{ {
print("Failed to save import context:", error) print("Failed to save import context:", error)
@ -154,27 +94,16 @@ class DatabaseManager
{ {
completion(identifiers) 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 extension DatabaseManager
{ {
class var databaseDirectoryURL: URL override class func defaultDirectoryURL() -> URL
{ {
let documentsDirectoryURL: URL let documentsDirectoryURL: URL
@ -188,23 +117,23 @@ extension DatabaseManager
} }
let databaseDirectoryURL = documentsDirectoryURL.appendingPathComponent("Database") let databaseDirectoryURL = documentsDirectoryURL.appendingPathComponent("Database")
self.createDirectoryAtURLIfNeeded(databaseDirectoryURL) self.createDirectory(at: databaseDirectoryURL)
return databaseDirectoryURL return databaseDirectoryURL
} }
class var gamesDirectoryURL: URL class var gamesDirectoryURL: URL
{ {
let gamesDirectoryURL = DatabaseManager.databaseDirectoryURL.appendingPathComponent("Games") let gamesDirectoryURL = DatabaseManager.defaultDirectoryURL().appendingPathComponent("Games")
self.createDirectoryAtURLIfNeeded(gamesDirectoryURL) self.createDirectory(at: gamesDirectoryURL)
return gamesDirectoryURL return gamesDirectoryURL
} }
class var saveStatesDirectoryURL: URL class var saveStatesDirectoryURL: URL
{ {
let saveStatesDirectoryURL = DatabaseManager.databaseDirectoryURL.appendingPathComponent("Save States") let saveStatesDirectoryURL = DatabaseManager.defaultDirectoryURL().appendingPathComponent("Save States")
self.createDirectoryAtURLIfNeeded(saveStatesDirectoryURL) self.createDirectory(at: saveStatesDirectoryURL)
return saveStatesDirectoryURL return saveStatesDirectoryURL
} }
@ -212,141 +141,20 @@ extension DatabaseManager
class func saveStatesDirectoryURLForGame(_ game: Game) -> URL class func saveStatesDirectoryURLForGame(_ game: Game) -> URL
{ {
let gameDirectoryURL = DatabaseManager.saveStatesDirectoryURL.appendingPathComponent(game.identifier) let gameDirectoryURL = DatabaseManager.saveStatesDirectoryURL.appendingPathComponent(game.identifier)
self.createDirectoryAtURLIfNeeded(gameDirectoryURL) self.createDirectory(at: gameDirectoryURL)
return gameDirectoryURL return gameDirectoryURL
} }
} }
//MARK: - Private -
private extension DatabaseManager private extension DatabaseManager
{ {
// MARK: - Saving - class func createDirectory(at url: URL)
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<URL>()
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)
{ {
do do
{ {
try FileManager.default.createDirectory(at: URL, withIntermediateDirectories: true, attributes: nil) try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
} }
catch catch
{ {

View File

@ -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 extension Game
{ {
class func supportedTypeIdentifiers() -> Set<String> class func supportedTypeIdentifiers() -> Set<String>

View File

@ -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 extension SaveState
{ {
@NSManaged private var primitiveFilename: String @NSManaged private var primitiveFilename: String

View File

@ -53,15 +53,14 @@ class GamePickerController: NSObject
let importAction = UIAlertAction(title: NSLocalizedString("Import", comment: ""), style: .default) { action in let importAction = UIAlertAction(title: NSLocalizedString("Import", comment: ""), style: .default) { action in
let documentsDirectoryURL = DatabaseManager.databaseDirectoryURL.deletingLastPathComponent() let documentsDirectoryURL = DatabaseManager.defaultDirectoryURL().deletingLastPathComponent()
do do
{ {
let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectoryURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectoryURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
let managedObjectContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() DatabaseManager.shared.performBackgroundTask { (context) in
managedObjectContext.perform() { let gameURLs = contents.filter({ GameCollection.gameSystemCollectionForPathExtension($0.pathExtension, inManagedObjectContext: context).identifier != GameType.delta.rawValue })
let gameURLs = contents.filter({ GameCollection.gameSystemCollectionForPathExtension($0.pathExtension, inManagedObjectContext: managedObjectContext).identifier != GameType.delta.rawValue })
self.importGamesAtURLs(gameURLs) self.importGamesAtURLs(gameURLs)
} }
@ -87,12 +86,12 @@ class GamePickerController: NSObject
private func importGamesAtURLs(_ URLs: [URL]) 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 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) self.delegate?.gamePickerController(self, didImportGames: games)

View File

@ -35,7 +35,6 @@
BF65E8631CEE5C6A00CD3247 /* Cheat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF65E8621CEE5C6A00CD3247 /* Cheat.swift */; }; BF65E8631CEE5C6A00CD3247 /* Cheat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF65E8621CEE5C6A00CD3247 /* Cheat.swift */; };
BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; }; 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, ); }; }; 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 */; }; 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 */; }; BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; };
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.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 */; }; BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.swift */; };
BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */; }; BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */; };
BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.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 */; }; BFC2731A1BE6152200D22B05 /* GameCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC273171BE6152200D22B05 /* GameCollection.swift */; };
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.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 */; }; 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 = "<group>"; }; BF65E8621CEE5C6A00CD3247 /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cheat.swift; sourceTree = "<group>"; };
BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; };
BF762EAA1BC1B076002C8866 /* NSManagedObject+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Conveniences.swift"; sourceTree = "<group>"; }; BF762EAA1BC1B076002C8866 /* NSManagedObject+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Conveniences.swift"; sourceTree = "<group>"; };
BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; }; BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; };
BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseMenuViewController.swift; path = "Pause Menu/PauseMenuViewController.swift"; sourceTree = "<group>"; }; BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseMenuViewController.swift; path = "Pause Menu/PauseMenuViewController.swift"; sourceTree = "<group>"; };
@ -138,6 +137,7 @@
BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; }; BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllersSettingsViewController.swift; sourceTree = "<group>"; }; BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllersSettingsViewController.swift; sourceTree = "<group>"; };
BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewDataSource.swift; path = "Collection View/GameCollectionViewDataSource.swift"; sourceTree = "<group>"; }; BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewDataSource.swift; path = "Collection View/GameCollectionViewDataSource.swift"; sourceTree = "<group>"; };
BFBAA8691D5A483900A29C1B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SNESDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; BFC273171BE6152200D22B05 /* GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollection.swift; sourceTree = "<group>"; };
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.swift"; sourceTree = "<group>"; }; BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.swift"; sourceTree = "<group>"; };
@ -229,7 +229,7 @@
BF4566E41BC0902E007BFA1A /* Database */ = { BF4566E41BC0902E007BFA1A /* Database */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BF762E9D1BC19D31002C8866 /* DatabaseManager.swift */, BFBAA8691D5A483900A29C1B /* DatabaseManager.swift */,
BF4566E51BC09033007BFA1A /* Model */, BF4566E51BC09033007BFA1A /* Model */,
); );
path = Database; path = Database;
@ -583,6 +583,7 @@
BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */, BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */,
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */, BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */,
BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */, BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */,
BFBAA86A1D5A483900A29C1B /* DatabaseManager.swift in Sources */,
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */, BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
BF7AE81E1C2E984300B1B5BC /* GridCollectionViewCell.swift in Sources */, BF7AE81E1C2E984300B1B5BC /* GridCollectionViewCell.swift in Sources */,
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */, BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
@ -600,7 +601,6 @@
BF02BD001D361BD1000892F2 /* NSFetchedResultsController+Conveniences.m in Sources */, BF02BD001D361BD1000892F2 /* NSFetchedResultsController+Conveniences.m in Sources */,
BFFC464C1D5998D600AF2CC6 /* CheatTableViewCell.swift in Sources */, BFFC464C1D5998D600AF2CC6 /* CheatTableViewCell.swift in Sources */,
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */, BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
BF762E9E1BC19D31002C8866 /* DatabaseManager.swift in Sources */,
BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */, BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */,
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */, BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */, BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,

View File

@ -26,14 +26,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate
// Database // Database
let semaphore = DispatchSemaphore(value: 0) DatabaseManager.shared.loadPersistentStores { (description, error) in
DatabaseManager.sharedManager.startWithCompletion { performingMigration in
semaphore.signal()
} }
semaphore.wait()
// Controllers // Controllers
ExternalControllerManager.shared.startMonitoringExternalControllers() ExternalControllerManager.shared.startMonitoringExternalControllers()

View File

@ -453,7 +453,7 @@ extension GameViewController: CheatsViewControllerDelegate
self.pauseEmulation() self.pauseEmulation()
} }
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait { backgroundContext.performAndWait {
let predicate = NSPredicate(format: "%K == %@", Cheat.Attributes.game.rawValue, game) let predicate = NSPredicate(format: "%K == %@", Cheat.Attributes.game.rawValue, game)

View File

@ -45,7 +45,7 @@ class GamesViewController: UIViewController
let fetchRequest = GameCollection.rst_fetchRequest() let fetchRequest = GameCollection.rst_fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: GameCollection.Attributes.index.rawValue, ascending: true)] 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) super.init(coder: aDecoder)

View File

@ -97,7 +97,7 @@ private extension CheatsViewController
fetchRequest.predicate = NSPredicate(format: "%K == %@", Cheat.Attributes.game.rawValue, self.game) fetchRequest.predicate = NSPredicate(format: "%K == %@", Cheat.Attributes.game.rawValue, self.game)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: Cheat.Attributes.name.rawValue, ascending: true)] 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 self.fetchedResultsController.delegate = self
} }
@ -130,11 +130,10 @@ private extension CheatsViewController
{ {
self.delegate?.cheatsViewController(self, deactivateCheat: cheat) self.delegate?.cheatsViewController(self, deactivateCheat: cheat)
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() DatabaseManager.shared.performBackgroundTask { (context) in
backgroundContext.perform { let temporaryCheat = context.object(with: cheat.objectID)
let temporaryCheat = backgroundContext.object(with: cheat.objectID) context.delete(temporaryCheat)
backgroundContext.delete(temporaryCheat) context.saveWithErrorLogging()
backgroundContext.saveWithErrorLogging()
} }
} }
} }
@ -193,7 +192,7 @@ extension CheatsViewController
{ {
let cheat = self.fetchedResultsController.object(at: indexPath) as! Cheat let cheat = self.fetchedResultsController.object(at: indexPath) as! Cheat
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait { backgroundContext.performAndWait {
let temporaryCheat = backgroundContext.object(with: cheat.objectID) as! Cheat let temporaryCheat = backgroundContext.object(with: cheat.objectID) as! Cheat
temporaryCheat.enabled = !temporaryCheat.enabled temporaryCheat.enabled = !temporaryCheat.enabled

View File

@ -49,7 +49,7 @@ class EditCheatViewController: UITableViewController
} }
private var mutableCheat: Cheat! private var mutableCheat: Cheat!
private var managedObjectContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() private var managedObjectContext = DatabaseManager.shared.newBackgroundContext()
@IBOutlet private var nameTextField: UITextField! @IBOutlet private var nameTextField: UITextField!
@IBOutlet private var typeSegmentedControl: UISegmentedControl! @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 let deleteAction = UIPreviewAction(title: NSLocalizedString("Delete", comment: ""), style: .destructive) { [unowned self] (action, viewController) in
self.delegate?.editCheatViewController(self, deactivateCheat: cheat) self.delegate?.editCheatViewController(self, deactivateCheat: cheat)
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() DatabaseManager.shared.performBackgroundTask { (context) in
backgroundContext.perform { let temporaryCheat = context.object(with: cheat.objectID)
let temporaryCheat = backgroundContext.object(with: cheat.objectID) context.delete(temporaryCheat)
backgroundContext.delete(temporaryCheat) context.saveWithErrorLogging()
backgroundContext.saveWithErrorLogging()
} }
} }

View File

@ -168,7 +168,7 @@ private extension SaveStatesViewController
fetchRequest.predicate = NSPredicate(format: "%K == %@", SaveState.Attributes.game.rawValue, self.game) 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)] 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 self.fetchedResultsController.delegate = self
} }
@ -275,7 +275,7 @@ private extension SaveStatesViewController
{ {
var saveState: SaveState! var saveState: SaveState!
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait { backgroundContext.performAndWait {
let game = backgroundContext.object(with: self.game.objectID) as! Game 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) 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 confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Delete", comment: ""), style: .default, handler: { action in
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() DatabaseManager.shared.performBackgroundTask { (context) in
backgroundContext.perform { let temporarySaveState = context.object(with: saveState.objectID)
let temporarySaveState = backgroundContext.object(with: saveState.objectID) context.delete(temporarySaveState)
backgroundContext.delete(temporarySaveState) context.saveWithErrorLogging()
backgroundContext.saveWithErrorLogging()
} }
})) }))
confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)) confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
@ -370,13 +368,11 @@ private extension SaveStatesViewController
text = nil text = nil
} }
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() DatabaseManager.shared.performBackgroundTask { (context) in
backgroundContext.perform { let saveState = context.object(with: selectedSaveState.objectID) as! SaveState
let saveState = backgroundContext.object(with: selectedSaveState.objectID) as! SaveState
saveState.name = text saveState.name = text
backgroundContext.saveWithErrorLogging() context.saveWithErrorLogging()
} }
self.selectedSaveState = nil 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("Cancel", comment: ""), style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Change", comment: ""), style: .default, handler: { (action) in alertController.addAction(UIAlertAction(title: NSLocalizedString("Change", comment: ""), style: .default, handler: { (action) in
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() DatabaseManager.shared.performBackgroundTask { (context) in
backgroundContext.perform { let game = context.object(with: self.game.objectID) as! Game
let game = backgroundContext.object(with: self.game.objectID) as! Game
if let saveState = saveState 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 game.previewSaveState = previewSaveState
} }
else else
@ -403,9 +397,8 @@ private extension SaveStatesViewController
game.previewSaveState = nil game.previewSaveState = nil
} }
backgroundContext.saveWithErrorLogging() context.saveWithErrorLogging()
} }
})) }))
self.present(alertController, animated: true, completion: nil) self.present(alertController, animated: true, completion: nil)
@ -413,7 +406,7 @@ private extension SaveStatesViewController
func lockSaveState(_ saveState: SaveState) func lockSaveState(_ saveState: SaveState)
{ {
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait() { backgroundContext.performAndWait() {
let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState
temporarySaveState.type = .locked temporarySaveState.type = .locked
@ -423,7 +416,7 @@ private extension SaveStatesViewController
func unlockSaveState(_ saveState: SaveState) func unlockSaveState(_ saveState: SaveState)
{ {
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait() { backgroundContext.performAndWait() {
let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState
temporarySaveState.type = .general temporarySaveState.type = .general
@ -692,7 +685,7 @@ extension SaveStatesViewController
{ {
case .auto: break case .auto: break
case .general: case .general:
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait() { backgroundContext.performAndWait() {
let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState
self.updateSaveState(temporarySaveState) self.updateSaveState(temporarySaveState)