diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift index a3fc871b..cca50224 100644 --- a/AltStore/App Detail/AppViewController.swift +++ b/AltStore/App Detail/AppViewController.swift @@ -360,13 +360,21 @@ private extension AppViewController { func update() { + var buttonAction: AppBannerView.AppAction? + + if let installedApp = self.app.installedApp, let latestVersion = self.app.latestAvailableVersion, !installedApp.matches(latestVersion) + { + // Explicitly set button action to .update if there is an update available, even if it's not supported. + buttonAction = .update + } + for button in [self.bannerView.button!, self.navigationBarDownloadButton!] { button.tintColor = self.app.tintColor button.isIndicatingActivity = false } - self.bannerView.configure(for: self.app) + self.bannerView.configure(for: self.app, action: buttonAction) let title = self.bannerView.button.title(for: .normal) self.navigationBarDownloadButton.setTitle(title, for: .normal) @@ -498,9 +506,9 @@ extension AppViewController { if let installedApp = self.app.installedApp { - if let latestVersion = self.app.latestSupportedVersion, !installedApp.matches(latestVersion) + if let latestVersion = self.app.latestAvailableVersion, !installedApp.matches(latestVersion) { - self.updateApp(installedApp) + self.updateApp(installedApp, to: latestVersion) } else { @@ -551,7 +559,7 @@ extension AppViewController UIApplication.shared.open(installedApp.openAppURL) } - func updateApp(_ installedApp: InstalledApp) + func updateApp(_ installedApp: InstalledApp, to version: AppVersion) { let previousProgress = AppManager.shared.installationProgress(for: installedApp) guard previousProgress == nil else { @@ -560,7 +568,7 @@ extension AppViewController return } - _ = AppManager.shared.update(installedApp, presentingViewController: self) { (result) in + AppManager.shared.update(installedApp, to: version, presentingViewController: self) { (result) in DispatchQueue.main.async { switch result { diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 0218518f..48bda993 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -557,9 +557,9 @@ extension AppManager } @discardableResult - func update(_ installedApp: InstalledApp, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result) -> Void) -> Progress + func update(_ installedApp: InstalledApp, to version: AppVersion? = nil, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result) -> Void) -> Progress { - guard let appVersion = installedApp.storeApp?.latestSupportedVersion else { + guard let appVersion = version ?? installedApp.storeApp?.latestSupportedVersion else { completionHandler(.failure(OperationError.appNotFound(name: installedApp.name))) return Progress.discreteProgress(totalUnitCount: 1) } diff --git a/AltStore/Operations/DownloadAppOperation.swift b/AltStore/Operations/DownloadAppOperation.swift index ef9c474f..39dccba5 100644 --- a/AltStore/Operations/DownloadAppOperation.swift +++ b/AltStore/Operations/DownloadAppOperation.swift @@ -17,7 +17,9 @@ import Roxas @objc(DownloadAppOperation) class DownloadAppOperation: ResultOperation { - let app: AppProtocol + @Managed + private(set) var app: AppProtocol + let context: InstallAppOperationContext private let appName: String @@ -59,22 +61,36 @@ class DownloadAppOperation: ResultOperation // Set _after_ checking self.context.error to prevent overwriting localized failure for previous errors. self.localizedFailure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), self.appName) - guard let storeApp = self.app as? StoreApp else { - // Only StoreApp allows falling back to previous versions. - // AppVersion can only install itself, and ALTApplication doesn't have previous versions. - return self.download(self.app) - } - - // Verify storeApp - storeApp.managedObjectContext?.perform { + self.$app.perform { app in do { - let latestVersion = try self.verify(storeApp) - self.download(latestVersion) + var appVersion: AppVersion? + + if let version = app as? AppVersion + { + appVersion = version + } + else if let storeApp = app as? StoreApp + { + guard let latestVersion = storeApp.latestAvailableVersion else { + let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName) + throw OperationError.unknown(failureReason: failureReason) + } + + // Attempt to download latest _available_ version, and fall back to older versions if necessary. + appVersion = latestVersion + } + + if let appVersion + { + try self.verify(appVersion) + } + + self.download(appVersion ?? app) } catch let error as VerificationError where error.code == .iOSVersionNotSupported { - guard let presentingViewController = self.context.presentingViewController, let latestSupportedVersion = storeApp.latestSupportedVersion + guard let presentingViewController = self.context.presentingViewController, let storeApp = app.storeApp, let latestSupportedVersion = storeApp.latestSupportedVersion else { return self.finish(.failure(error)) } if let installedApp = storeApp.installedApp @@ -85,7 +101,7 @@ class DownloadAppOperation: ResultOperation let title = NSLocalizedString("Unsupported iOS Version", comment: "") let message = error.localizedDescription + "\n\n" + NSLocalizedString("Would you like to download the last version compatible with this device instead?", comment: "") let localizedVersion = latestSupportedVersion.localizedVersion - + DispatchQueue.main.async { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { _ in @@ -121,23 +137,16 @@ class DownloadAppOperation: ResultOperation private extension DownloadAppOperation { - func verify(_ storeApp: StoreApp) throws -> AppVersion + func verify(_ version: AppVersion) throws { - guard let version = storeApp.latestAvailableVersion else { - let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName) - throw OperationError.unknown(failureReason: failureReason) - } - if let minOSVersion = version.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) { - throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: minOSVersion) + throw VerificationError.iOSVersionNotSupported(app: version, requiredOSVersion: minOSVersion) } else if let maxOSVersion = version.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion { - throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: maxOSVersion) + throw VerificationError.iOSVersionNotSupported(app: version, requiredOSVersion: maxOSVersion) } - - return version } func download(@Managed _ app: AppProtocol)