mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-13 08:43:27 +01:00
[Shared] Refactors error handling based on ALTLocalizedError protocol (#1115)
* [Shared] Revises ALTLocalizedError protocol * Refactors errors to conform to revised ALTLocalizedError protocol * [Missing Commit] Remaining changes for ALTLocalizedError * [AltServer] Refactors errors to conform to revised ALTLocalizedError protocol * [Missing Commit] Declares ALTLocalizedTitleErrorKey + ALTLocalizedDescriptionKey * Updates Objective-C errors to match revised ALTLocalizedError * [Missing Commit] Unnecessary ALTLocalizedDescription logic * [Shared] Refactors NSError.withLocalizedFailure to properly support ALTLocalizedError * [Shared] Supports adding localized titles to errors via NSError.withLocalizedTitle() * Revises ErrorResponse logic to support arbitrary errors and user info values * [Missed Commit] Renames CodableServerError to CodableError * Merges ConnectionError into OperationError * [Missed Commit] Doesn’t check ALTWrappedError’s userInfo for localizedDescription * [Missed] Fixes incorrect errorDomain for ALTErrorEnums * [Missed] Removes nonexistent ALTWrappedError.h * Includes source file and line number in OperationError.unknown failureReason * Adds localizedTitle to AppManager operation errors * Fixes adding localizedTitle + localizedFailure to ALTWrappedError * Updates ToastView to use error’s localizedTitle as title * [Shared] Adds NSError.formattedDetailedDescription(with:) Returns formatted NSAttributedString containing all user info values intended for displaying to the user. * [Shared] Updates Error.localizedErrorCode to say “code” instead of “error” * Conforms ALTLocalizedError to CustomStringConvertible * Adds “View More Details” option to Error Log context menu to view detailed error description * [Shared] Fixes NSError.formattedDetailedDescription appearing black in dark mode * [AltServer] Updates error alert to match revised error logic Uses error’s localizedTitle as alert title. * [AltServer] Adds “View More Details” button to error alert to view detailed error info * [AltServer] Renames InstallError to OperationError and conforms to ALTErrorEnum * [Shared] Removes CodableError support for Date user info values Not currently used, and we don’t want to accidentally parse a non-Date as a Date in the meantime. * [Shared] Includes dynamic UserInfoValueProvider values in NSError.formattedDetailedDescription() * [Shared] Includes source file + line in NSError.formattedDetailedDescription() Automatically captures source file + line when throwing ALTErrorEnums. * [Shared] Captures source file + line for unknown errors * Removes sourceFunction from OperationError * Adds localizedTitle to AuthenticationViewController errors * [Shared] Moves nested ALTWrappedError logic to ALTWrappedError initializer * [AltServer] Removes now-redundant localized failure from JIT errors All JIT errors now have a localizedTitle which effectively says the same thing. * Makes OperationError.Code start at 1000 “Connection errors” subsection starts at 1200. * [Shared] Updates Error domains to revised [Source].[ErrorType] format * Updates ALTWrappedError.localizedDescription to prioritize using wrapped NSLocalizedDescription as failure reason * Makes ALTAppleAPIError codes start at 3000 * [AltServer] Adds relevant localizedFailures to ALTDeviceManager.installApplication() errors * Revises OperationError failureReasons and recovery suggestions All failure reasons now read correctly when preceded by a failure reason and “because”. * Revises ALTServerError error messages All failure reasons now read correctly when preceded by a failure reason and “because”. * Most failure reasons now read correctly when preceded by a failure reason and “because”. * ALTServerErrorUnderlyingError forwards all user info provider calls to underlying error. * Revises error messages for ALTAppleAPIErrorIncorrectCredentials * [Missed] Removes NSError+AltStore.swift from AltStore target * [Shared] Updates AltServerErrorDomain to revised [Source].[ErrorType] format * [Shared] Removes “code” from Error.localizedErrorCode * [Shared] Makes ALTServerError codes (appear to) start at 2000 We can’t change the actual error codes without breaking backwards compatibility, so instead we just add 2000 whenever we display ALTServerError codes to the user. * Moves VerificationError.errorFailure to VerifyAppOperation * Supports custom failure reason for OperationError.unknown * [Shared] Changes AltServerErrorDomain to “AltServer.ServerError” * [Shared] Converts ALTWrappedError to Objective-C class NSError subclasses must be written in ObjC for Swift.Error <-> NSError bridging to work correctly. # Conflicts: # AltStore.xcodeproj/project.pbxproj * Fixes decoding CodableError nested user info values
This commit is contained in:
@@ -11,5 +11,6 @@
|
||||
#import "ALTConstants.h"
|
||||
#import "ALTConnection.h"
|
||||
#import "AltXPCProtocol.h"
|
||||
#import "ALTWrappedError.h"
|
||||
#import "NSError+ALTServerError.h"
|
||||
#import "CFNotificationName+AltStore.h"
|
||||
|
||||
@@ -56,6 +56,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
private var isAltPluginUpdateAvailable = false
|
||||
|
||||
private var popoverController: NSPopover?
|
||||
private var popoverError: NSError?
|
||||
private var errorAlert: NSAlert?
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification)
|
||||
{
|
||||
UserDefaults.standard.registerDefaults()
|
||||
@@ -159,8 +163,9 @@ private extension AppDelegate
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
self.showErrorAlert(error: error, localizedFailure: String(format: NSLocalizedString("JIT compilation could not be enabled for %@.", comment: ""), app.name))
|
||||
case .failure(let error as NSError):
|
||||
let localizedTitle = String(format: NSLocalizedString("JIT could not be enabled for %@.", comment: ""), app.name)
|
||||
self.showErrorAlert(error: error.withLocalizedTitle(localizedTitle))
|
||||
|
||||
case .success:
|
||||
let alert = NSAlert()
|
||||
@@ -250,13 +255,13 @@ private extension AppDelegate
|
||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
|
||||
case .failure(InstallError.cancelled), .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
|
||||
case .failure(~OperationErrorCode.cancelled), .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
|
||||
// Ignore
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
DispatchQueue.main.async {
|
||||
self.showErrorAlert(error: error, localizedFailure: String(format: NSLocalizedString("Could not install app to %@.", comment: ""), device.name))
|
||||
self.showErrorAlert(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,7 +283,10 @@ private extension AppDelegate
|
||||
self.pluginManager.isUpdateAvailable { result in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(error))
|
||||
case .failure(let error):
|
||||
let error = (error as NSError).withLocalizedTitle(NSLocalizedString("Could not check for Mail plug-in updates.", comment: ""))
|
||||
finish(.failure(error))
|
||||
|
||||
case .success(let isUpdateAvailable):
|
||||
self.isAltPluginUpdateAvailable = isUpdateAvailable
|
||||
|
||||
@@ -302,85 +310,61 @@ private extension AppDelegate
|
||||
}
|
||||
}
|
||||
|
||||
func showErrorAlert(error: Error, localizedFailure: String)
|
||||
func showErrorAlert(error: Error)
|
||||
{
|
||||
self.popoverError = error as NSError
|
||||
|
||||
let nsError = error as NSError
|
||||
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .critical
|
||||
alert.messageText = localizedFailure
|
||||
|
||||
var messageComponents = [String]()
|
||||
|
||||
let separator: String
|
||||
switch error
|
||||
{
|
||||
case ALTServerError.maximumFreeAppLimitReached: separator = "\n\n"
|
||||
default: separator = " "
|
||||
}
|
||||
|
||||
if let errorFailure = nsError.localizedFailure
|
||||
{
|
||||
if let debugDescription = nsError.localizedDebugDescription
|
||||
{
|
||||
alert.messageText = errorFailure
|
||||
messageComponents.append(debugDescription)
|
||||
}
|
||||
else if let failureReason = nsError.localizedFailureReason
|
||||
{
|
||||
if nsError.localizedDescription.starts(with: errorFailure)
|
||||
{
|
||||
alert.messageText = errorFailure
|
||||
messageComponents.append(failureReason)
|
||||
}
|
||||
else
|
||||
{
|
||||
alert.messageText = errorFailure
|
||||
messageComponents.append(nsError.localizedDescription)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No failure reason given.
|
||||
|
||||
if nsError.localizedDescription.starts(with: errorFailure)
|
||||
{
|
||||
// No need to duplicate errorFailure in both title and message.
|
||||
alert.messageText = localizedFailure
|
||||
messageComponents.append(nsError.localizedDescription)
|
||||
}
|
||||
else
|
||||
{
|
||||
alert.messageText = errorFailure
|
||||
messageComponents.append(nsError.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
alert.messageText = localizedFailure
|
||||
|
||||
if let debugDescription = nsError.localizedDebugDescription
|
||||
{
|
||||
messageComponents.append(debugDescription)
|
||||
}
|
||||
else
|
||||
{
|
||||
messageComponents.append(nsError.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
var messageComponents = [error.localizedDescription]
|
||||
if let recoverySuggestion = nsError.localizedRecoverySuggestion
|
||||
{
|
||||
messageComponents.append(recoverySuggestion)
|
||||
}
|
||||
|
||||
let informativeText = messageComponents.joined(separator: separator)
|
||||
alert.informativeText = informativeText
|
||||
let title = nsError.localizedTitle ?? NSLocalizedString("Operation Failed", comment: "")
|
||||
let message = messageComponents.joined(separator: "\n\n")
|
||||
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .critical
|
||||
alert.messageText = title
|
||||
alert.informativeText = message
|
||||
alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
alert.addButton(withTitle: NSLocalizedString("View More Details", comment: ""))
|
||||
|
||||
if let viewMoreButton = alert.buttons.last
|
||||
{
|
||||
viewMoreButton.target = self
|
||||
viewMoreButton.action = #selector(AppDelegate.showDetailedErrorDescription)
|
||||
|
||||
self.errorAlert = alert
|
||||
}
|
||||
|
||||
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
||||
|
||||
|
||||
alert.runModal()
|
||||
|
||||
self.popoverController = nil
|
||||
self.errorAlert = nil
|
||||
self.popoverError = nil
|
||||
}
|
||||
|
||||
@objc func showDetailedErrorDescription()
|
||||
{
|
||||
guard let errorAlert, let contentView = errorAlert.window.contentView else { return }
|
||||
|
||||
let errorDetailsViewController = NSStoryboard(name: "Main", bundle: .main).instantiateController(withIdentifier: "errorDetailsViewController") as! ErrorDetailsViewController
|
||||
errorDetailsViewController.error = self.popoverError
|
||||
|
||||
let fittingSize = errorDetailsViewController.view.fittingSize
|
||||
errorDetailsViewController.view.frame.size = fittingSize
|
||||
|
||||
let popoverController = NSPopover()
|
||||
popoverController.contentViewController = errorDetailsViewController
|
||||
popoverController.contentSize = fittingSize
|
||||
popoverController.behavior = .transient
|
||||
popoverController.show(relativeTo: contentView.bounds, of: contentView, preferredEdge: .maxX)
|
||||
self.popoverController = popoverController
|
||||
}
|
||||
|
||||
@objc func toggleLaunchAtLogin(_ item: NSMenuItem)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -384,5 +384,59 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="0.0"/>
|
||||
</scene>
|
||||
<!--Error Details View Controller-->
|
||||
<scene sceneID="IpC-sd-lPu">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="errorDetailsViewController" id="vPa-1q-slD" customClass="ErrorDetailsViewController" customModule="AltServer" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="umL-zC-zP8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="88"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="16" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="aUr-m2-nXm">
|
||||
<rect key="frame" x="20" y="20" width="410" height="48"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8GZ-nV-XXA">
|
||||
<rect key="frame" x="-2" y="32" width="40" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="Label" id="V5D-v5-MVX">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="j2o-2b-63k">
|
||||
<rect key="frame" x="-2" y="0.0" width="92" height="16"/>
|
||||
<textFieldCell key="cell" selectable="YES" title="Multiline Label" id="3jf-Z7-88l">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="aUr-m2-nXm" secondAttribute="trailing" constant="20" id="RnW-JC-q92"/>
|
||||
<constraint firstAttribute="bottom" secondItem="aUr-m2-nXm" secondAttribute="bottom" constant="20" id="eGe-BS-TFJ"/>
|
||||
<constraint firstItem="aUr-m2-nXm" firstAttribute="top" secondItem="umL-zC-zP8" secondAttribute="top" constant="20" id="fel-H8-WFO"/>
|
||||
<constraint firstItem="aUr-m2-nXm" firstAttribute="leading" secondItem="umL-zC-zP8" secondAttribute="leading" constant="20" id="tcH-p8-4QH"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="detailedDescriptionLabel" destination="j2o-2b-63k" id="de3-yf-oTt"/>
|
||||
<outlet property="errorCodeLabel" destination="8GZ-nV-XXA" id="cXZ-11-wrJ"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="wrv-m0-8zg" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="537" y="17"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@@ -108,9 +108,6 @@ char *bin2hex(const unsigned char *bin, size_t length)
|
||||
- (void)_enableUnsignedCodeExecutionForProcessWithName:(nullable NSString *)processName pid:(int32_t)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
|
||||
{
|
||||
dispatch_async(self.connectionQueue, ^{
|
||||
NSString *name = processName ?: NSLocalizedString(@"this app", @"");
|
||||
NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"JIT could not be enabled for %@.", comment: @""), name];
|
||||
|
||||
NSString *attachCommand = nil;
|
||||
|
||||
if (processName)
|
||||
@@ -133,7 +130,6 @@ char *bin2hex(const unsigned char *bin, size_t length)
|
||||
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
|
||||
userInfo[ALTAppNameErrorKey] = processName;
|
||||
userInfo[ALTDeviceNameErrorKey] = self.device.name;
|
||||
userInfo[NSLocalizedFailureErrorKey] = localizedFailure;
|
||||
|
||||
NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
|
||||
return completionHandler(NO, returnError);
|
||||
@@ -145,7 +141,6 @@ char *bin2hex(const unsigned char *bin, size_t length)
|
||||
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
|
||||
userInfo[ALTAppNameErrorKey] = processName;
|
||||
userInfo[ALTDeviceNameErrorKey] = self.device.name;
|
||||
userInfo[NSLocalizedFailureErrorKey] = localizedFailure;
|
||||
|
||||
NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
|
||||
return completionHandler(NO, returnError);
|
||||
@@ -249,7 +244,11 @@ char *bin2hex(const unsigned char *bin, size_t length)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
*error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}];
|
||||
*error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{
|
||||
NSLocalizedFailureReasonErrorKey: response,
|
||||
ALTSourceFileErrorKey: @(__FILE__).lastPathComponent,
|
||||
ALTSourceLineErrorKey: @(__LINE__)
|
||||
}];
|
||||
}
|
||||
|
||||
return NO;
|
||||
@@ -292,7 +291,11 @@ char *bin2hex(const unsigned char *bin, size_t length)
|
||||
break;
|
||||
|
||||
default:
|
||||
*error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}];
|
||||
*error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{
|
||||
NSLocalizedFailureReasonErrorKey: response,
|
||||
ALTSourceFileErrorKey: @(__FILE__).lastPathComponent,
|
||||
ALTSourceLineErrorKey: @(__LINE__)
|
||||
}];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,14 @@ import Foundation
|
||||
|
||||
import AltSign
|
||||
|
||||
enum DeveloperDiskError: LocalizedError
|
||||
typealias DeveloperDiskError = DeveloperDiskErrorCode.Error
|
||||
enum DeveloperDiskErrorCode: Int, ALTErrorEnum
|
||||
{
|
||||
case unknownDownloadURL
|
||||
case unsupportedOperatingSystem
|
||||
case downloadedDiskNotFound
|
||||
|
||||
var errorDescription: String? {
|
||||
var errorFailureReason: String {
|
||||
switch self
|
||||
{
|
||||
case .unknownDownloadURL: return NSLocalizedString("The URL to download the Developer disk image could not be determined.", comment: "")
|
||||
@@ -88,14 +89,14 @@ class DeveloperDiskManager
|
||||
{
|
||||
do
|
||||
{
|
||||
guard let osName = device.type.osName else { throw DeveloperDiskError.unsupportedOperatingSystem }
|
||||
guard let osName = device.type.osName else { throw DeveloperDiskError(.unsupportedOperatingSystem) }
|
||||
|
||||
let osKeyPath: KeyPath<FetchURLsResponse.Disks, [String: DeveloperDiskURL]?>
|
||||
switch device.type
|
||||
{
|
||||
case .iphone, .ipad: osKeyPath = \FetchURLsResponse.Disks.iOS
|
||||
case .appletv: osKeyPath = \FetchURLsResponse.Disks.tvOS
|
||||
default: throw DeveloperDiskError.unsupportedOperatingSystem
|
||||
default: throw DeveloperDiskError(.unsupportedOperatingSystem)
|
||||
}
|
||||
|
||||
var osVersion = device.osVersion
|
||||
@@ -146,7 +147,7 @@ class DeveloperDiskManager
|
||||
do
|
||||
{
|
||||
let developerDiskURLs = try result.get()
|
||||
guard let diskURL = developerDiskURLs[keyPath: osKeyPath]?[osVersion.stringValue] else { throw DeveloperDiskError.unknownDownloadURL }
|
||||
guard let diskURL = developerDiskURLs[keyPath: osKeyPath]?[osVersion.stringValue] else { throw DeveloperDiskError(.unknownDownloadURL) }
|
||||
|
||||
switch diskURL
|
||||
{
|
||||
@@ -201,7 +202,7 @@ private extension DeveloperDiskManager
|
||||
{
|
||||
guard let data = data else { throw error! }
|
||||
|
||||
let response = try JSONDecoder().decode(FetchURLsResponse.self, from: data)
|
||||
let response = try Foundation.JSONDecoder().decode(FetchURLsResponse.self, from: data)
|
||||
completionHandler(.success(response.disks))
|
||||
}
|
||||
catch
|
||||
@@ -244,7 +245,7 @@ private extension DeveloperDiskManager
|
||||
}
|
||||
}
|
||||
|
||||
guard let diskFileURL = tempDiskFileURL, let signatureFileURL = tempSignatureFileURL else { throw DeveloperDiskError.downloadedDiskNotFound }
|
||||
guard let diskFileURL = tempDiskFileURL, let signatureFileURL = tempSignatureFileURL else { throw DeveloperDiskError(.downloadedDiskNotFound) }
|
||||
|
||||
completionHandler(.success((diskFileURL, signatureFileURL)))
|
||||
}
|
||||
@@ -318,7 +319,7 @@ private extension DeveloperDiskManager
|
||||
}
|
||||
|
||||
guard let diskFileURL = diskFileURL, let signatureFileURL = signatureFileURL else {
|
||||
return completionHandler(.failure(downloadError ?? DeveloperDiskError.downloadedDiskNotFound))
|
||||
return completionHandler(.failure(downloadError ?? DeveloperDiskError(.downloadedDiskNotFound)))
|
||||
}
|
||||
|
||||
completionHandler(.success((diskFileURL, signatureFileURL)))
|
||||
|
||||
@@ -14,34 +14,21 @@ private let appGroupsSemaphore = DispatchSemaphore(value: 1)
|
||||
|
||||
private let developerDiskManager = DeveloperDiskManager()
|
||||
|
||||
enum InstallError: Int, LocalizedError, _ObjectiveCBridgeableError
|
||||
typealias OperationError = OperationErrorCode.Error
|
||||
enum OperationErrorCode: Int, ALTErrorEnum
|
||||
{
|
||||
case cancelled
|
||||
case noTeam
|
||||
case missingPrivateKey
|
||||
case missingCertificate
|
||||
|
||||
var errorDescription: String? {
|
||||
var errorFailureReason: String {
|
||||
switch self
|
||||
{
|
||||
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
|
||||
case .noTeam: return "You are not a member of any developer teams."
|
||||
case .missingPrivateKey: return "The developer certificate's private key could not be found."
|
||||
case .missingCertificate: return "The developer certificate could not be found."
|
||||
}
|
||||
}
|
||||
|
||||
init?(_bridgedNSError error: NSError)
|
||||
{
|
||||
guard error.domain == InstallError.cancelled._domain else { return nil }
|
||||
|
||||
if let installError = InstallError(rawValue: error.code)
|
||||
{
|
||||
self = installError
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil
|
||||
case .noTeam: return NSLocalizedString("You are not a member of any developer teams.", comment: "")
|
||||
case .missingPrivateKey: return NSLocalizedString("The developer certificate's private key could not be found.", comment: "")
|
||||
case .missingCertificate: return NSLocalizedString("The developer certificate could not be found.", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,16 +41,18 @@ extension ALTDeviceManager
|
||||
|
||||
var appName = (url.isFileURL) ? url.deletingPathExtension().lastPathComponent : NSLocalizedString("AltStore", comment: "")
|
||||
|
||||
func finish(_ result: Result<ALTApplication, Error>, title: String = "")
|
||||
func finish(_ result: Result<ALTApplication, Error>, failure: String? = nil)
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
{
|
||||
case .success(let app): completion(.success(app))
|
||||
case .failure(var error as NSError):
|
||||
if error.localizedFailure == nil
|
||||
error = error.withLocalizedTitle(String(format: NSLocalizedString("%@ could not be installed onto %@.", comment: ""), appName, altDevice.name))
|
||||
|
||||
if let failure, error.localizedFailure == nil
|
||||
{
|
||||
error = error.withLocalizedFailure(String(format: NSLocalizedString("Could not install %@ to %@.", comment: ""), appName, altDevice.name))
|
||||
error = error.withLocalizedFailure(failure)
|
||||
}
|
||||
|
||||
completion(.failure(error))
|
||||
@@ -144,24 +133,25 @@ extension ALTDeviceManager
|
||||
let profiles = try result.get()
|
||||
|
||||
self.install(application, to: device, team: team, certificate: certificate, profiles: profiles) { (result) in
|
||||
finish(result.map { application }, title: "Failed to Install AltStore")
|
||||
finish(result.map { application })
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(error), title: "Failed to Fetch Provisioning Profiles")
|
||||
finish(.failure(error), failure: NSLocalizedString("AltServer could not fetch new provisioning profiles.", comment: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(error), title: "Failed to Refresh Anisette Data")
|
||||
finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(error), title: "Failed to Download AltStore")
|
||||
let failure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), appName)
|
||||
finish(.failure(error), failure: failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,31 +159,31 @@ extension ALTDeviceManager
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(error), title: "Failed to Fetch Certificate")
|
||||
finish(.failure(error), failure: NSLocalizedString("A valid signing certificate could not be created.", comment: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(error), title: "Failed to Register Device")
|
||||
finish(.failure(error), failure: NSLocalizedString("Your device could not be registered with your development team.", comment: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(error), title: "Failed to Fetch Team")
|
||||
finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(error), title: "Failed to Authenticate")
|
||||
finish(.failure(error), failure: NSLocalizedString("AltServer could not sign in with your Apple ID.", comment: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(error), title: "Failed to Fetch Anisette Data")
|
||||
finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +296,7 @@ private extension ALTDeviceManager
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.failure(error ?? ALTAppleAPIError(.unknown)))
|
||||
completionHandler(.failure(error ?? ALTAppleAPIError.unknown()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,7 +322,7 @@ private extension ALTDeviceManager
|
||||
}
|
||||
else
|
||||
{
|
||||
throw InstallError.noTeam
|
||||
throw OperationError(.noTeam)
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -384,7 +374,7 @@ private extension ALTDeviceManager
|
||||
}
|
||||
}
|
||||
|
||||
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
|
||||
guard !isCancelled else { return completionHandler(.failure(OperationError(.cancelled))) }
|
||||
}
|
||||
|
||||
func addCertificate()
|
||||
@@ -393,7 +383,7 @@ private extension ALTDeviceManager
|
||||
do
|
||||
{
|
||||
let certificate = try Result(certificate, error).get()
|
||||
guard let privateKey = certificate.privateKey else { throw InstallError.missingPrivateKey }
|
||||
guard let privateKey = certificate.privateKey else { throw OperationError(.missingPrivateKey) }
|
||||
|
||||
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
||||
do
|
||||
@@ -401,7 +391,7 @@ private extension ALTDeviceManager
|
||||
let certificates = try Result(certificates, error).get()
|
||||
|
||||
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
|
||||
throw InstallError.missingCertificate
|
||||
throw OperationError(.missingCertificate)
|
||||
}
|
||||
|
||||
certificate.privateKey = privateKey
|
||||
@@ -454,7 +444,7 @@ private extension ALTDeviceManager
|
||||
}
|
||||
}
|
||||
|
||||
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
|
||||
guard !isCancelled else { return completionHandler(.failure(OperationError(.cancelled))) }
|
||||
}
|
||||
|
||||
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
|
||||
|
||||
@@ -1217,7 +1217,11 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
|
||||
|
||||
// ALTServerErrorUnderlyingError uses its underlying error's failure reason as its error description (if one exists),
|
||||
// so assign our recovery suggestion to NSLocalizedFailureReasonErrorKey to make sure it's always displayed on client.
|
||||
NSError *underlyingError = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: recoverySuggestion}];
|
||||
NSError *underlyingError = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{
|
||||
NSLocalizedFailureReasonErrorKey: recoverySuggestion,
|
||||
ALTSourceFileErrorKey: @(__FILE__).lastPathComponent,
|
||||
ALTSourceLineErrorKey: @(__LINE__)
|
||||
}];
|
||||
|
||||
returnError = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnderlyingError userInfo:@{NSUnderlyingErrorKey: underlyingError}];
|
||||
}
|
||||
|
||||
48
AltServer/ErrorDetailsViewController.swift
Normal file
48
AltServer/ErrorDetailsViewController.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// ErrorDetailsViewController.swift
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 10/4/22.
|
||||
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
class ErrorDetailsViewController: NSViewController
|
||||
{
|
||||
var error: NSError? {
|
||||
didSet {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet private var errorCodeLabel: NSTextField!
|
||||
@IBOutlet private var detailedDescriptionLabel: NSTextField!
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.detailedDescriptionLabel.preferredMaxLayoutWidth = 800
|
||||
}
|
||||
}
|
||||
|
||||
private extension ErrorDetailsViewController
|
||||
{
|
||||
func update()
|
||||
{
|
||||
if !self.isViewLoaded
|
||||
{
|
||||
self.loadView()
|
||||
}
|
||||
|
||||
guard let error = self.error else { return }
|
||||
|
||||
self.errorCodeLabel.stringValue = error.localizedErrorCode
|
||||
|
||||
let font = self.detailedDescriptionLabel.font ?? NSFont.systemFont(ofSize: 12)
|
||||
let detailedDescription = error.formattedDetailedDescription(with: font)
|
||||
self.detailedDescriptionLabel.attributedStringValue = detailedDescription
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,24 +15,71 @@ import STPrivilegedTask
|
||||
private let pluginDirectoryURL = URL(fileURLWithPath: "/Library/Mail/Bundles", isDirectory: true)
|
||||
private let pluginURL = pluginDirectoryURL.appendingPathComponent("AltPlugin.mailbundle")
|
||||
|
||||
enum PluginError: LocalizedError
|
||||
extension PluginError
|
||||
{
|
||||
case cancelled
|
||||
case unknown
|
||||
case notFound
|
||||
case mismatchedHash(hash: String, expectedHash: String)
|
||||
case taskError(String)
|
||||
case taskErrorCode(Int)
|
||||
enum Code: Int, ALTErrorCode
|
||||
{
|
||||
typealias Error = PluginError
|
||||
|
||||
case cancelled
|
||||
case unknown
|
||||
case notFound
|
||||
case mismatchedHash
|
||||
case taskError
|
||||
case taskErrorCode
|
||||
}
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self
|
||||
static let cancelled = PluginError(code: .cancelled)
|
||||
static let notFound = PluginError(code: .notFound)
|
||||
|
||||
static func unknown(file: String = #fileID, line: UInt = #line) -> PluginError { PluginError(code: .unknown, sourceFile: file, sourceLine: line) }
|
||||
static func mismatchedHash(hash: String, expectedHash: String) -> PluginError { PluginError(code: .mismatchedHash, hash: hash, expectedHash: expectedHash) }
|
||||
static func taskError(output: String) -> PluginError { PluginError(code: .taskError, taskErrorOutput: output) }
|
||||
static func taskErrorCode(_ code: Int) -> PluginError { PluginError(code: .taskErrorCode, taskErrorCode: code) }
|
||||
}
|
||||
|
||||
struct PluginError: ALTLocalizedError
|
||||
{
|
||||
let code: Code
|
||||
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
var sourceFile: String?
|
||||
var sourceLine: UInt?
|
||||
|
||||
var hash: String?
|
||||
var expectedHash: String?
|
||||
var taskErrorOutput: String?
|
||||
var taskErrorCode: Int?
|
||||
|
||||
var errorFailureReason: String {
|
||||
switch self.code
|
||||
{
|
||||
case .cancelled: return NSLocalizedString("Mail plug-in installation was cancelled.", comment: "")
|
||||
case .unknown: return NSLocalizedString("Failed to install Mail plug-in.", comment: "")
|
||||
case .notFound: return NSLocalizedString("The Mail plug-in does not exist at the requested URL.", comment: "")
|
||||
case .mismatchedHash(let hash, let expectedHash): return String(format: NSLocalizedString("The hash of the downloaded Mail plug-in does not match the expected hash.\n\nHash:\n%@\n\nExpected Hash:\n%@", comment: ""), hash, expectedHash)
|
||||
case .taskError(let output): return output
|
||||
case .taskErrorCode(let errorCode): return String(format: NSLocalizedString("There was an error installing the Mail plug-in. (Error Code: %@)", comment: ""), NSNumber(value: errorCode))
|
||||
case .mismatchedHash:
|
||||
let baseMessage = NSLocalizedString("The hash of the downloaded Mail plug-in does not match the expected hash.", comment: "")
|
||||
guard let hash = self.hash, let expectedHash = self.expectedHash else { return baseMessage }
|
||||
|
||||
let additionalInfo = String(format: NSLocalizedString("Hash:\n%@\n\nExpected Hash:\n%@", comment: ""), hash, expectedHash)
|
||||
return baseMessage + "\n\n" + additionalInfo
|
||||
|
||||
case .taskError:
|
||||
if let output = self.taskErrorOutput
|
||||
{
|
||||
return output
|
||||
}
|
||||
|
||||
// Use .taskErrorCode base message as fallback.
|
||||
fallthrough
|
||||
|
||||
case .taskErrorCode:
|
||||
let baseMessage = NSLocalizedString("There was an error installing the Mail plug-in.", comment: "")
|
||||
guard let errorCode = self.taskErrorCode else { return baseMessage }
|
||||
|
||||
let additionalInfo = String(format: NSLocalizedString("(Error Code: %@)", comment: ""), NSNumber(value: errorCode))
|
||||
return baseMessage + " " + additionalInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,7 +207,7 @@ extension PluginManager
|
||||
let unzippedPluginURL = temporaryDirectoryURL.appendingPathComponent(pluginURL.lastPathComponent)
|
||||
try self.runAndKeepAuthorization("cp", arguments: ["-R", unzippedPluginURL.path, pluginDirectoryURL.path], authorization: authorization)
|
||||
|
||||
guard self.isMailPluginInstalled else { throw PluginError.unknown }
|
||||
guard self.isMailPluginInstalled else { throw PluginError.unknown() }
|
||||
|
||||
// Enable Mail plug-in preferences.
|
||||
try self.run("defaults", arguments: ["write", "/Library/Preferences/com.apple.mail", "EnableBundles", "-bool", "YES"], authorization: authorization)
|
||||
@@ -360,13 +407,13 @@ private extension PluginManager
|
||||
|
||||
if let outputString = String(data: outputData, encoding: .utf8), !outputString.isEmpty
|
||||
{
|
||||
throw PluginError.taskError(outputString)
|
||||
throw PluginError.taskError(output: outputString)
|
||||
}
|
||||
|
||||
throw PluginError.taskErrorCode(Int(task.terminationStatus))
|
||||
}
|
||||
|
||||
guard let authorization = task.authorization else { throw PluginError.unknown }
|
||||
guard let authorization = task.authorization else { throw PluginError.unknown() }
|
||||
return authorization
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user