mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-13 00:33:28 +01:00
Displays progress when downloading/refreshing apps
Refactors download/refresh steps into separate Operation subclasses
This commit is contained in:
@@ -124,7 +124,11 @@ private extension AppDetailViewController
|
||||
|
||||
sender.isIndicatingActivity = true
|
||||
|
||||
AppManager.shared.install(self.app, presentingViewController: self) { (result) in
|
||||
let progressView = UIProgressView(progressViewStyle: .bar)
|
||||
progressView.translatesAutoresizingMaskIntoConstraints = false
|
||||
progressView.progress = 0.0
|
||||
|
||||
let progress = AppManager.shared.install(self.app, presentingViewController: self) { (result) in
|
||||
do
|
||||
{
|
||||
let installedApp = try result.get()
|
||||
@@ -138,7 +142,7 @@ private extension AppDetailViewController
|
||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
||||
}
|
||||
}
|
||||
catch AppManager.AppError.authentication(AuthenticationOperation.Error.cancelled)
|
||||
catch OperationError.cancelled
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
@@ -150,12 +154,28 @@ private extension AppDetailViewController
|
||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
UIView.animate(withDuration: 0.4, animations: {
|
||||
progressView.alpha = 0.0
|
||||
}) { _ in
|
||||
progressView.removeFromSuperview()
|
||||
}
|
||||
|
||||
sender.isIndicatingActivity = false
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
|
||||
progressView.observedProgress = progress
|
||||
|
||||
if let navigationBar = self.navigationController?.navigationBar
|
||||
{
|
||||
navigationBar.addSubview(progressView)
|
||||
|
||||
NSLayoutConstraint.activate([progressView.widthAnchor.constraint(equalTo: navigationBar.widthAnchor),
|
||||
progressView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,52 +14,10 @@ import AltKit
|
||||
|
||||
import Roxas
|
||||
|
||||
extension AppManager
|
||||
{
|
||||
enum AppError: LocalizedError
|
||||
{
|
||||
case unknown
|
||||
case missingUDID
|
||||
case noServersFound
|
||||
case missingPrivateKey
|
||||
case missingCertificate
|
||||
case notAuthenticated
|
||||
|
||||
case multipleCertificates
|
||||
case multipleTeams
|
||||
|
||||
case download(URLError)
|
||||
case authentication(Error)
|
||||
case fetchingSigningResources(Error)
|
||||
case prepare(Error)
|
||||
case install(Error)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self
|
||||
{
|
||||
case .unknown: return "An unknown error occured."
|
||||
case .missingUDID: return "The UDID for this device is unknown."
|
||||
case .noServersFound: return "An active AltServer could not be found."
|
||||
case .missingPrivateKey: return "A valid private key must be provided."
|
||||
case .missingCertificate: return "A valid certificate must be provided."
|
||||
case .notAuthenticated: return "You must be logged in with your Apple ID to install apps."
|
||||
case .multipleCertificates: return "You must select a certificate to use to install apps."
|
||||
case .multipleTeams: return "You must select a team to use to install apps."
|
||||
case .download(let error): return error.localizedDescription
|
||||
case .authentication(let error): return error.localizedDescription
|
||||
case .fetchingSigningResources(let error): return error.localizedDescription
|
||||
case .prepare(let error): return error.localizedDescription
|
||||
case .install(let error): return error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppManager
|
||||
{
|
||||
static let shared = AppManager()
|
||||
|
||||
private let session = URLSession(configuration: .default)
|
||||
private let operationQueue = OperationQueue()
|
||||
|
||||
private init()
|
||||
@@ -107,7 +65,7 @@ extension AppManager
|
||||
#endif
|
||||
}
|
||||
|
||||
func authenticate(presentingViewController: UIViewController?, completionHandler: @escaping (Result<(ALTTeam, ALTCertificate), Error>) -> Void)
|
||||
func authenticate(presentingViewController: UIViewController?, completionHandler: @escaping (Result<ALTSigner, Error>) -> Void)
|
||||
{
|
||||
let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController)
|
||||
authenticationOperation.resultHandler = { (result) in
|
||||
@@ -119,91 +77,60 @@ extension AppManager
|
||||
|
||||
extension AppManager
|
||||
{
|
||||
func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result<InstalledApp, AppError>) -> Void)
|
||||
func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
||||
{
|
||||
let ipaURL = InstalledApp.ipaURL(for: app)
|
||||
let progress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
|
||||
let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.InstallApp")
|
||||
|
||||
func finish(_ result: Result<InstalledApp, AppError>)
|
||||
{
|
||||
completionHandler(result)
|
||||
|
||||
RSTEndBackgroundTask(backgroundTaskID)
|
||||
}
|
||||
|
||||
// Download app
|
||||
self.downloadApp(from: app.downloadURL) { (result) in
|
||||
let result = result.flatMap { (fileURL) -> Result<Void, URLError> in
|
||||
// Copy downloaded app to proper location
|
||||
let result = Result { try FileManager.default.copyItem(at: fileURL, to: ipaURL, shouldReplace: true) }
|
||||
return result.mapError { _ in URLError(.cannotWriteToFile) }
|
||||
}
|
||||
|
||||
// Authenticate
|
||||
let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController)
|
||||
authenticationOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.download(error)))
|
||||
case .success:
|
||||
// Authenticate
|
||||
self.authenticate(presentingViewController: presentingViewController) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.authentication(error)))
|
||||
case .success(let team, let certificate):
|
||||
|
||||
// Fetch provisioning profile
|
||||
self.prepareProvisioningProfile(for: app, team: team) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.fetchingSigningResources(error)))
|
||||
case .success(let profile):
|
||||
|
||||
// Prepare app
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let app = context.object(with: app.objectID) as! App
|
||||
|
||||
let installedApp = InstalledApp(app: app,
|
||||
bundleIdentifier: profile.appID.bundleIdentifier,
|
||||
expirationDate: profile.expirationDate,
|
||||
context: context)
|
||||
|
||||
let signer = ALTSigner(team: team, certificate: certificate)
|
||||
self.prepare(installedApp, provisioningProfile: profile, signer: signer) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.prepare(error)))
|
||||
case .success(let resignedURL):
|
||||
|
||||
// Send app to server
|
||||
context.perform {
|
||||
self.sendAppToServer(fileURL: resignedURL, identifier: installedApp.bundleIdentifier) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.install(error)))
|
||||
case .success:
|
||||
context.perform {
|
||||
finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
progress.addChild(authenticationOperation.progress, withPendingUnitCount: 5)
|
||||
self.operationQueue.addOperation(authenticationOperation)
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
func refresh(_ app: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
|
||||
func refresh(_ app: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
||||
{
|
||||
self.refresh([app], presentingViewController: presentingViewController) { (result) in
|
||||
return self.refresh([app], presentingViewController: presentingViewController) { (result) in
|
||||
do
|
||||
{
|
||||
guard let (_, result) = try result.get().first else { throw AppError.unknown }
|
||||
guard let (_, result) = try result.get().first else { throw OperationError.unknown }
|
||||
completionHandler(result)
|
||||
}
|
||||
catch
|
||||
@@ -213,303 +140,101 @@ extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
func refreshAllApps(presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], AppError>) -> Void)
|
||||
@discardableResult func refresh(_ installedApps: [InstalledApp], presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void) -> Progress
|
||||
{
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
do
|
||||
{
|
||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
||||
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)]
|
||||
|
||||
let installedApps = try context.fetch(fetchRequest)
|
||||
self.refresh(installedApps, presentingViewController: presentingViewController) { (result) in
|
||||
context.perform { // keep context alive
|
||||
completionHandler(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(.prepare(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refresh<T: Collection>(_ installedApps: T, presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], AppError>) -> Void) where T.Element == InstalledApp
|
||||
{
|
||||
let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.RefreshApps")
|
||||
let progress = Progress.discreteProgress(totalUnitCount: Int64(installedApps.count))
|
||||
|
||||
func finish(_ result: Result<[String: Result<InstalledApp, Error>], AppError>)
|
||||
{
|
||||
completionHandler(result)
|
||||
|
||||
RSTEndBackgroundTask(backgroundTaskID)
|
||||
guard let context = installedApps.first?.managedObjectContext else {
|
||||
completionHandler(.success([:]))
|
||||
return progress
|
||||
}
|
||||
|
||||
guard !ServerManager.shared.discoveredServers.isEmpty else { return finish(.failure(.noServersFound)) }
|
||||
|
||||
// Authenticate
|
||||
self.authenticate(presentingViewController: presentingViewController) { (result) in
|
||||
let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController)
|
||||
authenticationOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.authentication(error)))
|
||||
case .success(let team, let certificate):
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success(let signer):
|
||||
|
||||
// Sign
|
||||
let signer = ALTSigner(team: team, certificate: certificate)
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
var results = [String: Result<InstalledApp, Error>]()
|
||||
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
|
||||
for app in installedApps
|
||||
{
|
||||
dispatchGroup.enter()
|
||||
// Refresh
|
||||
context.perform {
|
||||
let dispatchGroup = DispatchGroup()
|
||||
var results = [String: Result<InstalledApp, Error>]()
|
||||
|
||||
app.managedObjectContext?.perform {
|
||||
let bundleIdentifier = app.bundleIdentifier
|
||||
for installedApp in installedApps
|
||||
{
|
||||
let bundleIdentifier = installedApp.bundleIdentifier
|
||||
print("Refreshing App:", bundleIdentifier)
|
||||
|
||||
self.refresh(app, signer: signer, context: context) { (result) in
|
||||
dispatchGroup.enter()
|
||||
|
||||
let (resignProgress, installProgress) = self.refresh(installedApp, signer: signer, presentingViewController: presentingViewController) { (result) in
|
||||
print("Refreshed App: \(bundleIdentifier).", result)
|
||||
results[bundleIdentifier] = result
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
|
||||
let refreshProgress = Progress(totalUnitCount: 100)
|
||||
refreshProgress.addChild(resignProgress, withPendingUnitCount: 20)
|
||||
refreshProgress.addChild(installProgress, withPendingUnitCount: 80)
|
||||
|
||||
progress.addChild(refreshProgress, withPendingUnitCount: 1)
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .global()) {
|
||||
context.perform {
|
||||
finish(.success(results))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppManager
|
||||
{
|
||||
func downloadApp(from url: URL, completionHandler: @escaping (Result<URL, URLError>) -> Void)
|
||||
{
|
||||
let downloadTask = self.session.downloadTask(with: url) { (fileURL, response, error) in
|
||||
do
|
||||
{
|
||||
let (fileURL, _) = try Result((fileURL, response), error).get()
|
||||
completionHandler(.success(fileURL))
|
||||
}
|
||||
catch let error as URLError
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(URLError(.unknown)))
|
||||
}
|
||||
}
|
||||
|
||||
downloadTask.resume()
|
||||
}
|
||||
|
||||
func prepareProvisioningProfile(for app: App, team: ALTTeam, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
|
||||
{
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return completionHandler(.failure(AppError.missingUDID)) }
|
||||
|
||||
let device = ALTDevice(name: UIDevice.current.name, identifier: udid)
|
||||
self.register(device, team: team) { (result) in
|
||||
do
|
||||
{
|
||||
_ = try result.get()
|
||||
|
||||
app.managedObjectContext?.perform {
|
||||
self.register(app, with: team) { (result) in
|
||||
do
|
||||
{
|
||||
let appID = try result.get()
|
||||
self.fetchProvisioningProfile(for: appID, team: team) { (result) in
|
||||
do
|
||||
{
|
||||
let provisioningProfile = try result.get()
|
||||
completionHandler(.success(provisioningProfile))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepare(_ installedApp: InstalledApp, provisioningProfile: ALTProvisioningProfile, signer: ALTSigner, completionHandler: @escaping (Result<URL, Error>) -> Void)
|
||||
{
|
||||
do
|
||||
{
|
||||
let refreshedAppDirectory = installedApp.directoryURL.appendingPathComponent("Refreshed", isDirectory: true)
|
||||
|
||||
if FileManager.default.fileExists(atPath: refreshedAppDirectory.path)
|
||||
{
|
||||
try FileManager.default.removeItem(at: refreshedAppDirectory)
|
||||
}
|
||||
try FileManager.default.createDirectory(at: refreshedAppDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
let appBundleURL = try FileManager.default.unzipAppBundle(at: installedApp.ipaURL, toDirectory: refreshedAppDirectory)
|
||||
guard let bundle = Bundle(url: appBundleURL) else { throw ALTError(.missingAppBundle) }
|
||||
|
||||
guard var infoDictionary = NSDictionary(contentsOf: bundle.infoPlistURL) as? [String: Any] else { throw ALTError(.missingInfoPlist) }
|
||||
|
||||
var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? []
|
||||
|
||||
let altstoreURLScheme = ["CFBundleTypeRole": "Editor",
|
||||
"CFBundleURLName": installedApp.bundleIdentifier,
|
||||
"CFBundleURLSchemes": [installedApp.openAppURL.scheme!]] as [String : Any]
|
||||
allURLSchemes.append(altstoreURLScheme)
|
||||
|
||||
infoDictionary[Bundle.Info.urlTypes] = allURLSchemes
|
||||
|
||||
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
|
||||
|
||||
signer.signApp(at: appBundleURL, provisioningProfile: provisioningProfile) { (success, error) in
|
||||
do
|
||||
{
|
||||
try Result(success, error).get()
|
||||
|
||||
let resignedURL = try FileManager.default.zipAppBundle(at: appBundleURL)
|
||||
completionHandler(.success(resignedURL))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
dispatchGroup.notify(queue: .global()) {
|
||||
context.perform {
|
||||
completionHandler(.success(results))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
func sendAppToServer(fileURL: URL, identifier: String, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
guard let server = ServerManager.shared.discoveredServers.first else { return completionHandler(.failure(AppError.noServersFound)) }
|
||||
|
||||
server.installApp(at: fileURL, identifier: identifier) { (result) in
|
||||
let result = result.mapError { $0 as Error }
|
||||
completionHandler(result)
|
||||
}
|
||||
self.operationQueue.addOperation(authenticationOperation)
|
||||
|
||||
return progress
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppManager
|
||||
{
|
||||
func register(_ device: ALTDevice, team: ALTTeam, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||
func refresh(_ installedApp: InstalledApp, signer: ALTSigner, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> (Progress, Progress)
|
||||
{
|
||||
ALTAppleAPI.shared.fetchDevices(for: team) { (devices, error) in
|
||||
do
|
||||
{
|
||||
let devices = try Result(devices, error).get()
|
||||
|
||||
if let device = devices.first(where: { $0.identifier == device.identifier })
|
||||
{
|
||||
completionHandler(.success(device))
|
||||
}
|
||||
else
|
||||
{
|
||||
ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, team: team) { (device, error) in
|
||||
completionHandler(Result(device, error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func register(_ app: App, with team: ALTTeam, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
||||
{
|
||||
let appName = app.name
|
||||
let bundleID = "com.\(team.identifier).\(app.identifier)"
|
||||
let context = installedApp.managedObjectContext
|
||||
|
||||
ALTAppleAPI.shared.fetchAppIDs(for: team) { (appIDs, error) in
|
||||
do
|
||||
{
|
||||
let appIDs = try Result(appIDs, error).get()
|
||||
|
||||
if let appID = appIDs.first(where: { $0.bundleIdentifier == bundleID })
|
||||
{
|
||||
completionHandler(.success(appID))
|
||||
}
|
||||
else
|
||||
{
|
||||
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team) { (appID, error) in
|
||||
completionHandler(Result(appID, error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
let resignAppOperation = ResignAppOperation(installedApp: installedApp)
|
||||
let installAppOperation = InstallAppOperation()
|
||||
|
||||
// Resign
|
||||
resignAppOperation.signer = signer
|
||||
resignAppOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
installAppOperation.cancel()
|
||||
completionHandler(.failure(error))
|
||||
|
||||
case .success(let resignedURL):
|
||||
installAppOperation.fileURL = resignedURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
|
||||
{
|
||||
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team) { (profile, error) in
|
||||
completionHandler(Result(profile, error))
|
||||
}
|
||||
}
|
||||
|
||||
func refresh(_ installedApp: InstalledApp, signer: ALTSigner, context: NSManagedObjectContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
|
||||
{
|
||||
self.prepareProvisioningProfile(for: installedApp.app, team: signer.team) { (result) in
|
||||
|
||||
// Install
|
||||
installAppOperation.addDependency(resignAppOperation)
|
||||
installAppOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success(let profile):
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
self.prepare(installedApp, provisioningProfile: profile, signer: signer) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success(let resignedURL):
|
||||
|
||||
// Send app to server
|
||||
installedApp.managedObjectContext?.perform {
|
||||
self.sendAppToServer(fileURL: resignedURL, identifier: installedApp.bundleIdentifier) { (result) in
|
||||
context.perform {
|
||||
switch result
|
||||
{
|
||||
case .success:
|
||||
let installedApp = context.object(with: installedApp.objectID) as! InstalledApp
|
||||
installedApp.expirationDate = profile.expirationDate
|
||||
completionHandler(.success(installedApp))
|
||||
|
||||
case .failure(let error):
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case .success:
|
||||
context?.perform {
|
||||
completionHandler(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.operationQueue.addOperations([resignAppOperation, installAppOperation], waitUntilFinished: false)
|
||||
|
||||
return (resignAppOperation.progress, installAppOperation.progress)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user