From 75869c06fdfad629bad1977d46b8446e0c7c20c4 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 27 Apr 2020 13:13:34 -0700 Subject: [PATCH] Fixes overwriting save file when previewing games --- .../Emulation/PreviewGameViewController.swift | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/Delta/Emulation/PreviewGameViewController.swift b/Delta/Emulation/PreviewGameViewController.swift index 2089c3c..7dff3f7 100644 --- a/Delta/Emulation/PreviewGameViewController.swift +++ b/Delta/Emulation/PreviewGameViewController.swift @@ -28,6 +28,13 @@ class PreviewGameViewController: DeltaCore.GameViewController } private var emulatorCoreQueue = DispatchQueue(label: "com.rileytestut.Delta.PreviewGameViewController.emulatorCoreQueue", qos: .userInitiated) + private var copiedSaveFiles = [(originalURL: URL, copyURL: URL)]() + + private lazy var temporaryDirectoryURL: URL = { + let directoryURL = FileManager.default.temporaryDirectory.appendingPathComponent("preview-" + UUID().uuidString) + try? FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) + return directoryURL + }() override var game: GameProtocol? { willSet { @@ -53,6 +60,10 @@ class PreviewGameViewController: DeltaCore.GameViewController deinit { + // Explicitly stop emulatorCore _before_ we remove ourselves as observer + // so we can wait until stopped before restoring save files (again). + self.emulatorCore?.stop() + self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext) } } @@ -70,6 +81,13 @@ extension PreviewGameViewController // Temporarily prevent emulatorCore from updating gameView to prevent flicker of black, or other visual glitches self.emulatorCore?.remove(self.gameView) } + + override func viewWillAppear(_ animated: Bool) + { + super.viewWillAppear(animated) + + self.copySaveFiles() + } override func viewDidAppear(_ animated: Bool) { @@ -88,6 +106,19 @@ extension PreviewGameViewController self.emulatorCore?.pause() } + override func viewDidDisappear(_ animated: Bool) + { + super.viewDidDisappear(animated) + + // Already stopped = we've already restored save files and removed directory. + if self.emulatorCore?.state != .stopped + { + // Pre-emptively restore save files in case something goes wrong while stopping emulation. + // This also ensures if the core is never stopped (for some reason), saves are still restored. + self.restoreSaveFiles(removeCopyDirectory: false) + } + } + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -112,13 +143,21 @@ extension PreviewGameViewController let state = self.emulatorCore?.state else { return } - if previousState == .stopped, state == .running + switch state { + case .running where previousState == .stopped: self.emulatorCoreQueue.async { // Pause to prevent it from starting before visible (in case user peeked slowly) self.emulatorCore?.pause() self.preparePreview() } + + case .stopped: + // Emulation has stopped, so we can safely restore save files, + // and also remove the directory they were copied to. + self.restoreSaveFiles(removeCopyDirectory: true) + + default: break } } } @@ -172,4 +211,56 @@ private extension PreviewGameViewController self.emulatorCore?.resume() } + + func copySaveFiles() + { + guard let game = self.game as? Game, let gameSave = game.gameSave else { return } + + self.copiedSaveFiles.removeAll() + + let fileURLs = gameSave.syncableFiles.lazy.map { $0.fileURL } + for fileURL in fileURLs + { + do + { + let destinationURL = self.temporaryDirectoryURL.appendingPathComponent(fileURL.lastPathComponent) + try FileManager.default.copyItem(at: fileURL, to: destinationURL, shouldReplace: true) + + self.copiedSaveFiles.append((fileURL, destinationURL)) + print("Copied save file:", fileURL.lastPathComponent) + } + catch + { + print("Failed to back up save file \(fileURL.lastPathComponent).", error) + } + } + } + + func restoreSaveFiles(removeCopyDirectory: Bool) + { + for (originalURL, copyURL) in self.copiedSaveFiles + { + do + { + try FileManager.default.copyItem(at: copyURL, to: originalURL, shouldReplace: true) + print("Restored save file:", originalURL.lastPathComponent) + } + catch + { + print("Failed to restore copied save file \(copyURL.lastPathComponent).", error) + } + } + + if removeCopyDirectory + { + do + { + try FileManager.default.removeItem(at: self.temporaryDirectoryURL) + } + catch + { + print("Failed to remove preview temporary directory.", error) + } + } + } }