Implements error handling when importing games + controller skins
This commit is contained in:
parent
4d5dee6cea
commit
2bb4c4d278
@ -31,6 +31,7 @@
|
|||||||
BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */; };
|
BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */; };
|
||||||
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */; };
|
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */; };
|
||||||
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */; };
|
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */; };
|
||||||
|
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */; };
|
||||||
BF1DAD5D1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift */; };
|
BF1DAD5D1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift */; };
|
||||||
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
|
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
|
||||||
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
|
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
|
||||||
@ -133,6 +134,7 @@
|
|||||||
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllersSettingsViewController.swift; path = Controllers/ControllersSettingsViewController.swift; sourceTree = "<group>"; };
|
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllersSettingsViewController.swift; path = Controllers/ControllersSettingsViewController.swift; sourceTree = "<group>"; };
|
||||||
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewGameViewController.swift; sourceTree = "<group>"; };
|
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewGameViewController.swift; sourceTree = "<group>"; };
|
||||||
BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EmulatorCore+Cheats.swift"; sourceTree = "<group>"; };
|
BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EmulatorCore+Cheats.swift"; sourceTree = "<group>"; };
|
||||||
|
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Importing.swift"; sourceTree = "<group>"; };
|
||||||
BF1DAD5C1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameTypeControllerSkinsViewController.swift; path = "Controller Skins/GameTypeControllerSkinsViewController.swift"; sourceTree = "<group>"; };
|
BF1DAD5C1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameTypeControllerSkinsViewController.swift; path = "Controller Skins/GameTypeControllerSkinsViewController.swift"; sourceTree = "<group>"; };
|
||||||
BF27CC861BC9E3C600A20D89 /* Delta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Delta.entitlements; sourceTree = "<group>"; };
|
BF27CC861BC9E3C600A20D89 /* Delta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Delta.entitlements; sourceTree = "<group>"; };
|
||||||
BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods.framework; path = "Pods/../build/Debug-appletvos/Pods.framework"; sourceTree = "<group>"; };
|
BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods.framework; path = "Pods/../build/Debug-appletvos/Pods.framework"; sourceTree = "<group>"; };
|
||||||
@ -239,6 +241,7 @@
|
|||||||
BF5942911E09BD1A0051894B /* NSManagedObject+Conveniences.swift */,
|
BF5942911E09BD1A0051894B /* NSManagedObject+Conveniences.swift */,
|
||||||
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */,
|
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */,
|
||||||
BFC314761E0C8CFC0056E3A8 /* GameType+Delta.swift */,
|
BFC314761E0C8CFC0056E3A8 /* GameType+Delta.swift */,
|
||||||
|
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -717,6 +720,7 @@
|
|||||||
BF5942731E09BC700051894B /* Model.xcdatamodel in Sources */,
|
BF5942731E09BC700051894B /* Model.xcdatamodel in Sources */,
|
||||||
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
||||||
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */,
|
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */,
|
||||||
|
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */,
|
||||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
|
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
|
||||||
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
|
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
|
||||||
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
|
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
|
||||||
|
|||||||
@ -97,14 +97,40 @@ extension AppDelegate
|
|||||||
|
|
||||||
private func importGame(at url: URL) -> Bool
|
private func importGame(at url: URL) -> Bool
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.importGames(at: [url], completion: nil)
|
DatabaseManager.shared.importGames(at: [url]) { (games, errors) in
|
||||||
|
if errors.count > 0
|
||||||
|
{
|
||||||
|
let alertController = UIAlertController.alertController(for: .games, with: errors)
|
||||||
|
self.present(alertController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func importControllerSkin(at url: URL) -> Bool
|
private func importControllerSkin(at url: URL) -> Bool
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.importControllerSkins(at: [url], completion: nil)
|
DatabaseManager.shared.importControllerSkins(at: [url]) { (games, errors) in
|
||||||
|
if errors.count > 0
|
||||||
|
{
|
||||||
|
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors)
|
||||||
|
self.present(alertController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func present(_ alertController: UIAlertController)
|
||||||
|
{
|
||||||
|
var rootViewController = self.window?.rootViewController
|
||||||
|
|
||||||
|
while rootViewController?.presentedViewController != nil
|
||||||
|
{
|
||||||
|
rootViewController = rootViewController?.presentedViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
rootViewController?.present(alertController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import MobileCoreServices
|
|||||||
|
|
||||||
protocol ImportControllerDelegate
|
protocol ImportControllerDelegate
|
||||||
{
|
{
|
||||||
func importController(_ importController: ImportController, didImport games: Set<Game>)
|
func importController(_ importController: ImportController, didImport games: Set<Game>, with errors: Set<DatabaseManager.ImportError>)
|
||||||
func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>)
|
func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>, with errors: Set<DatabaseManager.ImportError>)
|
||||||
|
|
||||||
/** Optional **/
|
/** Optional **/
|
||||||
func importControllerDidCancel(_ importController: ImportController)
|
func importControllerDidCancel(_ importController: ImportController)
|
||||||
@ -73,10 +73,10 @@ class ImportController: NSObject
|
|||||||
|
|
||||||
DatabaseManager.shared.performBackgroundTask { (context) in
|
DatabaseManager.shared.performBackgroundTask { (context) in
|
||||||
let controllerSkinURLs = contents.filter { $0.pathExtension.lowercased() == "deltaskin" }
|
let controllerSkinURLs = contents.filter { $0.pathExtension.lowercased() == "deltaskin" }
|
||||||
self.importControllerSkins(at: controllerSkinURLs)
|
self.importControllerSkins(at: Set(controllerSkinURLs))
|
||||||
|
|
||||||
let gameURLs = contents.filter { GameType.gameType(forFileExtension: $0.pathExtension) != .unknown || $0.pathExtension.lowercased() == "zip" }
|
let gameURLs = contents.filter { GameType.gameType(forFileExtension: $0.pathExtension) != .unknown || $0.pathExtension.lowercased() == "zip" }
|
||||||
self.importGames(at: gameURLs)
|
self.importGames(at: Set(gameURLs))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -99,37 +99,17 @@ class ImportController: NSObject
|
|||||||
self.presentingViewController?.present(alertController, animated: true, completion: completion)
|
self.presentingViewController?.present(alertController, animated: true, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func importGames(at urls: [URL])
|
fileprivate func importGames(at urls: Set<URL>)
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.importGames(at: urls) { identifiers in
|
DatabaseManager.shared.importGames(at: urls) { (games, errors) in
|
||||||
|
self.delegate?.importController(self, didImport: games, with: errors)
|
||||||
DatabaseManager.shared.viewContext.perform() {
|
|
||||||
|
|
||||||
let predicate = NSPredicate(format: "%K IN (%@)", #keyPath(Game.identifier), identifiers)
|
|
||||||
let games = Game.instancesWithPredicate(predicate, inManagedObjectContext: DatabaseManager.shared.viewContext, type: Game.self)
|
|
||||||
|
|
||||||
self.delegate?.importController(self, didImport: Set(games))
|
|
||||||
|
|
||||||
self.presentingViewController?.importController = nil
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func importControllerSkins(at urls: [URL])
|
fileprivate func importControllerSkins(at urls: Set<URL>)
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.importControllerSkins(at: urls) { identifiers in
|
DatabaseManager.shared.importControllerSkins(at: urls) { (controllerSkins, errors) in
|
||||||
|
self.delegate?.importController(self, didImport: controllerSkins, with: errors)
|
||||||
DatabaseManager.shared.viewContext.perform() {
|
|
||||||
|
|
||||||
let predicate = NSPredicate(format: "%K IN (%@)", #keyPath(ControllerSkin.identifier), identifiers)
|
|
||||||
let controllerSkins = ControllerSkin.instancesWithPredicate(predicate, inManagedObjectContext: DatabaseManager.shared.viewContext, type: ControllerSkin.self)
|
|
||||||
|
|
||||||
self.delegate?.importController(self, didImport: Set(controllerSkins))
|
|
||||||
|
|
||||||
self.presentingViewController?.importController = nil
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,43 @@ import ZipZap
|
|||||||
// Pods
|
// Pods
|
||||||
import FileMD5Hash
|
import FileMD5Hash
|
||||||
|
|
||||||
|
extension DatabaseManager
|
||||||
|
{
|
||||||
|
enum ImportError: Error, Hashable
|
||||||
|
{
|
||||||
|
case doesNotExist(URL)
|
||||||
|
case invalid(URL)
|
||||||
|
case unknown(URL, NSError)
|
||||||
|
case saveFailed(Set<URL>, NSError)
|
||||||
|
|
||||||
|
var hashValue: Int {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .doesNotExist: return 0
|
||||||
|
case .invalid: return 1
|
||||||
|
case .unknown: return 2
|
||||||
|
case .saveFailed: return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ImportError, rhs: ImportError) -> Bool
|
||||||
|
{
|
||||||
|
switch (lhs, rhs)
|
||||||
|
{
|
||||||
|
case (let .doesNotExist(url1), let .doesNotExist(url2)) where url1 == url2: return true
|
||||||
|
case (let .invalid(url1), let .invalid(url2)) where url1 == url2: return true
|
||||||
|
case (let .unknown(url1, error1), let .unknown(url2, error2)) where url1 == url2 && error1 == error2: return true
|
||||||
|
case (let .saveFailed(urls1, error1), let .saveFailed(urls2, error2)) where urls1 == urls2 && error1 == error2: return true
|
||||||
|
case (.doesNotExist, _): return false
|
||||||
|
case (.invalid, _): return false
|
||||||
|
case (.unknown, _): return false
|
||||||
|
case (.saveFailed, _): return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class DatabaseManager: NSPersistentContainer
|
final class DatabaseManager: NSPersistentContainer
|
||||||
{
|
{
|
||||||
static let shared = DatabaseManager()
|
static let shared = DatabaseManager()
|
||||||
@ -108,14 +145,19 @@ private extension DatabaseManager
|
|||||||
/// Importing
|
/// Importing
|
||||||
extension DatabaseManager
|
extension DatabaseManager
|
||||||
{
|
{
|
||||||
func importGames(at urls: [URL], completion: ((Set<String>) -> Void)?)
|
func importGames(at urls: Set<URL>, completion: ((Set<Game>, Set<ImportError>) -> Void)?)
|
||||||
{
|
{
|
||||||
|
var errors = Set<ImportError>()
|
||||||
|
|
||||||
let zipFileURLs = urls.filter { $0.pathExtension.lowercased() == "zip" }
|
let zipFileURLs = urls.filter { $0.pathExtension.lowercased() == "zip" }
|
||||||
if zipFileURLs.count > 0
|
if zipFileURLs.count > 0
|
||||||
{
|
{
|
||||||
self.extractCompressedGames(at: zipFileURLs) { (extractedURLs) in
|
self.extractCompressedGames(at: Set(zipFileURLs)) { (extractedURLs, extractErrors) in
|
||||||
let gameURLs = urls.filter { $0.pathExtension.lowercased() != "zip" } + extractedURLs
|
let gameURLs = urls.filter { $0.pathExtension.lowercased() != "zip" } + extractedURLs
|
||||||
self.importGames(at: gameURLs, completion: completion)
|
self.importGames(at: Set(gameURLs)) { (importedGames, importErrors) in
|
||||||
|
let allErrors = importErrors.union(extractErrors)
|
||||||
|
completion?(importedGames, allErrors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -127,7 +169,10 @@ extension DatabaseManager
|
|||||||
|
|
||||||
for url in urls
|
for url in urls
|
||||||
{
|
{
|
||||||
guard FileManager.default.fileExists(atPath: url.path) else { continue }
|
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||||
|
errors.insert(.doesNotExist(url))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
let identifier = FileHash.sha1HashOfFile(atPath: url.path) as String
|
let identifier = FileHash.sha1HashOfFile(atPath: url.path) as String
|
||||||
|
|
||||||
@ -159,38 +204,56 @@ extension DatabaseManager
|
|||||||
|
|
||||||
identifiers.insert(game.identifier)
|
identifiers.insert(game.identifier)
|
||||||
}
|
}
|
||||||
catch
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
print("Import Games error:", error)
|
print("Import Games error:", error)
|
||||||
game.managedObjectContext?.delete(game)
|
game.managedObjectContext?.delete(game)
|
||||||
|
|
||||||
|
errors.insert(.unknown(url, error))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
try context.save()
|
try context.save()
|
||||||
}
|
}
|
||||||
catch
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
print("Failed to save import context:", error)
|
print("Failed to save import context:", error)
|
||||||
|
|
||||||
identifiers.removeAll()
|
identifiers.removeAll()
|
||||||
|
|
||||||
|
errors.insert(.saveFailed(urls, error))
|
||||||
}
|
}
|
||||||
|
|
||||||
completion?(identifiers)
|
DatabaseManager.shared.viewContext.perform {
|
||||||
|
let predicate = NSPredicate(format: "%K IN (%@)", #keyPath(Game.identifier), identifiers)
|
||||||
|
let games = Game.instancesWithPredicate(predicate, inManagedObjectContext: DatabaseManager.shared.viewContext, type: Game.self)
|
||||||
|
completion?(Set(games), errors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func importControllerSkins(at urls: [URL], completion: ((Set<String>) -> Void)?)
|
func importControllerSkins(at urls: Set<URL>, completion: ((Set<ControllerSkin>, Set<ImportError>) -> Void)?)
|
||||||
{
|
{
|
||||||
|
var errors = Set<ImportError>()
|
||||||
|
|
||||||
self.performBackgroundTask { (context) in
|
self.performBackgroundTask { (context) in
|
||||||
|
|
||||||
var identifiers = Set<String>()
|
var identifiers = Set<String>()
|
||||||
|
|
||||||
for url in urls
|
for url in urls
|
||||||
{
|
{
|
||||||
guard let deltaControllerSkin = DeltaCore.ControllerSkin(fileURL: url) else { continue }
|
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||||
|
errors.insert(.doesNotExist(url))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let deltaControllerSkin = DeltaCore.ControllerSkin(fileURL: url) else {
|
||||||
|
errors.insert(.invalid(url))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
let controllerSkin = ControllerSkin(context: context)
|
let controllerSkin = ControllerSkin(context: context)
|
||||||
controllerSkin.filename = deltaControllerSkin.identifier + ".deltaskin"
|
controllerSkin.filename = deltaControllerSkin.identifier + ".deltaskin"
|
||||||
@ -212,10 +275,12 @@ extension DatabaseManager
|
|||||||
|
|
||||||
identifiers.insert(controllerSkin.identifier)
|
identifiers.insert(controllerSkin.identifier)
|
||||||
}
|
}
|
||||||
catch
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
print("Import Controller Skins error:", error)
|
print("Import Controller Skins error:", error)
|
||||||
controllerSkin.managedObjectContext?.delete(controllerSkin)
|
controllerSkin.managedObjectContext?.delete(controllerSkin)
|
||||||
|
|
||||||
|
errors.insert(.unknown(url, error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,26 +288,35 @@ extension DatabaseManager
|
|||||||
{
|
{
|
||||||
try context.save()
|
try context.save()
|
||||||
}
|
}
|
||||||
catch
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
print("Failed to save controller skin import context:", error)
|
print("Failed to save controller skin import context:", error)
|
||||||
|
|
||||||
identifiers.removeAll()
|
identifiers.removeAll()
|
||||||
|
|
||||||
|
errors.insert(.saveFailed(urls, error))
|
||||||
}
|
}
|
||||||
|
|
||||||
completion?(identifiers)
|
DatabaseManager.shared.viewContext.perform {
|
||||||
|
let predicate = NSPredicate(format: "%K IN (%@)", #keyPath(Game.identifier), identifiers)
|
||||||
|
let controllerSkins = ControllerSkin.instancesWithPredicate(predicate, inManagedObjectContext: DatabaseManager.shared.viewContext, type: ControllerSkin.self)
|
||||||
|
completion?(Set(controllerSkins), errors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func extractCompressedGames(at urls: [URL], completion: @escaping ((Set<URL>) -> Void))
|
private func extractCompressedGames(at urls: Set<URL>, completion: @escaping ((Set<URL>, Set<ImportError>) -> Void))
|
||||||
{
|
{
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
|
|
||||||
var semaphores = Set<DispatchSemaphore>()
|
var semaphores = Set<DispatchSemaphore>()
|
||||||
var outputURLs = Set<URL>()
|
var outputURLs = Set<URL>()
|
||||||
|
var errors = Set<ImportError>()
|
||||||
|
|
||||||
for url in urls
|
for url in urls
|
||||||
{
|
{
|
||||||
|
var archiveContainsValidGameFile = false
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let archive = try ZZArchive(url: url)
|
let archive = try ZZArchive(url: url)
|
||||||
@ -257,6 +331,11 @@ extension DatabaseManager
|
|||||||
|
|
||||||
guard gameType != .unknown else { continue }
|
guard gameType != .unknown else { continue }
|
||||||
|
|
||||||
|
// At least one entry is a valid game file, so we set archiveContainsValidGameFile to true
|
||||||
|
// This will result in this archive being considered valid, and thus we will not return an ImportError.invalid error for the archive
|
||||||
|
// However, if this game file does turn out to be invalid when extracting, we'll return an ImportError.invalid error specific to this game file
|
||||||
|
archiveContainsValidGameFile = true
|
||||||
|
|
||||||
// ROMs may potentially be very large, so we extract using file streams and not raw Data
|
// ROMs may potentially be very large, so we extract using file streams and not raw Data
|
||||||
let inputStream = try entry.newStream()
|
let inputStream = try entry.newStream()
|
||||||
|
|
||||||
@ -290,12 +369,15 @@ extension DatabaseManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
print(error)
|
print(error)
|
||||||
|
|
||||||
|
errors.insert(.invalid(outputURL))
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outputURLs.insert(outputURL)
|
outputURLs.insert(outputURL)
|
||||||
semaphore.signal()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
semaphore.signal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,6 +385,11 @@ extension DatabaseManager
|
|||||||
{
|
{
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !archiveContainsValidGameFile
|
||||||
|
{
|
||||||
|
errors.insert(.invalid(url))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for semaphore in semaphores
|
for semaphore in semaphores
|
||||||
@ -325,7 +412,7 @@ extension DatabaseManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
completion(outputURLs)
|
completion(outputURLs, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
Delta/Extensions/UIAlertController+Importing.swift
Normal file
80
Delta/Extensions/UIAlertController+Importing.swift
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
//
|
||||||
|
// UIAlertController+Importing.swift
|
||||||
|
// Delta
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 1/13/17.
|
||||||
|
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
import Roxas
|
||||||
|
|
||||||
|
extension UIAlertController
|
||||||
|
{
|
||||||
|
enum ImportType
|
||||||
|
{
|
||||||
|
case games
|
||||||
|
case controllerSkins
|
||||||
|
}
|
||||||
|
|
||||||
|
class func alertController(for importType: ImportType, with errors: Set<DatabaseManager.ImportError>) -> UIAlertController
|
||||||
|
{
|
||||||
|
let title: String
|
||||||
|
|
||||||
|
switch importType
|
||||||
|
{
|
||||||
|
case .games: title = NSLocalizedString("Error Importing Games", comment: "")
|
||||||
|
case .controllerSkins: title = NSLocalizedString("Error Importing Controller Skins", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
||||||
|
|
||||||
|
var urls = Set<URL>()
|
||||||
|
|
||||||
|
for error in errors
|
||||||
|
{
|
||||||
|
switch error
|
||||||
|
{
|
||||||
|
case .doesNotExist(let url): urls.insert(url)
|
||||||
|
case .invalid(let url): urls.insert(url)
|
||||||
|
case .unknown(let url, _): urls.insert(url)
|
||||||
|
case .saveFailed(let errorURLs, _): urls.formUnion(errorURLs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let filenames = urls.map{ $0.lastPathComponent }.sorted()
|
||||||
|
|
||||||
|
if filenames.count > 0
|
||||||
|
{
|
||||||
|
var message: String
|
||||||
|
|
||||||
|
switch importType
|
||||||
|
{
|
||||||
|
case .games: message = NSLocalizedString("The following game files could not be imported:", comment: "") + "\n"
|
||||||
|
case .controllerSkins: message = NSLocalizedString("The following controller skin files could not be imported:", comment: "") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
for filename in filenames
|
||||||
|
{
|
||||||
|
message += "\n" + filename
|
||||||
|
}
|
||||||
|
|
||||||
|
alertController.message = message
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This branch can be executed when there are no input URLs when importing, but there is an error saving the database anyway.
|
||||||
|
|
||||||
|
switch importType
|
||||||
|
{
|
||||||
|
case .games: alertController.message = NSLocalizedString("Delta was unable to import games. Please try again later.", comment: "")
|
||||||
|
case .controllerSkins: alertController.message = NSLocalizedString("Delta was unable to import controller skins. Please try again later.", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .cancel, handler: nil))
|
||||||
|
|
||||||
|
return alertController
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -276,14 +276,32 @@ extension GamesViewController: ImportControllerDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - ImportControllerDelegate
|
//MARK: - ImportControllerDelegate
|
||||||
@nonobjc func importController(_ importController: ImportController, didImport games: Set<Game>)
|
@nonobjc func importController(_ importController: ImportController, didImport games: Set<Game>, with errors: Set<DatabaseManager.ImportError>)
|
||||||
{
|
{
|
||||||
print(games)
|
if errors.count > 0
|
||||||
|
{
|
||||||
|
let alertController = UIAlertController.alertController(for: .games, with: errors)
|
||||||
|
self.present(alertController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if games.count > 0
|
||||||
|
{
|
||||||
|
print("Imported Games:", games.map { $0.name })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>)
|
@nonobjc func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>, with errors: Set<DatabaseManager.ImportError>)
|
||||||
{
|
{
|
||||||
print(controllerSkins)
|
if errors.count > 0
|
||||||
|
{
|
||||||
|
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors)
|
||||||
|
self.present(alertController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if controllerSkins.count > 0
|
||||||
|
{
|
||||||
|
print("Imported Controller Skins:", controllerSkins.map { $0.name })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
External/Roxas
vendored
2
External/Roxas
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 4ceaf192ac161e04adf4a1b6ab964ddf689367b4
|
Subproject commit df42ea9b1f360756387995917541e5fe7a6178e4
|
||||||
Loading…
Reference in New Issue
Block a user