Fixes sharing games + exporting save files
* Makes temporary copy instead of symbolic link when exporting game * Sanitizes game name before exporting game * Fixes prematurely deleting temporary game * Exported save file names now match exported game names
This commit is contained in:
parent
9e437797d9
commit
edab6ea432
@ -165,6 +165,7 @@
|
||||
BFFDF03723E3BB2600931B96 /* libSnes9x.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFFDF03623E3BB2600931B96 /* libSnes9x.a */; };
|
||||
BFFDF03F23E3C28A00931B96 /* libGambatte.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFFDF03D23E3C0F000931B96 /* libGambatte.a */; };
|
||||
BFFDF04623E3D3A600931B96 /* libMupen64Plus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFFDF04523E3D3A600931B96 /* libMupen64Plus.a */; };
|
||||
D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -358,6 +359,7 @@
|
||||
BFFDF03D23E3C0F000931B96 /* libGambatte.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libGambatte.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFFDF04523E3D3A600931B96 /* libMupen64Plus.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libMupen64Plus.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C786AF1D2DDB6223BE2063CC /* Pods-Delta.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Filename.swift"; sourceTree = "<group>"; };
|
||||
DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Delta.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -403,6 +405,7 @@
|
||||
BF647A6922FB8FCE0061D76D /* Bundle+SwizzleBundleID.swift */,
|
||||
BFD1EF3F2336BD8800D197CF /* UIDevice+Processor.swift */,
|
||||
BFE56E1823EB7BE00014FECD /* UIImage+SymbolFallback.swift */,
|
||||
D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -1114,6 +1117,7 @@
|
||||
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */,
|
||||
BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */,
|
||||
BF59427E1E09BC830051894B /* Game.swift in Sources */,
|
||||
D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */,
|
||||
BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */,
|
||||
BF63A1A321A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift in Sources */,
|
||||
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
|
||||
|
||||
22
Delta/Extensions/CharacterSet+Filename.swift
Normal file
22
Delta/Extensions/CharacterSet+Filename.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// CharacterSet+Filename.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 4/28/22.
|
||||
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension CharacterSet
|
||||
{
|
||||
// Different than .urlPathAllowed
|
||||
// Copied from https://stackoverflow.com/a/39443252
|
||||
static var urlFilenameAllowed: CharacterSet {
|
||||
var illegalCharacters = CharacterSet(charactersIn: ":/")
|
||||
illegalCharacters.formUnion(.newlines)
|
||||
illegalCharacters.formUnion(.illegalCharacters)
|
||||
illegalCharacters.formUnion(.controlCharacters)
|
||||
return illegalCharacters.inverted
|
||||
}
|
||||
}
|
||||
@ -705,28 +705,36 @@ private extension GameCollectionViewController
|
||||
|
||||
func share(_ game: Game)
|
||||
{
|
||||
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
let symbolicURL = temporaryDirectory.appendingPathComponent(game.name + "." + game.fileURL.pathExtension)
|
||||
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
|
||||
let sanitizedName = game.name.components(separatedBy: .urlFilenameAllowed.inverted).joined()
|
||||
let temporaryURL = temporaryDirectory.appendingPathComponent(sanitizedName + "." + game.fileURL.pathExtension, isDirectory: false)
|
||||
|
||||
do
|
||||
{
|
||||
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
// Create a symbolic link so we can control the file name used when sharing.
|
||||
|
||||
// Make a temporary copy so we can control the filename used when sharing.
|
||||
// Otherwise, if we just passed in game.fileURL to UIActivityViewController, the file name would be the game's SHA1 hash.
|
||||
try FileManager.default.createSymbolicLink(at: symbolicURL, withDestinationURL: game.fileURL)
|
||||
try FileManager.default.copyItem(at: game.fileURL, to: temporaryURL, shouldReplace: true)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Could Not Share Game", comment: ""), error: error)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let copyDeepLinkActivity = CopyDeepLinkActivity()
|
||||
|
||||
let activityViewController = UIActivityViewController(activityItems: [symbolicURL, game], applicationActivities: [copyDeepLinkActivity])
|
||||
let activityViewController = UIActivityViewController(activityItems: [temporaryURL, game], applicationActivities: [copyDeepLinkActivity])
|
||||
activityViewController.popoverPresentationController?.sourceView = self._popoverSourceView?.superview
|
||||
activityViewController.popoverPresentationController?.sourceRect = self._popoverSourceView?.frame ?? .zero
|
||||
activityViewController.completionWithItemsHandler = { (activityType, finished, returnedItems, error) in
|
||||
// Make sure the user either shared the game or cancelled before deleting temporaryDirectory.
|
||||
guard finished || activityType == nil else { return }
|
||||
|
||||
do
|
||||
{
|
||||
try FileManager.default.removeItem(at: temporaryDirectory)
|
||||
@ -736,6 +744,7 @@ private extension GameCollectionViewController
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
self.present(activityViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@ -791,8 +800,7 @@ private extension GameCollectionViewController
|
||||
{
|
||||
do
|
||||
{
|
||||
let illegalCharacterSet = CharacterSet(charactersIn: "\"\\/?<>:*|")
|
||||
let sanitizedFilename = game.name.components(separatedBy: illegalCharacterSet).joined() + "." + game.gameSaveURL.pathExtension
|
||||
let sanitizedFilename = game.name.components(separatedBy: .urlFilenameAllowed.inverted).joined()
|
||||
|
||||
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(sanitizedFilename)
|
||||
try FileManager.default.copyItem(at: game.gameSaveURL, to: temporaryURL, shouldReplace: true)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user