From 452e3dab06660909e21973f06720ab58b10968e2 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Fri, 11 Aug 2023 21:51:23 -0500 Subject: [PATCH] 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. --- .../Repair/RepairDatabaseViewController.swift | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/Delta/Database/Repair/RepairDatabaseViewController.swift b/Delta/Database/Repair/RepairDatabaseViewController.swift index 5f2f071..31ee719 100644 --- a/Delta/Database/Repair/RepairDatabaseViewController.swift +++ b/Delta/Database/Repair/RepairDatabaseViewController.swift @@ -162,38 +162,39 @@ private extension RepairDatabaseViewController let savesBackupsDirectory = self.backupsDirectory.appendingPathComponent("Saves") try FileManager.default.createDirectory(at: savesBackupsDirectory, withIntermediateDirectories: true) + var conflictedGames = Set() + for gameSave in gameSaves { - self.repair(gameSave, backupsDirectory: savesBackupsDirectory) - } - - if let coordinator = SyncManager.shared.coordinator - { - let records = try coordinator.recordController.fetchRecords(for: gameSaves) + let expectedGame = self.repair(gameSave, backupsDirectory: savesBackupsDirectory) - if let context = records.first?.recordedObject?.managedObjectContext + // At this point, gameSave is only updated in gameSavesContext, + // so gameSave here still points to previous game, + + if let game = gameSave.game { - try context.performAndWait { - for record in records - { - 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() - } + Logger.database.notice("The save file for “\(game.name, privacy: .public)” is potentially corrupted, writing to conflicts.txt") + conflictedGames.insert(game) + } + + if let expectedGame + { + Logger.database.notice("The save file for “\(expectedGame.name, privacy: .public)” is potentially corrupted, writing to conflicts.txt") + conflictedGames.insert(expectedGame) } } - + try self.gameSavesContext.performAndWait { try self.gameSavesContext.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) } 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)...") @@ -226,7 +228,7 @@ private extension RepairDatabaseViewController gameSave.game = nil } - return + return nil } let misplacedGameSave: GameSave? @@ -273,25 +275,28 @@ private extension RepairDatabaseViewController // GameSave data differs from actual .sav file, // 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 - gameSave.modifiedDate = misplacedGameSave.modifiedDate + // Not worth potential conflicts. + // gameSave.sha1 = misplacedGameSave.sha1 + // gameSave.modifiedDate = misplacedGameSave.modifiedDate } else { // GameSave data differs from actual .sav file, // 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 - - gameSave.sha1 = hash - gameSave.modifiedDate = modifiedDate ?? Date() + // Not worth potential conflicts. + // let modifiedDate = try? FileManager.default.attributesOfItem(atPath: expectedGame.gameSaveURL.path)[.modificationDate] as? Date + // gameSave.sha1 = hash + // gameSave.modifiedDate = modifiedDate ?? Date() } gameSave.game = expectedGame } + + return expectedGame } func backup(_ gameSave: GameSave, for expectedGame: Game?, to backupsDirectory: URL) throws