[removeExtensions]: Bug-Fix: 1. existing App is ALTApplication not InstalledApp - corrected this 2. process as background mode if prompt can't be made or else signal error if operation context changed in-flight

This commit is contained in:
Magesh K
2025-01-13 07:20:44 +05:30
parent dc4a543b3b
commit e8798499d3
2 changed files with 97 additions and 45 deletions

View File

@@ -58,6 +58,8 @@ extension OperationError
case anisetteV3Error//(message: String) case anisetteV3Error//(message: String)
case cacheClearError//(errors: [String]) case cacheClearError//(errors: [String])
case noWiFi case noWiFi
case invalidOperationContext
} }
static var cancelled: CancellationError { CancellationError() } static var cancelled: CancellationError { CancellationError() }
@@ -130,6 +132,10 @@ extension OperationError
OperationError(code: .invalidParameters, failureReason: message) OperationError(code: .invalidParameters, failureReason: message)
} }
static func invalidOperationContext(_ message: String? = nil) -> OperationError {
OperationError(code: .invalidOperationContext, failureReason: message)
}
static func forbidden(failureReason: String? = nil, file: String = #fileID, line: UInt = #line) -> OperationError { static func forbidden(failureReason: String? = nil, file: String = #fileID, line: UInt = #line) -> OperationError {
OperationError(code: .forbidden, failureReason: failureReason, sourceFile: file, sourceLine: line) OperationError(code: .forbidden, failureReason: failureReason, sourceFile: file, sourceLine: line)
} }
@@ -232,7 +238,10 @@ struct OperationError: ALTLocalizedError {
case .invalidParameters: case .invalidParameters:
let message = self._failureReason.map { ": \n\($0)" } ?? "." let message = self._failureReason.map { ": \n\($0)" } ?? "."
return String(format: NSLocalizedString("Invalid parameters%@", comment: ""), message) return String(format: NSLocalizedString("Invalid parameters\n%@", comment: ""), message)
case .invalidOperationContext:
let message = self._failureReason.map { ": \n\($0)" } ?? "."
return String(format: NSLocalizedString("Invalid Operation Context\n%@", comment: ""), message)
case .serverNotFound: return NSLocalizedString("AltServer could not be found.", comment: "") case .serverNotFound: return NSLocalizedString("AltServer could not be found.", comment: "")
case .connectionFailed: return NSLocalizedString("A connection to AltServer could not be established.", comment: "") case .connectionFailed: return NSLocalizedString("A connection to AltServer could not be established.", comment: "")
case .connectionDropped: return NSLocalizedString("The connection to AltServer was dropped.", comment: "") case .connectionDropped: return NSLocalizedString("The connection to AltServer was dropped.", comment: "")

View File

@@ -41,8 +41,9 @@ final class RemoveAppExtensionsOperation: ResultOperation<Void>
)) ))
} }
self.removeAppExtensions(from: targetAppBundle, self.removeAppExtensions(from: targetAppBundle,
appInDatabase: appInDatabase as? InstalledApp, appInDatabase: appInDatabase as? ALTApplication,
extensions: targetAppBundle.appExtensions, extensions: targetAppBundle.appExtensions,
context.authenticatedContext.presentingViewController) context.authenticatedContext.presentingViewController)
@@ -59,9 +60,9 @@ final class RemoveAppExtensionsOperation: ResultOperation<Void>
private func removeAppExtensions(from targetAppBundle: ALTApplication, private func removeAppExtensions(from targetAppBundle: ALTApplication,
appInDatabase: InstalledApp?, appInDatabase: ALTApplication?,
extensions: Set<ALTApplication>, extensions: Set<ALTApplication>,
_ presentingViewController: UIViewController?) _ presentingViewController: UIViewController?)
{ {
// target App Bundle doesn't contain extensions so don't bother // target App Bundle doesn't contain extensions so don't bother
@@ -69,47 +70,39 @@ final class RemoveAppExtensionsOperation: ResultOperation<Void>
return self.finish(.success(())) return self.finish(.success(()))
} }
//App-Extensions: Ensure existing app's extensions in DB and currently installing app bundle's extensions must match // process extensionsInfo
let existingAppEx: Set<InstalledExtension> = appInDatabase?.appExtensions ?? Set() let excessExtensions = processExtensionsInfo(from: targetAppBundle, appInDatabase: appInDatabase)
let targetAppEx: Set<ALTApplication> = targetAppBundle.appExtensions
DispatchQueue.main.async {
let existingAppExNames = existingAppEx.map{ appEx in appEx.bundleIdentifier} guard let presentingViewController: UIViewController = presentingViewController,
let targetAppExNames = targetAppEx.map{ appEx in appEx.bundleIdentifier} presentingViewController.viewIfLoaded?.window != nil else {
// background mode: remove only the excess extensions automatically for re-installs
let excessExtensionsInTargetApp = targetAppEx.filter{ // keep all extensions for fresh install (appInDatabase = nil)
!(existingAppExNames.contains($0.bundleIdentifier)) return self.backgroundModeExtensionsCleanup(excessExtensions: excessExtensions)
} }
let necessaryExtensionsInExistingApp = existingAppEx.filter{ // present prompt to the user if we have a view context
targetAppExNames.contains($0.bundleIdentifier) let alertController = self.createAlertDialog(from: targetAppBundle, extensions: extensions, presentingViewController)
} presentingViewController.present(alertController, animated: true){
// always cleanup existing app (app-in-db) based on incoming app that is targeted for install // if for any reason the view wasn't presented, then just signal that as error
appInDatabase?.appExtensions = necessaryExtensionsInExistingApp if presentingViewController.presentedViewController == nil {
let errMsg = "RemoveAppExtensionsOperation: unable to present dialog, view context not available." +
let isMatching = (targetAppEx.count == existingAppEx.count) && excessExtensionsInTargetApp.isEmpty "\nDid you move to different screen or background after starting the operation?"
let diagnosticsMsg = "RemoveAppExtensionsOperation: App Extensions in existingApp and targetAppBundle are matching: \(isMatching)\n" self.finish(.failure(
+ "RemoveAppExtensionsOperation: existingAppEx: \(existingAppExNames); targetAppBundleEx: \(String(describing: targetAppExNames))\n" OperationError.invalidOperationContext(errMsg)
print(diagnosticsMsg) ))
}
// if background mode, then remove only the excess extensions
guard let presentingViewController: UIViewController = presentingViewController else {
// perform silent extensions cleanup for those that aren't already present in existing app
print("\n Performing background mode Extensions removal \n")
print("RemoveAppExtensionsOperation: Excess Extensions In TargetAppBundle: \(excessExtensionsInTargetApp)")
print("RemoveAppExtensionsOperation: Necessary Extensions In ExistingAppInDatabase: \(necessaryExtensionsInExistingApp)")
do {
try Self.removeExtensions(from: excessExtensionsInTargetApp)
return self.finish(.success(()))
} catch {
return self.finish(.failure(error))
} }
} }
}
private func createAlertDialog(from targetAppBundle: ALTApplication,
extensions: Set<ALTApplication>,
_ presentingViewController: UIViewController) -> UIAlertController
{
/// Foreground prompt:
let firstSentence: String let firstSentence: String
if UserDefaults.standard.activeAppLimitIncludesExtensions if UserDefaults.standard.activeAppLimitIncludesExtensions
@@ -175,8 +168,58 @@ final class RemoveAppExtensionsOperation: ResultOperation<Void>
} }
}) })
DispatchQueue.main.async { return alertController
presentingViewController.present(alertController, animated: true) }
struct ExtensionsInfo{
let excessInTarget: Set<ALTApplication>
let necessaryInExisting: Set<ALTApplication>
}
private func processExtensionsInfo(from targetAppBundle: ALTApplication,
appInDatabase: ALTApplication?) -> Set<ALTApplication>
{
//App-Extensions: Ensure existing app's extensions in DB and currently installing app bundle's extensions must match
let targetAppEx: Set<ALTApplication> = targetAppBundle.appExtensions
let targetAppExNames = targetAppEx.map{ appEx in appEx.bundleIdentifier}
guard let extensionsInExistingApp = appInDatabase?.appExtensions else {
let diagnosticsMsg = "RemoveAppExtensionsOperation: ExistingApp is nil, Hence keeping all app extensions from targetAppBundle"
+ "RemoveAppExtensionsOperation: ExistingAppEx: nil; targetAppBundleEx: \(targetAppExNames)"
print(diagnosticsMsg)
return Set() // nothing is excess since we are keeping all, so returning empty
}
let existingAppEx: Set<ALTApplication> = extensionsInExistingApp
let existingAppExNames = existingAppEx.map{ appEx in appEx.bundleIdentifier}
let excessExtensionsInTargetApp = targetAppEx.filter{
!(existingAppExNames.contains($0.bundleIdentifier))
}
let excessExtensionsInExistingApp = existingAppEx.filter{
!(targetAppExNames.contains($0.bundleIdentifier))
}
let isMatching = (targetAppEx.count == existingAppEx.count) && excessExtensionsInTargetApp.isEmpty
let diagnosticsMsg = "RemoveAppExtensionsOperation: App Extensions in existingApp and targetAppBundle are matching: \(isMatching)\n"
+ "RemoveAppExtensionsOperation: existingAppEx: \(existingAppExNames); targetAppBundleEx: \(String(describing: targetAppExNames))\n"
print(diagnosticsMsg)
return excessExtensionsInTargetApp
}
private func backgroundModeExtensionsCleanup(excessExtensions: Set<ALTApplication>) {
// perform silent extensions cleanup for those that aren't already present in existing app
print("\n Performing background mode Extensions removal \n")
print("RemoveAppExtensionsOperation: Excess Extensions In TargetAppBundle: \(excessExtensions.map{$0.bundleIdentifier})")
do {
try Self.removeExtensions(from: excessExtensions)
return self.finish(.success(()))
} catch {
return self.finish(.failure(error))
} }
} }
} }