Throws SyncValidationError when downloading corrupted Game or GameSave record
This commit is contained in:
parent
ca8c2cb8c5
commit
fcdd3c7840
@ -201,6 +201,7 @@
|
|||||||
D5A9C01D29DE058C00A8D610 /* VariableFastForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9C01C29DE058C00A8D610 /* VariableFastForward.swift */; };
|
D5A9C01D29DE058C00A8D610 /* VariableFastForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9C01C29DE058C00A8D610 /* VariableFastForward.swift */; };
|
||||||
D5AAF27729884F8600F21ACF /* CheatDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AAF27629884F8600F21ACF /* CheatDevice.swift */; };
|
D5AAF27729884F8600F21ACF /* CheatDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AAF27629884F8600F21ACF /* CheatDevice.swift */; };
|
||||||
D5ADD12229F33FBF00CE0560 /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADD12129F33FBF00CE0560 /* Features.swift */; };
|
D5ADD12229F33FBF00CE0560 /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ADD12129F33FBF00CE0560 /* Features.swift */; };
|
||||||
|
D5CDCCEE2A859DC200E22131 /* SyncValidationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CDCCEC2A859B2B00E22131 /* SyncValidationError.swift */; };
|
||||||
D5CDCCEF2A859E5300E22131 /* OSLog+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CDCCC32A85765900E22131 /* OSLog+Delta.swift */; };
|
D5CDCCEF2A859E5300E22131 /* OSLog+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CDCCC32A85765900E22131 /* OSLog+Delta.swift */; };
|
||||||
D5CDCCF02A859E5500E22131 /* UserDefaults+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CDCCEA2A8593FC00E22131 /* UserDefaults+Delta.swift */; };
|
D5CDCCF02A859E5500E22131 /* UserDefaults+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CDCCEA2A8593FC00E22131 /* UserDefaults+Delta.swift */; };
|
||||||
D5CDCCF12A859E7500E22131 /* ReviewSaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1375A2A7D8F2600AB1372 /* ReviewSaveStatesViewController.swift */; };
|
D5CDCCF12A859E7500E22131 /* ReviewSaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1375A2A7D8F2600AB1372 /* ReviewSaveStatesViewController.swift */; };
|
||||||
@ -478,6 +479,7 @@
|
|||||||
D5ADD12129F33FBF00CE0560 /* Features.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features.swift; sourceTree = "<group>"; };
|
D5ADD12129F33FBF00CE0560 /* Features.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features.swift; sourceTree = "<group>"; };
|
||||||
D5CDCCC32A85765900E22131 /* OSLog+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSLog+Delta.swift"; sourceTree = "<group>"; };
|
D5CDCCC32A85765900E22131 /* OSLog+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSLog+Delta.swift"; sourceTree = "<group>"; };
|
||||||
D5CDCCEA2A8593FC00E22131 /* UserDefaults+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Delta.swift"; sourceTree = "<group>"; };
|
D5CDCCEA2A8593FC00E22131 /* UserDefaults+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Delta.swift"; sourceTree = "<group>"; };
|
||||||
|
D5CDCCEC2A859B2B00E22131 /* SyncValidationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncValidationError.swift; sourceTree = "<group>"; };
|
||||||
D5D78AE429F9BC3700E064F0 /* DSAirPlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSAirPlay.swift; sourceTree = "<group>"; };
|
D5D78AE429F9BC3700E064F0 /* DSAirPlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSAirPlay.swift; sourceTree = "<group>"; };
|
||||||
D5D78AE629F9D40A00E064F0 /* EnvironmentValues+FeatureOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+FeatureOption.swift"; sourceTree = "<group>"; };
|
D5D78AE629F9D40A00E064F0 /* EnvironmentValues+FeatureOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+FeatureOption.swift"; sourceTree = "<group>"; };
|
||||||
D5D797E5298D946200738869 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = "<group>"; };
|
D5D797E5298D946200738869 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = "<group>"; };
|
||||||
@ -888,6 +890,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BFAB9F7C219A43380080EC7D /* SyncManager.swift */,
|
BFAB9F7C219A43380080EC7D /* SyncManager.swift */,
|
||||||
|
D5CDCCEC2A859B2B00E22131 /* SyncValidationError.swift */,
|
||||||
BF1F45A321AF274D00EF9895 /* SyncResultViewController.swift */,
|
BF1F45A321AF274D00EF9895 /* SyncResultViewController.swift */,
|
||||||
BF1F45AA21AF4B5800EF9895 /* SyncResultsViewController.storyboard */,
|
BF1F45AA21AF4B5800EF9895 /* SyncResultsViewController.storyboard */,
|
||||||
);
|
);
|
||||||
@ -1660,6 +1663,7 @@
|
|||||||
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */,
|
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */,
|
||||||
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */,
|
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */,
|
||||||
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
|
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
|
||||||
|
D5CDCCEE2A859DC200E22131 /* SyncValidationError.swift in Sources */,
|
||||||
BF4828881F90290F00028B97 /* Action.swift in Sources */,
|
BF4828881F90290F00028B97 /* Action.swift in Sources */,
|
||||||
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
|
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
|
||||||
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
|
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
|
||||||
|
|||||||
@ -199,4 +199,14 @@ extension Game: Syncable
|
|||||||
public var syncableLocalizedName: String? {
|
public var syncableLocalizedName: String? {
|
||||||
return self.name
|
return self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func awakeFromSync(_ record: AnyRecord) throws
|
||||||
|
{
|
||||||
|
guard let gameCollection = self.gameCollection else { throw SyncValidationError.incorrectGameCollection(nil) }
|
||||||
|
|
||||||
|
if gameCollection.identifier != self.type.rawValue
|
||||||
|
{
|
||||||
|
throw SyncValidationError.incorrectGameCollection(gameCollection.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,9 @@ extension GameSave: Syncable
|
|||||||
|
|
||||||
public var syncableMetadata: [HarmonyMetadataKey : String] {
|
public var syncableMetadata: [HarmonyMetadataKey : String] {
|
||||||
guard let game = self.game else { return [:] }
|
guard let game = self.game else { return [:] }
|
||||||
return [.gameID: game.identifier, .gameName: game.name]
|
|
||||||
|
// Use self.identifier to always link with exact matching game.
|
||||||
|
return [.gameID: self.identifier, .gameName: game.name]
|
||||||
}
|
}
|
||||||
|
|
||||||
public var syncableLocalizedName: String? {
|
public var syncableLocalizedName: String? {
|
||||||
@ -66,4 +68,30 @@ extension GameSave: Syncable
|
|||||||
|
|
||||||
return self.game?.identifier != Game.melonDSBIOSIdentifier && self.game?.identifier != Game.melonDSDSiBIOSIdentifier
|
return self.game?.identifier != Game.melonDSBIOSIdentifier && self.game?.identifier != Game.melonDSDSiBIOSIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func awakeFromSync(_ record: AnyRecord) throws
|
||||||
|
{
|
||||||
|
guard let game = self.game else { throw SyncValidationError.incorrectGame(nil) }
|
||||||
|
|
||||||
|
if game.identifier != self.identifier
|
||||||
|
{
|
||||||
|
let fetchRequest = GameSave.fetchRequest()
|
||||||
|
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(GameSave.identifier), game.identifier)
|
||||||
|
|
||||||
|
if let misplacedGameSave = try self.managedObjectContext?.fetch(fetchRequest).first, misplacedGameSave.game == nil
|
||||||
|
{
|
||||||
|
// Relink game with its correct gameSave, in case we accidentally misplaced it.
|
||||||
|
// Otherwise, corrupted records might displace already-downloaded GameSaves
|
||||||
|
// due to automatic Core Data relationship propagation, despite us throwing error.
|
||||||
|
game.gameSave = misplacedGameSave
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Either there is no misplacedGameSave, or there is but it's linked to another game somehow.
|
||||||
|
game.gameSave = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
throw SyncValidationError.incorrectGame(game.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,6 +173,15 @@ private extension SyncResultViewController
|
|||||||
errorMessage = NSLocalizedString("The game for this item is missing. Please re-import the game to resume syncing its data.", comment: "")
|
errorMessage = NSLocalizedString("The game for this item is missing. Please re-import the game to resume syncing its data.", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .other(_, let error as SyncValidationError):
|
||||||
|
var message = error.failureReason ?? error.localizedDescription
|
||||||
|
if let recoverySuggestion = error.recoverySuggestion
|
||||||
|
{
|
||||||
|
message += "\n\n" + recoverySuggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMessage = message
|
||||||
|
|
||||||
case .other(_, let error as NSError): errorMessage = error.localizedFailureReason ?? error.localizedDescription
|
case .other(_, let error as NSError): errorMessage = error.localizedFailureReason ?? error.localizedDescription
|
||||||
default: errorMessage = error.failureReason
|
default: errorMessage = error.failureReason
|
||||||
}
|
}
|
||||||
@ -249,9 +258,7 @@ private extension SyncResultViewController
|
|||||||
case .gameControllerInputMapping: group = .gameControllerInputMapping
|
case .gameControllerInputMapping: group = .gameControllerInputMapping
|
||||||
|
|
||||||
case .gameSave:
|
case .gameSave:
|
||||||
guard let gameID = metadata?[.gameID] else { continue }
|
let recordID = RecordID(type: SyncManager.RecordType.game.rawValue, identifier: error.record.recordID.identifier)
|
||||||
|
|
||||||
let recordID = RecordID(type: SyncManager.RecordType.game.rawValue, identifier: gameID)
|
|
||||||
group = .game(recordID)
|
group = .game(recordID)
|
||||||
|
|
||||||
case .saveState:
|
case .saveState:
|
||||||
@ -387,7 +394,11 @@ extension SyncResultViewController
|
|||||||
|
|
||||||
case .game:
|
case .game:
|
||||||
guard let error = section.errors.first as? RecordError else { return nil }
|
guard let error = section.errors.first as? RecordError else { return nil }
|
||||||
return error.record.localizedName
|
|
||||||
|
let recordID = RecordID(type: SyncManager.RecordType.game.rawValue, identifier: error.record.recordID.identifier)
|
||||||
|
|
||||||
|
let gameName = self.gameNamesByRecordID[recordID] // In case the remote record is corrupted, rely on local names.
|
||||||
|
return gameName ?? error.record.localizedName
|
||||||
|
|
||||||
case .saveState(let gameID):
|
case .saveState(let gameID):
|
||||||
guard let error = section.errors.first as? RecordError else { return nil }
|
guard let error = section.errors.first as? RecordError else { return nil }
|
||||||
|
|||||||
40
Delta/Syncing/SyncValidationError.swift
Normal file
40
Delta/Syncing/SyncValidationError.swift
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// SyncValidationError.swift
|
||||||
|
// Delta
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/10/23.
|
||||||
|
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum SyncValidationError: LocalizedError
|
||||||
|
{
|
||||||
|
case incorrectGame(String?)
|
||||||
|
case incorrectGameCollection(String?)
|
||||||
|
|
||||||
|
var failureReason: String? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .incorrectGame(let name?):
|
||||||
|
return String(format: NSLocalizedString("The downloaded record is associated with the wrong game (%@).", comment: ""), name)
|
||||||
|
|
||||||
|
case .incorrectGame(nil):
|
||||||
|
return NSLocalizedString("The downloaded record is not associated with a game.", comment: "")
|
||||||
|
|
||||||
|
case .incorrectGameCollection(let name?):
|
||||||
|
return String(format: NSLocalizedString("The downloaded record is associated with the wrong game system (%@).", comment: ""), name)
|
||||||
|
|
||||||
|
case .incorrectGameCollection(nil):
|
||||||
|
return NSLocalizedString("The downloaded record is not associated with a game system.", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var recoverySuggestion: String? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .incorrectGame: return NSLocalizedString("Try restoring an older version to resolve this issue.", comment: "")
|
||||||
|
case .incorrectGameCollection: return NSLocalizedString("Try restoring an older version, or manually re-import the game to resolve this issue.", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user