From 5c6c66c9a1a6de75c7b6c43ac319baced63238f8 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 26 Dec 2016 15:56:33 -0600 Subject: [PATCH] Fixes crashes when loading Game or SaveState that had been overwritten by a duplicate entry When Core Data detects a duplicate entry, it deletes the previous entry. This caused Delta to delete the associated ROM and Save State files. Now, there is a check in prepareForDeletion to make sure the object being deleted is not simply being replaced by a newer entry during a merge. --- Delta/Database/Model/Human/Game.swift | 39 ++++++++++++---------- Delta/Database/Model/Human/SaveState.swift | 6 ++++ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Delta/Database/Model/Human/Game.swift b/Delta/Database/Model/Human/Game.swift index d65975d..7234bb6 100644 --- a/Delta/Database/Model/Human/Game.swift +++ b/Delta/Database/Model/Human/Game.swift @@ -30,6 +30,12 @@ extension Game { super.prepareForDeletion() + guard let managedObjectContext = self.managedObjectContext else { return } + + // If a game with the same identifier is also currently being inserted, Core Data is more than likely resolving a conflict by deleting the previous instance + // In this case, we make sure we DON'T delete the game file + misc other Core Data relationships, or else we'll just lose all that data + guard !managedObjectContext.insertedObjects.contains(where: { ($0 as? Game)?.identifier == self.identifier }) else { return } + guard FileManager.default.fileExists(atPath: self.fileURL.path) else { return } do @@ -41,25 +47,22 @@ extension Game print(error) } - if let managedObjectContext = self.managedObjectContext + for collection in self.gameCollections where collection.games.count == 1 { - 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) - } - - // Manually cascade deletion since SaveState.fileURL references Game, and so we need to ensure we delete SaveState's before Game - // Otherwise, we crash when accessing SaveState.game since it is nil - for saveState in self.saveStates - { - managedObjectContext.delete(saveState) - } - - if managedObjectContext.hasChanges - { - managedObjectContext.saveWithErrorLogging() - } + // Once this game is deleted, collection will have 0 games, so we should delete it + managedObjectContext.delete(collection) + } + + // Manually cascade deletion since SaveState.fileURL references Game, and so we need to ensure we delete SaveState's before Game + // Otherwise, we crash when accessing SaveState.game since it is nil + for saveState in self.saveStates + { + managedObjectContext.delete(saveState) + } + + if managedObjectContext.hasChanges + { + managedObjectContext.saveWithErrorLogging() } } } diff --git a/Delta/Database/Model/Human/SaveState.swift b/Delta/Database/Model/Human/SaveState.swift index 26b27d9..acdfd55 100644 --- a/Delta/Database/Model/Human/SaveState.swift +++ b/Delta/Database/Model/Human/SaveState.swift @@ -60,6 +60,12 @@ public class SaveState: _SaveState, SaveStateProtocol // In rare cases, game may actually be nil if game is corrupted, so we ensure it is non-nil first guard self.game != nil else { return } + guard let managedObjectContext = self.managedObjectContext else { return } + + // If a save state with the same identifier is also currently being inserted, Core Data is more than likely resolving a conflict by deleting the previous instance + // In this case, we make sure we DON'T delete the save state file + misc other Core Data relationships, or else we'll just lose all that data + guard !managedObjectContext.insertedObjects.contains(where: { ($0 as? SaveState)?.identifier == self.identifier }) else { return } + guard FileManager.default.fileExists(atPath: self.fileURL.path) else { return } do