From 8946ab8a65c7928040a731c087a27059bed2d20e Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Fri, 1 Dec 2023 16:03:06 -0600 Subject: [PATCH] Downloads latest _available_ version when updating from AppViewController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Asks user to fall back to latest supported verson if version is not compatible with device’s iOS version. --- AltStore/App Detail/AppViewController.swift | 18 ++++-- AltStore/Managing Apps/AppManager.swift | 4 +- .../Operations/DownloadAppOperation.swift | 60 +++++++++++++------ 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift index 8854c56d..27c9dbee 100644 --- a/AltStore/App Detail/AppViewController.swift +++ b/AltStore/App Detail/AppViewController.swift @@ -346,13 +346,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) @@ -484,9 +492,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 { @@ -536,7 +544,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 { @@ -545,7 +553,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 126ee7c5..78d79d41 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -570,9 +570,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 5808d283..88fb04a1 100644 --- a/AltStore/Operations/DownloadAppOperation.swift +++ b/AltStore/Operations/DownloadAppOperation.swift @@ -17,7 +17,9 @@ import Roxas @objc(DownloadAppOperation) final class DownloadAppOperation: ResultOperation { - let app: AppProtocol + @Managed + private(set) var app: AppProtocol + let context: InstallAppOperationContext private let appName: String @@ -67,15 +69,36 @@ final class DownloadAppOperation: ResultOperation return self.download(self.app) } - // Verify storeApp - storeApp.managedObjectContext?.perform { - do { - let latestVersion = try self.verify(storeApp) - self.download(latestVersion) + self.$app.perform { app in + do + { + 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 @@ -86,7 +109,7 @@ final 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 @@ -114,19 +137,18 @@ final class DownloadAppOperation: ResultOperation } } -private extension DownloadAppOperation { - func verify(_ storeApp: StoreApp) throws -> AppVersion { - 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) +private extension DownloadAppOperation +{ + func verify(_ version: AppVersion) throws + { + if let minOSVersion = version.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) + { + throw VerificationError.iOSVersionNotSupported(app: version, requiredOSVersion: minOSVersion) } - if let minOSVersion = version.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) { - throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: minOSVersion) - } else if let maxOSVersion = version.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion { - throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: maxOSVersion) + else if let maxOSVersion = version.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion + { + throw VerificationError.iOSVersionNotSupported(app: version, requiredOSVersion: maxOSVersion) } - - return version } func download(@Managed _ app: AppProtocol)