Throws SyncValidationError when downloading corrupted versions of “verified” SaveStates
After reviewing save states upon first launch, Delta will upload the verified game ID as metadata to ensure other devices don’t download remote versions with incorrect relationships.
This commit is contained in:
parent
dc3a5b479c
commit
d1643dbc8f
@ -23,12 +23,6 @@ public class GameSave: _GameSave
|
||||
}
|
||||
}
|
||||
|
||||
extension GameSave
|
||||
{
|
||||
// Hacky, but YOLO I'm under time crunch.
|
||||
public static var ignoredCorruptedIDs = Set<String>()
|
||||
}
|
||||
|
||||
extension GameSave: Syncable
|
||||
{
|
||||
public static var syncablePrimaryKey: AnyKeyPath {
|
||||
@ -104,7 +98,7 @@ extension GameSave: Syncable
|
||||
}
|
||||
catch let error as SyncValidationError
|
||||
{
|
||||
guard GameSave.ignoredCorruptedIDs.contains(self.identifier) else { throw error }
|
||||
guard SyncManager.shared.ignoredCorruptedRecordIDs.contains(record.recordID) else { throw error }
|
||||
|
||||
let fetchRequest = Game.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(Game.identifier), self.identifier)
|
||||
|
||||
@ -134,7 +134,7 @@ extension SaveState: Syncable
|
||||
|
||||
public var syncableMetadata: [HarmonyMetadataKey : String] {
|
||||
guard let game = self.game else { return [:] }
|
||||
return [.gameID: game.identifier, .gameName: game.name, .coreID: self.coreIdentifier].compactMapValues { $0 }
|
||||
return [.gameID: game.identifier, .gameName: game.name, .coreID: self.coreIdentifier, .verifiedGameID: game.identifier].compactMapValues { $0 }
|
||||
}
|
||||
|
||||
public var syncableLocalizedName: String? {
|
||||
@ -143,21 +143,51 @@ extension SaveState: Syncable
|
||||
|
||||
public func awakeFromSync(_ record: AnyRecord)
|
||||
{
|
||||
guard self.coreIdentifier == nil else { return }
|
||||
guard let game = self.game, let system = System(gameType: game.type) else { return }
|
||||
|
||||
if let coreIdentifier = record.remoteMetadata?[.coreID]
|
||||
let verifiedGameID = record.remoteMetadata?[.verifiedGameID]
|
||||
|
||||
do
|
||||
{
|
||||
// SaveState was synced to older version of Delta and lost its coreIdentifier,
|
||||
// but it remains in the remote metadata so we can reassign it.
|
||||
self.coreIdentifier = coreIdentifier
|
||||
}
|
||||
else
|
||||
{
|
||||
switch system
|
||||
guard let game = self.game else { return }
|
||||
|
||||
if let system = System(gameType: game.type), self.coreIdentifier == nil
|
||||
{
|
||||
case .ds: self.coreIdentifier = DS.core.identifier // Assume DS save state with nil coreIdentifier is from DeSmuME core.
|
||||
default: self.coreIdentifier = system.deltaCore.identifier
|
||||
if let coreIdentifier = record.remoteMetadata?[.coreID]
|
||||
{
|
||||
// SaveState was synced to older version of Delta and lost its coreIdentifier,
|
||||
// but it remains in the remote metadata so we can reassign it.
|
||||
self.coreIdentifier = coreIdentifier
|
||||
}
|
||||
else
|
||||
{
|
||||
switch system
|
||||
{
|
||||
case .ds: self.coreIdentifier = DS.core.identifier // Assume DS save state with nil coreIdentifier is from DeSmuME core.
|
||||
default: self.coreIdentifier = system.deltaCore.identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let verifiedGameID, verifiedGameID != game.identifier
|
||||
{
|
||||
// Game does not match verified game ID, which most likely means
|
||||
// this SaveState was reviewed + fixed on another device, but not uploaded.
|
||||
throw SyncValidationError.incorrectGame(game.name)
|
||||
}
|
||||
}
|
||||
catch let error as SyncValidationError
|
||||
{
|
||||
guard let verifiedGameID, SyncManager.shared.ignoredCorruptedRecordIDs.contains(record.recordID) else { throw error }
|
||||
|
||||
let fetchRequest = Game.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(Game.identifier), verifiedGameID)
|
||||
|
||||
if let correctGame = try self.managedObjectContext?.fetch(fetchRequest).first
|
||||
{
|
||||
self.game = correctGame
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ValidationError.nilRelationshipObjects(keys: [#keyPath(GameSave.game)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,14 @@
|
||||
import UIKit
|
||||
import OSLog
|
||||
|
||||
import Harmony
|
||||
import Roxas
|
||||
|
||||
extension RecordFlags
|
||||
{
|
||||
static let isGameRelationshipVerified = RecordFlags(rawValue: 1 << 0)
|
||||
}
|
||||
|
||||
class ReviewSaveStatesViewController: UITableViewController
|
||||
{
|
||||
var completionHandler: (() -> Void)?
|
||||
@ -186,6 +192,28 @@ private extension ReviewSaveStatesViewController
|
||||
{
|
||||
try self.managedObjectContext.save()
|
||||
|
||||
if let saveStates = self.saveStatesDataSource.fetchedResultsController.fetchedObjects, let coordinator = SyncManager.shared.coordinator
|
||||
{
|
||||
let records = try coordinator.recordController.fetchRecords(for: saveStates)
|
||||
if let context = records.first?.recordedObject?.managedObjectContext
|
||||
{
|
||||
try context.performAndWait {
|
||||
for record in records
|
||||
{
|
||||
record.perform { managedRecord in
|
||||
managedRecord.flags.insert(.isGameRelationshipVerified)
|
||||
managedRecord.setNeedsMetadataUpdate()
|
||||
|
||||
let saveState = record.recordedObject
|
||||
Logger.database.notice("Flagged SaveState “\(saveState?.localizedName ?? record.recordID.identifier, privacy: .public)” for metadata update.")
|
||||
}
|
||||
}
|
||||
|
||||
try context.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.completionHandler?()
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ extension HarmonyMetadataKey
|
||||
{
|
||||
static let gameID = HarmonyMetadataKey("gameID")
|
||||
static let gameName = HarmonyMetadataKey("gameName")
|
||||
static let verifiedGameID = HarmonyMetadataKey("verifiedGameID")
|
||||
|
||||
// Backwards compatibility
|
||||
static let coreID = HarmonyMetadataKey("coreID")
|
||||
|
||||
@ -258,7 +258,7 @@ private extension RecordVersionsViewController
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
|
||||
GameSave.ignoredCorruptedIDs.remove(self.record.recordID.identifier)
|
||||
SyncManager.shared.ignoredCorruptedRecordIDs.remove(self.record.recordID)
|
||||
|
||||
CATransaction.begin()
|
||||
|
||||
@ -295,11 +295,11 @@ private extension RecordVersionsViewController
|
||||
// Only allow restoring corrupted records with incorrect games.
|
||||
guard case .incorrectGame = error else { fallthrough }
|
||||
|
||||
let message = NSLocalizedString("Would you like to download this version anyway?", comment: "")
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Record Version Corrupted", comment: ""), message: message, preferredStyle: .alert)
|
||||
let message = NSLocalizedString("Would you like to download this version anyway? Delta will try to fix the corruption if possible.", comment: "")
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Version Corrupted", comment: ""), message: message, preferredStyle: .alert)
|
||||
alertController.addAction(.cancel)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Download Anyway", comment: ""), style: .destructive) { _ in
|
||||
GameSave.ignoredCorruptedIDs.insert(record.recordID.identifier)
|
||||
SyncManager.shared.ignoredCorruptedRecordIDs.insert(record.recordID)
|
||||
self.restoreVersion()
|
||||
})
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
|
||||
@ -95,6 +95,9 @@ final class SyncManager
|
||||
return self.coordinator?.recordController
|
||||
}
|
||||
|
||||
// Hacky, but YOLO I'm under time crunch.
|
||||
public var ignoredCorruptedRecordIDs = Set<RecordID>()
|
||||
|
||||
private(set) var syncProgress: Progress?
|
||||
|
||||
private(set) var previousSyncResult: SyncResult?
|
||||
|
||||
2
External/Harmony
vendored
2
External/Harmony
vendored
@ -1 +1 @@
|
||||
Subproject commit a30b440dfedb5fd109fc2b40064035cbcfdd4cce
|
||||
Subproject commit 07883cf4d90f4fc841a7162f348ea3b1b438fa50
|
||||
4731
Pods/Pods.xcodeproj/project.pbxproj
generated
4731
Pods/Pods.xcodeproj/project.pbxproj
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user