[AltStore] Ensures apps are downloaded before attempting to refresh

This commit is contained in:
Riley Testut
2019-06-18 18:31:59 -07:00
parent 3ac663fbf1
commit 9d3eda9526
2 changed files with 109 additions and 67 deletions

View File

@@ -104,48 +104,17 @@ extension AppManager
{ {
func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
{ {
let progress = Progress.discreteProgress(totalUnitCount: 100) let progress = self.install([app], forceDownload: true, presentingViewController: presentingViewController) { (result) in
do
// Authenticate
let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController)
authenticationOperation.resultHandler = { (result) in
switch result
{ {
case .failure(let error): completionHandler(.failure(error)) guard let (_, result) = try result.get().first else { throw OperationError.unknown }
case .success(let signer): completionHandler(result)
}
// Download catch
app.managedObjectContext?.perform { {
let downloadAppOperation = DownloadAppOperation(app: app) completionHandler(.failure(error))
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)
}
} }
} }
progress.addChild(authenticationOperation.progress, withPendingUnitCount: 5)
self.operationQueue.addOperation(authenticationOperation)
return progress return progress
} }
@@ -167,9 +136,20 @@ extension AppManager
@discardableResult func refresh(_ installedApps: [InstalledApp], presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void) -> Progress @discardableResult func refresh(_ installedApps: [InstalledApp], presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], 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<InstalledApp, Error>], Error>) -> Void) -> Progress
{
let progress = Progress.discreteProgress(totalUnitCount: Int64(apps.count))
guard let context = apps.first?.managedObjectContext else {
completionHandler(.success([:])) completionHandler(.success([:]))
return progress return progress
} }
@@ -182,33 +162,76 @@ extension AppManager
case .failure(let error): completionHandler(.failure(error)) case .failure(let error): completionHandler(.failure(error))
case .success(let signer): case .success(let signer):
// Refresh // Download
context.perform { context.perform {
let dispatchGroup = DispatchGroup() let dispatchGroup = DispatchGroup()
var results = [String: Result<InstalledApp, Error>]() var results = [String: Result<InstalledApp, Error>]()
for installedApp in installedApps let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
for app in apps
{ {
let bundleIdentifier = installedApp.bundleIdentifier let appProgress = Progress(totalUnitCount: 100)
print("Refreshing App:", bundleIdentifier)
let appID = app.identifier
print("Installing app:", appID)
dispatchGroup.enter() dispatchGroup.enter()
let (resignProgress, installProgress) = self.refresh(installedApp, signer: signer, presentingViewController: presentingViewController) { (result) in func finishApp(_ result: Result<InstalledApp, Error>)
print("Refreshed App: \(bundleIdentifier).", result) {
results[bundleIdentifier] = 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() dispatchGroup.leave()
} }
let refreshProgress = Progress(totalUnitCount: 100) // Ensure app is downloaded.
refreshProgress.addChild(resignProgress, withPendingUnitCount: 20) let downloadAppOperation = DownloadAppOperation(app: app)
refreshProgress.addChild(installProgress, withPendingUnitCount: 80) downloadAppOperation.useCachedAppIfAvailable = !forceDownload
downloadAppOperation.context = backgroundContext
downloadAppOperation.resultHandler = { (result) in
switch result
{
case .failure(let error):
finishApp(.failure(error))
progress.addChild(refreshProgress, withPendingUnitCount: 1) 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)
}
}
}
if forceDownload
{
appProgress.addChild(downloadAppOperation.progress, withPendingUnitCount: 40)
}
progress.addChild(appProgress, withPendingUnitCount: 1)
self.operationQueue.addOperation(downloadAppOperation)
} }
dispatchGroup.notify(queue: .global()) { dispatchGroup.notify(queue: .global()) {
context.perform { backgroundContext.perform {
completionHandler(.success(results)) completionHandler(.success(results))
} }
} }
@@ -220,10 +243,7 @@ extension AppManager
return progress return progress
} }
}
private extension AppManager
{
func refresh(_ installedApp: InstalledApp, signer: ALTSigner, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> (Progress, Progress) func refresh(_ installedApp: InstalledApp, signer: ALTSigner, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> (Progress, Progress)
{ {
let context = installedApp.managedObjectContext let context = installedApp.managedObjectContext

View File

@@ -15,6 +15,10 @@ import AltSign
class DownloadAppOperation: ResultOperation<InstalledApp> class DownloadAppOperation: ResultOperation<InstalledApp>
{ {
let app: App let app: App
var useCachedAppIfAvailable = false
lazy var context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
private let downloadURL: URL private let downloadURL: URL
private let ipaURL: URL private let ipaURL: URL
@@ -35,15 +39,16 @@ class DownloadAppOperation: ResultOperation<InstalledApp>
{ {
super.main() super.main()
let downloadTask = self.session.downloadTask(with: self.downloadURL) { (fileURL, response, error) in func finish(error: Error?)
do {
if let error = error
{ {
let (fileURL, _) = try Result((fileURL, response), error).get() self.finish(.failure(error))
}
try FileManager.default.copyItem(at: fileURL, to: self.ipaURL, shouldReplace: true) else
{
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in self.context.perform {
let app = context.object(with: self.app.objectID) as! App let app = self.context.object(with: self.app.objectID) as! App
let installedApp: InstalledApp let installedApp: InstalledApp
@@ -57,16 +62,33 @@ class DownloadAppOperation: ResultOperation<InstalledApp>
installedApp = InstalledApp(app: app, installedApp = InstalledApp(app: app,
bundleIdentifier: app.identifier, bundleIdentifier: app.identifier,
expirationDate: Date(), expirationDate: Date(),
context: context) context: self.context)
} }
installedApp.version = app.version installedApp.version = app.version
self.finish(.success(installedApp)) 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 catch let error
{ {
self.finish(.failure(error)) finish(error: error)
} }
} }