Refactors DatabaseManager into NSPersistentStore subclass
This commit is contained in:
parent
0d562da309
commit
da7705aaff
@ -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()
|
||||
|
||||
@ -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<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)
|
||||
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
|
||||
{
|
||||
|
||||
@ -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<String>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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 = "<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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -138,6 +137,7 @@
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
@ -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 */,
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user