diff --git a/AltStore/Operations/Errors/VerificationError.swift b/AltStore/Operations/Errors/VerificationError.swift index c0173365..d3643dc0 100644 --- a/AltStore/Operations/Errors/VerificationError.swift +++ b/AltStore/Operations/Errors/VerificationError.swift @@ -53,8 +53,8 @@ extension VerificationError VerificationError(code: .undeclaredPermissions, app: app, permissions: permissions) } - static func addedPermissions(_ permissions: [any ALTAppPermission], app: AppProtocol) -> VerificationError { - VerificationError(code: .addedPermissions, app: app, permissions: permissions) + static func addedPermissions(_ permissions: [any ALTAppPermission], appVersion: AppVersion) -> VerificationError { + VerificationError(code: .addedPermissions, app: appVersion, permissions: permissions) } } @@ -158,8 +158,26 @@ struct VerificationError: ALTLocalizedError return String(format: NSLocalizedString("%@ requires additional permissions not specified by the source.", comment: ""), appName) case .addedPermissions: - let appName = self.$app.name ?? NSLocalizedString("The app", comment: "") - return String(format: NSLocalizedString("%@ requires more permissions than the version that is already installed.", comment: ""), appName) + let appName: String + let installedVersion: String? + + if let appVersion = self.app as? AppVersion + { + let (name, version, previousVersion) = self.$app.perform { _ in (appVersion.name, appVersion.localizedVersion, appVersion.app?.installedApp?.localizedVersion) } + + appName = name + " \(version)" + installedVersion = previousVersion.map { "(\(name) \($0))" } // Include app name because it looks weird to include build # in double parentheses without it. + } + else + { + appName = self.$app.name ?? NSLocalizedString("The app", comment: "") + installedVersion = nil + } + + let baseMessage = String(format: NSLocalizedString("%@ requires more permissions than the version that is already installed", comment: ""), appName) + + let failureReason = [baseMessage, installedVersion].compactMap { $0 }.joined(separator: " ") + "." + return failureReason } } @@ -167,41 +185,54 @@ struct VerificationError: ALTLocalizedError switch self.code { case .undeclaredPermissions: - guard let permissions, !permissions.isEmpty else { return nil } + guard let permissionsDescription else { return nil } let baseMessage = NSLocalizedString("These permissions must be declared by the source in order for AltStore to install this app:", comment: "") + let recoverySuggestion = [baseMessage, permissionsDescription].joined(separator: "\n\n") + return recoverySuggestion - let permissionsByType = Dictionary(grouping: permissions) { $0.type } - let permissionSections = [ALTAppPermissionType.entitlement, .privacy, .backgroundMode].compactMap { (type) -> String? in - guard let permissions = permissionsByType[type] else { return nil } - - // "Privacy:" - var sectionText = "\(type.localizedName ?? type.rawValue):\n" - - // Sort permissions + join into single string. - let sortedList = permissions.map { permission -> String in - if let localizedName = permission.localizedName - { - // "Entitlement Name (com.apple.entitlement.name)" - return "\(localizedName) (\(permission.rawValue))" - } - else - { - // "com.apple.entitlement.name" - return permission.rawValue - } - } - .sorted { $0.localizedStandardCompare($1) == .orderedAscending } // Case-insensitive sorting - .joined(separator: "\n") - - sectionText += sortedList - return sectionText - } - - let recoverySuggestion = ([baseMessage] + permissionSections).joined(separator: "\n\n") + case .addedPermissions: + let recoverySuggestion = self.permissionsDescription return recoverySuggestion default: return nil } } } + +private extension VerificationError +{ + var permissionsDescription: String? { + guard let permissions, !permissions.isEmpty else { return nil } + + let permissionsByType = Dictionary(grouping: permissions) { $0.type } + let permissionSections = [ALTAppPermissionType.entitlement, .privacy, .backgroundMode].compactMap { (type) -> String? in + guard let permissions = permissionsByType[type] else { return nil } + + // "Privacy:" + var sectionText = "\(type.localizedName ?? type.rawValue):\n" + + // Sort permissions + join into single string. + let sortedList = permissions.map { permission -> String in + if let localizedName = permission.localizedName + { + // "Entitlement Name (com.apple.entitlement.name)" + return "\(localizedName) (\(permission.rawValue))" + } + else + { + // "com.apple.entitlement.name" + return permission.rawValue + } + } + .sorted { $0.localizedStandardCompare($1) == .orderedAscending } // Case-insensitive sorting + .joined(separator: "\n") + + sectionText += sortedList + return sectionText + } + + let permissionsDescription = permissionSections.joined(separator: "\n\n") + return permissionsDescription + } +} diff --git a/AltStore/Operations/VerifyAppOperation.swift b/AltStore/Operations/VerifyAppOperation.swift index 19801670..d900ea24 100644 --- a/AltStore/Operations/VerifyAppOperation.swift +++ b/AltStore/Operations/VerifyAppOperation.swift @@ -274,7 +274,7 @@ private extension VerifyAppOperation // Make sure all entitlements already exist in previousApp. let addedEntitlements = Array(allPermissions.lazy.compactMap { $0 as? ALTEntitlement }.filter { !previousEntitlements.contains($0) }) - guard addedEntitlements.isEmpty else { throw VerificationError.addedPermissions(addedEntitlements, app: appVersion) } + guard addedEntitlements.isEmpty else { throw VerificationError.addedPermissions(addedEntitlements, appVersion: appVersion) } } }