From 5e25593c3dc1070da3582ed148759a7eb76beb77 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Wed, 11 Mar 2020 13:35:14 -0700 Subject: [PATCH] [Both] Improves error messages --- AltKit/NSError+ALTServerError.h | 6 +- AltKit/NSError+ALTServerError.m | 68 ++++++++++++------- AltServer/Devices/ALTDeviceManager.mm | 53 ++++++++++++--- AltStore.xcodeproj/project.pbxproj | 4 ++ AltStore/Components/ToastView.swift | 35 ++++++++-- .../Extensions/NSError+LocalizedFailure.swift | 18 +++++ 6 files changed, 141 insertions(+), 43 deletions(-) create mode 100644 AltStore/Extensions/NSError+LocalizedFailure.swift diff --git a/AltKit/NSError+ALTServerError.h b/AltKit/NSError+ALTServerError.h index 0a9e884a..a63c6905 100644 --- a/AltKit/NSError+ALTServerError.h +++ b/AltKit/NSError+ALTServerError.h @@ -16,6 +16,8 @@ extern NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey; typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError) { + ALTServerErrorUnderlyingError = -1, + ALTServerErrorUnknown = 0, ALTServerErrorConnectionFailed = 1, ALTServerErrorLostConnection = 2, @@ -37,9 +39,7 @@ typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError) ALTServerErrorInvalidAnisetteData = 13, ALTServerErrorPluginNotFound = 14, - ALTServerErrorProfileInstallFailed = 15, - ALTServerErrorProfileCopyFailed = 16, - ALTServerErrorProfileRemoveFailed = 17, + ALTServerErrorProfileNotFound = 15 }; NS_ASSUME_NONNULL_BEGIN diff --git a/AltKit/NSError+ALTServerError.m b/AltKit/NSError+ALTServerError.m index 2349152d..86dadb73 100644 --- a/AltKit/NSError+ALTServerError.m +++ b/AltKit/NSError+ALTServerError.m @@ -19,19 +19,35 @@ NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey = @"bundleIdenti + (void)load { [NSError setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) { - if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey]) + if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey]) { - return [error alt_localizedDescription]; + return [error alt_localizedFailureReason]; + } + + if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey]) + { + return [error alt_localizedRecoverySuggestion]; } return nil; }]; } -- (nullable NSString *)alt_localizedDescription +- (nullable NSString *)alt_localizedFailureReason { switch ((ALTServerError)self.code) { + case ALTServerErrorUnderlyingError: + { + NSString *underlyingErrorCode = self.userInfo[ALTUnderlyingErrorCodeErrorKey]; + if (underlyingErrorCode == nil) + { + return NSLocalizedString(@"An unknown error occured.", @""); + } + + return [NSString stringWithFormat:NSLocalizedString(@"Error code: %@", @""), underlyingErrorCode]; + } + case ALTServerErrorUnknown: return NSLocalizedString(@"An unknown error occured.", @""); @@ -60,7 +76,7 @@ NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey = @"bundleIdenti return NSLocalizedString(@"An error occured while installing the app.", @""); case ALTServerErrorMaximumFreeAppLimitReached: - return NSLocalizedString(@"You have reached the limit of 3 apps per device.", @""); + return NSLocalizedString(@"Cannot activate more than 3 apps and app extensions.", @""); case ALTServerErrorUnsupportediOSVersion: return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install AltStore.", @""); @@ -75,41 +91,43 @@ NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey = @"bundleIdenti return NSLocalizedString(@"Invalid anisette data.", @""); case ALTServerErrorPluginNotFound: - return NSLocalizedString(@"Could not connect to Mail plug-in. Please make sure Mail is running and that you've enabled the plug-in in Mail's preferences, then try again.", @""); + return NSLocalizedString(@"Could not connect to Mail plug-in.", @""); - case ALTServerErrorProfileInstallFailed: - return [self underlyingProfileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not install profile", "")]; - - case ALTServerErrorProfileCopyFailed: - return [self underlyingProfileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not copy provisioning profiles", "")]; - - case ALTServerErrorProfileRemoveFailed: - return [self underlyingProfileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not remove profile", "")]; + case ALTServerErrorProfileNotFound: + return [self profileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not find profile", "")]; } } -- (NSString *)underlyingProfileErrorLocalizedDescriptionWithBaseDescription:(NSString *)baseDescription +- (nullable NSString *)alt_localizedRecoverySuggestion { - NSMutableString *localizedDescription = [NSMutableString string]; + switch ((ALTServerError)self.code) + { + case ALTServerErrorConnectionFailed: + case ALTServerErrorDeviceNotFound: + return NSLocalizedString(@"Make sure you have trusted this phone with your computer and WiFi sync is enabled.", @""); + + case ALTServerErrorPluginNotFound: + return NSLocalizedString(@"Make sure Mail is running and the plug-in is enabled in Mail's preferences.", @""); + + default: + return nil; + } +} + +- (NSString *)profileErrorLocalizedDescriptionWithBaseDescription:(NSString *)baseDescription +{ + NSString *localizedDescription = nil; NSString *bundleID = self.userInfo[ALTProvisioningProfileBundleIDErrorKey]; if (bundleID) { - [localizedDescription appendFormat:NSLocalizedString(@"%@ “%@”", @""), baseDescription, bundleID]; + localizedDescription = [NSString stringWithFormat:@"%@ “%@”", baseDescription, bundleID]; } else { - [localizedDescription appendString:baseDescription]; + localizedDescription = [NSString stringWithFormat:@"%@.", baseDescription]; } - NSString *underlyingErrorCode = self.userInfo[ALTUnderlyingErrorCodeErrorKey]; - if (underlyingErrorCode) - { - [localizedDescription appendFormat:@" (%@)", underlyingErrorCode]; - } - - [localizedDescription appendString:@"."]; - return localizedDescription; } diff --git a/AltServer/Devices/ALTDeviceManager.mm b/AltServer/Devices/ALTDeviceManager.mm index 9b1d4bd8..14cf5224 100644 --- a/AltServer/Devices/ALTDeviceManager.mm +++ b/AltServer/Devices/ALTDeviceManager.mm @@ -702,22 +702,35 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT if (result == MISAGENT_E_SUCCESS) { - NSLog(@"Installed profile: %@ (Team: %@)", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier); + NSLog(@"Installed profile: %@ (%@)", provisioningProfile.bundleIdentifier, provisioningProfile.UUID); return YES; } else { - NSLog(@"Failed to install provisioning profile %@ (Team: %@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier, @(result)); + int statusCode = misagent_get_status_code(mis); + NSLog(@"Failed to install provisioning profile %@ (%@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.UUID, @(statusCode)); if (error) { - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorProfileInstallFailed userInfo:@{ALTUnderlyingErrorCodeErrorKey: [@(result) description], ALTProvisioningProfileBundleIDErrorKey: provisioningProfile.bundleIdentifier}]; + switch (statusCode) + { + case -402620383: + *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorMaximumFreeAppLimitReached userInfo:nil]; + break; + + default: + NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"Could not install profile “%@”", @""), provisioningProfile.bundleIdentifier]; + + *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnderlyingError userInfo:@{ + NSLocalizedFailureErrorKey: localizedFailure, + ALTUnderlyingErrorCodeErrorKey: [@(statusCode) description], + ALTProvisioningProfileBundleIDErrorKey: provisioningProfile.bundleIdentifier + }]; + } } return NO; } - - return NO; } - (BOOL)removeProvisioningProfile:(ALTProvisioningProfile *)provisioningProfile misagent:(misagent_client_t)mis error:(NSError **)error @@ -725,16 +738,33 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT misagent_error_t result = misagent_remove(mis, provisioningProfile.UUID.UUIDString.lowercaseString.UTF8String); if (result == MISAGENT_E_SUCCESS) { - NSLog(@"Removed provisioning profile: %@ (Team: %@)", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier); + NSLog(@"Removed provisioning profile: %@ (%@)", provisioningProfile.bundleIdentifier, provisioningProfile.UUID); return YES; } else { - NSLog(@"Failed to remove provisioning profile %@ (Team: %@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier, @(result)); + int statusCode = misagent_get_status_code(mis); + NSLog(@"Failed to remove provisioning profile %@ (%@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.UUID, @(statusCode)); if (error) { - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorProfileRemoveFailed userInfo:@{ALTUnderlyingErrorCodeErrorKey: [@(result) description], ALTProvisioningProfileBundleIDErrorKey: provisioningProfile.bundleIdentifier}]; + switch (statusCode) + { + case -402620405: + *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorProfileNotFound userInfo:nil]; + break; + + default: + { + NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"Could not remove profile “%@”", @""), provisioningProfile.bundleIdentifier]; + + *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnderlyingError userInfo:@{ + NSLocalizedFailureErrorKey: localizedFailure, + ALTUnderlyingErrorCodeErrorKey: [@(statusCode) description], + ALTProvisioningProfileBundleIDErrorKey: provisioningProfile.bundleIdentifier + }]; + } + } } return NO; @@ -747,9 +777,14 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT misagent_error_t result = misagent_copy_all(mis, &rawProfiles); if (result != MISAGENT_E_SUCCESS) { + int statusCode = misagent_get_status_code(mis); + if (error) { - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorProfileCopyFailed userInfo:@{ALTUnderlyingErrorCodeErrorKey: [@(result) description]}]; + *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnderlyingError userInfo:@{ + NSLocalizedFailureErrorKey: NSLocalizedString(@"Could not copy provisioning profiles.", @""), + ALTUnderlyingErrorCodeErrorKey: [@(statusCode) description] + }]; } return nil; diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index a435da3a..c5b1514d 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -122,6 +122,7 @@ BF56D2AC23DF8E170006506D /* FetchAppIDsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF56D2AB23DF8E170006506D /* FetchAppIDsOperation.swift */; }; BF56D2AF23DF9E310006506D /* AppIDsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF56D2AE23DF9E310006506D /* AppIDsViewController.swift */; }; BF5C5FCF237DF69100EDD0C6 /* ALTPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = BF5C5FCE237DF69100EDD0C6 /* ALTPluginService.m */; }; + BF6C336224197D700034FD24 /* NSError+LocalizedFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+LocalizedFailure.swift */; }; BF6F439223644C6E00A0B879 /* RefreshAltStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6F439123644C6E00A0B879 /* RefreshAltStoreViewController.swift */; }; BF718BC923C919E300A89F2D /* CFNotificationName+AltStore.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */; }; BF718BD123C91BD300A89F2D /* ALTWiredConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD023C91BD300A89F2D /* ALTWiredConnection.m */; }; @@ -433,6 +434,7 @@ BF5C5FC7237DF5AE00EDD0C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BF5C5FCD237DF69100EDD0C6 /* ALTPluginService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTPluginService.h; sourceTree = ""; }; BF5C5FCE237DF69100EDD0C6 /* ALTPluginService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTPluginService.m; sourceTree = ""; }; + BF6C336124197D700034FD24 /* NSError+LocalizedFailure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+LocalizedFailure.swift"; sourceTree = ""; }; BF6F439123644C6E00A0B879 /* RefreshAltStoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAltStoreViewController.swift; sourceTree = ""; }; BF718BC723C919CC00A89F2D /* CFNotificationName+AltStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CFNotificationName+AltStore.h"; sourceTree = ""; }; BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CFNotificationName+AltStore.m"; sourceTree = ""; }; @@ -1098,6 +1100,7 @@ BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */, BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */, BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */, + BF6C336124197D700034FD24 /* NSError+LocalizedFailure.swift */, ); path = Extensions; sourceTree = ""; @@ -1697,6 +1700,7 @@ BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */, BFD5D6EA230CCAE5007955AB /* PatreonAccount.swift in Sources */, BFE6326822A858F300F30809 /* Account.swift in Sources */, + BF6C336224197D700034FD24 /* NSError+LocalizedFailure.swift in Sources */, BFE6326622A857C200F30809 /* Team.swift in Sources */, BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */, BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */, diff --git a/AltStore/Components/ToastView.swift b/AltStore/Components/ToastView.swift index 7a8bb74d..e99a8f91 100644 --- a/AltStore/Components/ToastView.swift +++ b/AltStore/Components/ToastView.swift @@ -16,7 +16,7 @@ class ToastView: RSTToastView { if detailedText == nil { - self.preferredDuration = 2.0 + self.preferredDuration = 4.0 } else { @@ -25,20 +25,41 @@ class ToastView: RSTToastView super.init(text: text, detailText: detailedText) - self.layoutMargins = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12) + self.layoutMargins = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12) self.setNeedsLayout() + + if let stackView = self.textLabel.superview as? UIStackView + { + // RSTToastView does not expose stack view containing labels, + // so we access it indirectly as the labels' superview. + stackView.spacing = 4.0 + } } convenience init(error: Error) { - if let error = error as? LocalizedError + let error = error as NSError + + let text: String + let detailText: String? + + if let failure = error.localizedFailure { - self.init(text: error.localizedDescription, detailText: error.recoverySuggestion ?? error.failureReason) + text = failure + detailText = error.localizedFailureReason ?? error.localizedRecoverySuggestion + } + else if let reason = error.localizedFailureReason + { + text = reason + detailText = error.localizedRecoverySuggestion } else { - self.init(text: error.localizedDescription, detailText: nil) + text = error.localizedDescription + detailText = nil } + + self.init(text: text, detailText: detailText) } required init(coder aDecoder: NSCoder) { @@ -49,7 +70,9 @@ class ToastView: RSTToastView { super.layoutSubviews() - self.layer.cornerRadius = 16 + // Rough calculation to determine height of ToastView with one-line textLabel. + let minimumHeight = self.textLabel.font.lineHeight.rounded() + 20 + self.layer.cornerRadius = minimumHeight/2 } func show(in viewController: UIViewController) diff --git a/AltStore/Extensions/NSError+LocalizedFailure.swift b/AltStore/Extensions/NSError+LocalizedFailure.swift new file mode 100644 index 00000000..dc47b323 --- /dev/null +++ b/AltStore/Extensions/NSError+LocalizedFailure.swift @@ -0,0 +1,18 @@ +// +// NSError+LocalizedFailure.swift +// AltStore +// +// Created by Riley Testut on 3/11/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation + +extension NSError +{ + @objc(alt_localizedFailure) + var localizedFailure: String? { + let localizedFailure = (self.userInfo[NSLocalizedFailureErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedFailureErrorKey) as? String) + return localizedFailure + } +}