Writes text file of possibly corrupted save files to Files apps instead of actually conflicting them

In practice, the number of conflicts was far too high for the number of save files that actually matter.
This commit is contained in:
Riley Testut 2023-08-11 21:51:23 -05:00
parent 43fedf6fcc
commit 452e3dab06

View File

@ -162,29 +162,25 @@ private extension RepairDatabaseViewController
let savesBackupsDirectory = self.backupsDirectory.appendingPathComponent("Saves") let savesBackupsDirectory = self.backupsDirectory.appendingPathComponent("Saves")
try FileManager.default.createDirectory(at: savesBackupsDirectory, withIntermediateDirectories: true) try FileManager.default.createDirectory(at: savesBackupsDirectory, withIntermediateDirectories: true)
var conflictedGames = Set<Game>()
for gameSave in gameSaves for gameSave in gameSaves
{ {
self.repair(gameSave, backupsDirectory: savesBackupsDirectory) let expectedGame = self.repair(gameSave, backupsDirectory: savesBackupsDirectory)
}
if let coordinator = SyncManager.shared.coordinator // At this point, gameSave is only updated in gameSavesContext,
{ // so gameSave here still points to previous game,
let records = try coordinator.recordController.fetchRecords(for: gameSaves)
if let context = records.first?.recordedObject?.managedObjectContext if let game = gameSave.game
{ {
try context.performAndWait { Logger.database.notice("The save file for “\(game.name, privacy: .public)” is potentially corrupted, writing to conflicts.txt")
for record in records conflictedGames.insert(game)
{ }
record.perform { managedRecord in
// Mark ALL affected GameSaves as conflicted.
Logger.database.notice("Marking record \(managedRecord.recordID, privacy: .public) as conflicted.")
managedRecord.isConflicted = true
}
}
try context.save() if let expectedGame
} {
Logger.database.notice("The save file for “\(expectedGame.name, privacy: .public)” is potentially corrupted, writing to conflicts.txt")
conflictedGames.insert(expectedGame)
} }
} }
@ -194,6 +190,11 @@ private extension RepairDatabaseViewController
try self.managedObjectContext.save() try self.managedObjectContext.save()
let outputURL = self.backupsDirectory.appendingPathComponent("conflicts.txt")
let conflictsLog = conflictedGames.map { $0.name + " (" + $0.identifier + ")" }.sorted().joined(separator: "\n")
try conflictsLog.write(to: outputURL, atomically: true, encoding: .utf8)
completion(.success) completion(.success)
} }
catch catch
@ -203,7 +204,8 @@ private extension RepairDatabaseViewController
} }
} }
func repair(_ gameSave: GameSave, backupsDirectory: URL) // Returns expectedGame, but in managedObjectContext (not gameSavesContext)
func repair(_ gameSave: GameSave, backupsDirectory: URL) -> Game?
{ {
Logger.database.notice("Repairing GameSave \(gameSave.identifier, privacy: .public)...") Logger.database.notice("Repairing GameSave \(gameSave.identifier, privacy: .public)...")
@ -226,7 +228,7 @@ private extension RepairDatabaseViewController
gameSave.game = nil gameSave.game = nil
} }
return return nil
} }
let misplacedGameSave: GameSave? let misplacedGameSave: GameSave?
@ -273,25 +275,28 @@ private extension RepairDatabaseViewController
// GameSave data differs from actual .sav file, // GameSave data differs from actual .sav file,
// so copy metadata from misplacedGameSave. // so copy metadata from misplacedGameSave.
Logger.database.info("GameSave \(gameSave.identifier, privacy: .public)'s hash does NOT match .sav, updating GameSave to match misplaced save \(misplacedGameSave.identifier, privacy: .public).") Logger.database.info("GameSave \(gameSave.identifier, privacy: .public)'s hash does NOT match .sav, ignoring misplaced GameSave \(misplacedGameSave.identifier, privacy: .public).")
gameSave.sha1 = misplacedGameSave.sha1 // Not worth potential conflicts.
gameSave.modifiedDate = misplacedGameSave.modifiedDate // gameSave.sha1 = misplacedGameSave.sha1
// gameSave.modifiedDate = misplacedGameSave.modifiedDate
} }
else else
{ {
// GameSave data differs from actual .sav file, // GameSave data differs from actual .sav file,
// so copy metadata from disk. // so copy metadata from disk.
Logger.database.info("GameSave \(gameSave.identifier, privacy: .public)'s hash does NOT match .sav, updating GameSave from disk.") Logger.database.info("GameSave \(gameSave.identifier, privacy: .public)'s hash does NOT match .sav, ignoring.")
let modifiedDate = try? FileManager.default.attributesOfItem(atPath: expectedGame.gameSaveURL.path)[.modificationDate] as? Date // Not worth potential conflicts.
// let modifiedDate = try? FileManager.default.attributesOfItem(atPath: expectedGame.gameSaveURL.path)[.modificationDate] as? Date
gameSave.sha1 = hash // gameSave.sha1 = hash
gameSave.modifiedDate = modifiedDate ?? Date() // gameSave.modifiedDate = modifiedDate ?? Date()
} }
gameSave.game = expectedGame gameSave.game = expectedGame
} }
return expectedGame
} }
func backup(_ gameSave: GameSave, for expectedGame: Game?, to backupsDirectory: URL) throws func backup(_ gameSave: GameSave, for expectedGame: Game?, to backupsDirectory: URL) throws