From 60abb9ee078093499f1ff644273bb45875990133 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Sat, 16 May 2020 16:39:02 -0700 Subject: [PATCH] Adds option to manually restore backup for active apps that have one --- AltStore/Managing Apps/AppManager.swift | 43 +++++++++++++++++++-- AltStore/My Apps/MyAppsViewController.swift | 42 ++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 24d06980..27140b45 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -389,6 +389,32 @@ extension AppManager } } + func restore(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result) -> Void) + { + let group = RefreshGroup() + group.completionHandler = { (results) in + do + { + guard let result = results.values.first else { throw OperationError.unknown } + + let installedApp = try result.get() + assert(installedApp.managedObjectContext != nil) + + installedApp.managedObjectContext?.perform { + installedApp.isActive = true + completionHandler(.success(installedApp)) + } + } + catch + { + completionHandler(.failure(error)) + } + } + + let operation = AppOperation.restore(installedApp) + self.perform([operation], presentingViewController: presentingViewController, group: group) + } + func remove(_ installedApp: InstalledApp, completionHandler: @escaping (Result) -> Void) { let authenticationContext = AuthenticatedOperationContext() @@ -453,13 +479,14 @@ private extension AppManager case refresh(InstalledApp) case activate(InstalledApp) case deactivate(InstalledApp) + case restore(InstalledApp) var app: AppProtocol { switch self { case .install(let app), .update(let app), .refresh(let app as AppProtocol), .activate(let app as AppProtocol), - .deactivate(let app as AppProtocol): + .deactivate(let app as AppProtocol), .restore(let app as AppProtocol): return app } } @@ -578,6 +605,14 @@ private extension AppManager self.finish(operation, result: result, group: group, progress: progress) } progress?.addChild(deactivateProgress, withPendingUnitCount: 80) + + case .restore(let app): + // Restoring, which is effectively just activating an app. + + let activateProgress = self._activate(app, operation: operation, group: group) { (result) in + self.finish(operation, result: result, group: group, progress: progress) + } + progress?.addChild(activateProgress, withPendingUnitCount: 80) } } } @@ -1121,7 +1156,7 @@ private extension AppManager event = nil case .update: event = .updatedApp(installedApp) - case .activate, .deactivate: event = nil + case .activate, .deactivate, .restore: event = nil } if let event = event @@ -1179,7 +1214,7 @@ private extension AppManager switch operation { case .install, .update: return self.installationProgress[operation.bundleIdentifier] - case .refresh, .activate, .deactivate: return self.refreshProgress[operation.bundleIdentifier] + case .refresh, .activate, .deactivate, .restore: return self.refreshProgress[operation.bundleIdentifier] } } @@ -1188,7 +1223,7 @@ private extension AppManager switch operation { case .install, .update: self.installationProgress[operation.bundleIdentifier] = progress - case .refresh, .activate, .deactivate: self.refreshProgress[operation.bundleIdentifier] = progress + case .refresh, .activate, .deactivate, .restore: self.refreshProgress[operation.bundleIdentifier] = progress } } } diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 287af528..33473a1d 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -1002,6 +1002,39 @@ private extension MyAppsViewController self.present(alertController, animated: true, completion: nil) } + func restore(_ installedApp: InstalledApp) + { + let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name) + let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet) + alertController.addAction(.cancel) + alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { (action) in + AppManager.shared.restore(installedApp, presentingViewController: self) { (result) in + do + { + let app = try result.get() + try? app.managedObjectContext?.save() + + print("Finished restoring app:", app.bundleIdentifier) + } + catch + { + print("Failed to restore app:", error) + + DispatchQueue.main.async { + let toastView = ToastView(error: error) + toastView.show(in: self) + } + } + } + + DispatchQueue.main.async { + self.collectionView.reloadSections([Section.activeApps.rawValue]) + } + })) + + self.present(alertController, animated: true, completion: nil) + } + func exportBackup(for installedApp: InstalledApp) { guard let backupURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return } @@ -1201,6 +1234,10 @@ extension MyAppsViewController self.exportBackup(for: installedApp) } + let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc")) { (action) in + self.restore(installedApp) + } + guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else { return [refreshAction] } @@ -1231,6 +1268,11 @@ extension MyAppsViewController if backupExists { actions.append(exportBackupAction) + + if installedApp.isActive + { + actions.append(restoreBackupAction) + } } else if let error = outError {