From 9d3eda952660625126579b5df7761e0d32321e0e Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 18 Jun 2019 18:31:59 -0700 Subject: [PATCH] [AltStore] Ensures apps are downloaded before attempting to refresh --- AltStore/Apps/AppManager.swift | 134 ++++++++++-------- .../Operations/DownloadAppOperation.swift | 42 ++++-- 2 files changed, 109 insertions(+), 67 deletions(-) diff --git a/AltStore/Apps/AppManager.swift b/AltStore/Apps/AppManager.swift index 857b8f47..48020fb7 100644 --- a/AltStore/Apps/AppManager.swift +++ b/AltStore/Apps/AppManager.swift @@ -104,48 +104,17 @@ extension AppManager { func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result) -> Void) -> Progress { - let progress = Progress.discreteProgress(totalUnitCount: 100) - - // Authenticate - let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController) - authenticationOperation.resultHandler = { (result) in - switch result + let progress = self.install([app], forceDownload: true, presentingViewController: presentingViewController) { (result) in + do { - case .failure(let error): completionHandler(.failure(error)) - case .success(let signer): - - // Download - app.managedObjectContext?.perform { - let downloadAppOperation = DownloadAppOperation(app: app) - downloadAppOperation.resultHandler = { (result) in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success(let installedApp): - let context = installedApp.managedObjectContext - - // Refresh/Install - let (resignProgress, installProgress) = self.refresh(installedApp, signer: signer, presentingViewController: presentingViewController) { (result) in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success: - context?.perform { - completionHandler(.success(installedApp)) - } - } - } - progress.addChild(resignProgress, withPendingUnitCount: 10) - progress.addChild(installProgress, withPendingUnitCount: 45) - } - } - progress.addChild(downloadAppOperation.progress, withPendingUnitCount: 40) - self.operationQueue.addOperation(downloadAppOperation) - } + guard let (_, result) = try result.get().first else { throw OperationError.unknown } + completionHandler(result) + } + catch + { + completionHandler(.failure(error)) } } - progress.addChild(authenticationOperation.progress, withPendingUnitCount: 5) - self.operationQueue.addOperation(authenticationOperation) return progress } @@ -167,9 +136,20 @@ extension AppManager @discardableResult func refresh(_ installedApps: [InstalledApp], presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result], Error>) -> Void) -> Progress { - let progress = Progress.discreteProgress(totalUnitCount: Int64(installedApps.count)) + let apps = installedApps.compactMap { $0.app } - guard let context = installedApps.first?.managedObjectContext else { + let progress = self.install(apps, forceDownload: false, presentingViewController: presentingViewController, completionHandler: completionHandler) + return progress + } +} + +private extension AppManager +{ + func install(_ apps: [App], forceDownload: Bool, presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result], Error>) -> Void) -> Progress + { + let progress = Progress.discreteProgress(totalUnitCount: Int64(apps.count)) + + guard let context = apps.first?.managedObjectContext else { completionHandler(.success([:])) return progress } @@ -182,33 +162,76 @@ extension AppManager case .failure(let error): completionHandler(.failure(error)) case .success(let signer): - // Refresh + // Download context.perform { let dispatchGroup = DispatchGroup() var results = [String: Result]() - for installedApp in installedApps + let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext() + + for app in apps { - let bundleIdentifier = installedApp.bundleIdentifier - print("Refreshing App:", bundleIdentifier) + let appProgress = Progress(totalUnitCount: 100) + + let appID = app.identifier + print("Installing app:", appID) dispatchGroup.enter() - let (resignProgress, installProgress) = self.refresh(installedApp, signer: signer, presentingViewController: presentingViewController) { (result) in - print("Refreshed App: \(bundleIdentifier).", result) - results[bundleIdentifier] = result + func finishApp(_ result: Result) + { + switch result + { + case .failure(let error): print("Failed to install app \(appID).", error) + case .success: print("Installed app:", appID) + } + + results[appID] = result dispatchGroup.leave() } - let refreshProgress = Progress(totalUnitCount: 100) - refreshProgress.addChild(resignProgress, withPendingUnitCount: 20) - refreshProgress.addChild(installProgress, withPendingUnitCount: 80) + // Ensure app is downloaded. + let downloadAppOperation = DownloadAppOperation(app: app) + downloadAppOperation.useCachedAppIfAvailable = !forceDownload + downloadAppOperation.context = backgroundContext + downloadAppOperation.resultHandler = { (result) in + switch result + { + case .failure(let error): + finishApp(.failure(error)) + + case .success(let installedApp): + + // Refresh + let (resignProgress, installProgress) = self.refresh(installedApp, signer: signer, presentingViewController: presentingViewController) { (result) in + finishApp(result) + } + + if forceDownload + { + appProgress.addChild(resignProgress, withPendingUnitCount: 10) + appProgress.addChild(installProgress, withPendingUnitCount: 50) + } + else + { + appProgress.addChild(resignProgress, withPendingUnitCount: 20) + appProgress.addChild(installProgress, withPendingUnitCount: 80) + } + } + } - progress.addChild(refreshProgress, withPendingUnitCount: 1) + if forceDownload + { + appProgress.addChild(downloadAppOperation.progress, withPendingUnitCount: 40) + } + + progress.addChild(appProgress, withPendingUnitCount: 1) + + self.operationQueue.addOperation(downloadAppOperation) } dispatchGroup.notify(queue: .global()) { - context.perform { + backgroundContext.perform { completionHandler(.success(results)) } } @@ -220,10 +243,7 @@ extension AppManager return progress } -} - -private extension AppManager -{ + func refresh(_ installedApp: InstalledApp, signer: ALTSigner, presentingViewController: UIViewController?, completionHandler: @escaping (Result) -> Void) -> (Progress, Progress) { let context = installedApp.managedObjectContext diff --git a/AltStore/Operations/DownloadAppOperation.swift b/AltStore/Operations/DownloadAppOperation.swift index b0cf8eb2..a40b2651 100644 --- a/AltStore/Operations/DownloadAppOperation.swift +++ b/AltStore/Operations/DownloadAppOperation.swift @@ -15,6 +15,10 @@ import AltSign class DownloadAppOperation: ResultOperation { let app: App + + var useCachedAppIfAvailable = false + lazy var context = DatabaseManager.shared.persistentContainer.newBackgroundContext() + private let downloadURL: URL private let ipaURL: URL @@ -35,15 +39,16 @@ class DownloadAppOperation: ResultOperation { super.main() - let downloadTask = self.session.downloadTask(with: self.downloadURL) { (fileURL, response, error) in - do + func finish(error: Error?) + { + if let error = error { - let (fileURL, _) = try Result((fileURL, response), error).get() - - try FileManager.default.copyItem(at: fileURL, to: self.ipaURL, shouldReplace: true) - - DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in - let app = context.object(with: self.app.objectID) as! App + self.finish(.failure(error)) + } + else + { + self.context.perform { + let app = self.context.object(with: self.app.objectID) as! App let installedApp: InstalledApp @@ -57,16 +62,33 @@ class DownloadAppOperation: ResultOperation installedApp = InstalledApp(app: app, bundleIdentifier: app.identifier, expirationDate: Date(), - context: context) + context: self.context) } installedApp.version = app.version self.finish(.success(installedApp)) } } + } + + if self.useCachedAppIfAvailable && FileManager.default.fileExists(atPath: self.ipaURL.path) + { + finish(error: nil) + return + } + + let downloadTask = self.session.downloadTask(with: self.downloadURL) { (fileURL, response, error) in + do + { + let (fileURL, _) = try Result((fileURL, response), error).get() + + try FileManager.default.copyItem(at: fileURL, to: self.ipaURL, shouldReplace: true) + + finish(error: nil) + } catch let error { - self.finish(.failure(error)) + finish(error: error) } }