Fixes overwriting save file when previewing games

This commit is contained in:
Riley Testut 2020-04-27 13:13:34 -07:00
parent e11e4437c5
commit 75869c06fd

View File

@ -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)
}
}
}
}