mirror of
https://github.com/SideStore/SideStore.git
synced 2026-03-29 14:55:39 +02:00
Finish Riley's monster commit
3b38d725d7
May the Gods have mercy on my soul.
This commit is contained in:
@@ -36,6 +36,13 @@
|
|||||||
0EA1667D2ADFE140003015C1 /* xplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166592ADFE0D2003015C1 /* xplist.c */; };
|
0EA1667D2ADFE140003015C1 /* xplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166592ADFE0D2003015C1 /* xplist.c */; };
|
||||||
0EA1667E2ADFE140003015C1 /* time64.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664C2ADFE0D1003015C1 /* time64.c */; };
|
0EA1667E2ADFE140003015C1 /* time64.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664C2ADFE0D1003015C1 /* time64.c */; };
|
||||||
0EA4B9BC2AE4A414009209CE /* plist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA4B9BB2AE4A3F6009209CE /* plist.c */; };
|
0EA4B9BC2AE4A414009209CE /* plist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA4B9BB2AE4A3F6009209CE /* plist.c */; };
|
||||||
|
0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||||
|
0EE7FDC62BE8CEA300D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||||
|
0EE7FDC72BE8CF4100D1E390 /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */; };
|
||||||
|
0EE7FDC82BE8CF4800D1E390 /* ALTWrappedError.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
||||||
|
0EE7FDCB2BE8D12B00D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||||
|
0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */; };
|
||||||
19104D952909BAEA00C49C7B /* libimobiledevice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF45872B2298D31600BD7491 /* libimobiledevice.a */; };
|
19104D952909BAEA00C49C7B /* libimobiledevice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF45872B2298D31600BD7491 /* libimobiledevice.a */; };
|
||||||
19104DB52909C06D00C49C7B /* EmotionalDamage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19104DB42909C06D00C49C7B /* EmotionalDamage.swift */; };
|
19104DB52909C06D00C49C7B /* EmotionalDamage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19104DB42909C06D00C49C7B /* EmotionalDamage.swift */; };
|
||||||
19104DBC2909C4E500C49C7B /* libEmotionalDamage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */; };
|
19104DBC2909C4E500C49C7B /* libEmotionalDamage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */; };
|
||||||
@@ -162,7 +169,6 @@
|
|||||||
BF58048A246A28F9008AE704 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF580488246A28F9008AE704 /* LaunchScreen.storyboard */; };
|
BF58048A246A28F9008AE704 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF580488246A28F9008AE704 /* LaunchScreen.storyboard */; };
|
||||||
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */; };
|
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */; };
|
||||||
BF580498246A3D19008AE704 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF580497246A3D19008AE704 /* UIKit.framework */; };
|
BF580498246A3D19008AE704 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF580497246A3D19008AE704 /* UIKit.framework */; };
|
||||||
BF58049B246A432D008AE704 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
|
||||||
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */; };
|
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */; };
|
||||||
BF66EE822501AE50007EE018 /* AltStoreCore.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE802501AE50007EE018 /* AltStoreCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
BF66EE822501AE50007EE018 /* AltStoreCore.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE802501AE50007EE018 /* AltStoreCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; };
|
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; };
|
||||||
@@ -208,7 +214,6 @@
|
|||||||
BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE52501AED0007EE018 /* JSONDecoder+Properties.swift */; };
|
BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE52501AED0007EE018 /* JSONDecoder+Properties.swift */; };
|
||||||
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */; };
|
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */; };
|
||||||
BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE72501AED0007EE018 /* UIApplication+AppExtension.swift */; };
|
BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE72501AED0007EE018 /* UIApplication+AppExtension.swift */; };
|
||||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
|
||||||
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAA242935ED00125131 /* NSAttributedString+Markdown.m */; };
|
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAA242935ED00125131 /* NSAttributedString+Markdown.m */; };
|
||||||
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAD2429597900125131 /* BannerCollectionViewCell.swift */; };
|
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAD2429597900125131 /* BannerCollectionViewCell.swift */; };
|
||||||
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAF2429599900125131 /* TextCollectionReusableView.swift */; };
|
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAF2429599900125131 /* TextCollectionReusableView.swift */; };
|
||||||
@@ -239,7 +244,7 @@
|
|||||||
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4A22DD137F008935CF /* NavigationBar.swift */; };
|
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4A22DD137F008935CF /* NavigationBar.swift */; };
|
||||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4C22DD16DE008935CF /* PillButton.swift */; };
|
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4C22DD16DE008935CF /* PillButton.swift */; };
|
||||||
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */; };
|
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */; };
|
||||||
BFAECC522501B0A400528F27 /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
|
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableError.swift */; };
|
||||||
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
||||||
BFAECC542501B0A400528F27 /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
BFAECC542501B0A400528F27 /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
||||||
BFAECC552501B0A400528F27 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; };
|
BFAECC552501B0A400528F27 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; };
|
||||||
@@ -298,7 +303,7 @@
|
|||||||
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */; };
|
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */; };
|
||||||
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; };
|
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; };
|
||||||
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; };
|
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; };
|
||||||
BFECAC8824FD950E0077C41F /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
|
BFECAC8824FD950E0077C41F /* CodableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableError.swift */; };
|
||||||
BFECAC8924FD950E0077C41F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; };
|
BFECAC8924FD950E0077C41F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; };
|
||||||
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; };
|
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; };
|
||||||
BFECAC8B24FD950E0077C41F /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
BFECAC8B24FD950E0077C41F /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
||||||
@@ -535,6 +540,10 @@
|
|||||||
0EA166662ADFE122003015C1 /* base64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = base64.h; path = Dependencies/libplist/src/base64.h; sourceTree = SOURCE_ROOT; };
|
0EA166662ADFE122003015C1 /* base64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = base64.h; path = Dependencies/libplist/src/base64.h; sourceTree = SOURCE_ROOT; };
|
||||||
0EA166672ADFE122003015C1 /* strbuf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = strbuf.h; path = Dependencies/libplist/src/strbuf.h; sourceTree = SOURCE_ROOT; };
|
0EA166672ADFE122003015C1 /* strbuf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = strbuf.h; path = Dependencies/libplist/src/strbuf.h; sourceTree = SOURCE_ROOT; };
|
||||||
0EA4B9BB2AE4A3F6009209CE /* plist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = plist.c; path = Dependencies/libplist/src/plist.c; sourceTree = SOURCE_ROOT; };
|
0EA4B9BB2AE4A3F6009209CE /* plist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = plist.c; path = Dependencies/libplist/src/plist.c; sourceTree = SOURCE_ROOT; };
|
||||||
|
0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWrappedError.m; sourceTree = "<group>"; };
|
||||||
|
0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = "<group>"; };
|
||||||
|
0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALTLocalizedError.swift; sourceTree = "<group>"; };
|
||||||
|
0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
||||||
19104DB22909C06C00C49C7B /* libEmotionalDamage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEmotionalDamage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
19104DB22909C06C00C49C7B /* libEmotionalDamage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEmotionalDamage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
19104DB42909C06D00C49C7B /* EmotionalDamage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmotionalDamage.swift; sourceTree = "<group>"; };
|
19104DB42909C06D00C49C7B /* EmotionalDamage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmotionalDamage.swift; sourceTree = "<group>"; };
|
||||||
191E5FAB290A5D92001A3B7C /* libminimuxer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
191E5FAB290A5D92001A3B7C /* libminimuxer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -785,7 +794,7 @@
|
|||||||
BFD2478B2284C4C300981D42 /* AppIconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconImageView.swift; sourceTree = "<group>"; };
|
BFD2478B2284C4C300981D42 /* AppIconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconImageView.swift; sourceTree = "<group>"; };
|
||||||
BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
||||||
BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
||||||
BFD44605241188C300EAB90A /* CodableServerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableServerError.swift; sourceTree = "<group>"; };
|
BFD44605241188C300EAB90A /* CodableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableError.swift; sourceTree = "<group>"; };
|
||||||
BFD52BD222A06EFB000B7ED1 /* ALTConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTConstants.h; sourceTree = "<group>"; };
|
BFD52BD222A06EFB000B7ED1 /* ALTConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTConstants.h; sourceTree = "<group>"; };
|
||||||
BFD52C1D22A1A9EC000B7ED1 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node.c; path = Dependencies/libplist/libcnary/node.c; sourceTree = SOURCE_ROOT; };
|
BFD52C1D22A1A9EC000B7ED1 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node.c; path = Dependencies/libplist/libcnary/node.c; sourceTree = SOURCE_ROOT; };
|
||||||
BFD52C1E22A1A9EC000B7ED1 /* node_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node_list.c; path = Dependencies/libplist/libcnary/node_list.c; sourceTree = SOURCE_ROOT; };
|
BFD52C1E22A1A9EC000B7ED1 /* node_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node_list.c; path = Dependencies/libplist/libcnary/node_list.c; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -936,6 +945,16 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
0EE7FDBF2BE8BBBF00D1E390 /* Errors */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */,
|
||||||
|
0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */,
|
||||||
|
0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */,
|
||||||
|
);
|
||||||
|
path = Errors;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
19104DB32909C06D00C49C7B /* EmotionalDamage */ = {
|
19104DB32909C06D00C49C7B /* EmotionalDamage */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1061,7 +1080,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BF1E3128229F474900370A3C /* ServerProtocol.swift */,
|
BF1E3128229F474900370A3C /* ServerProtocol.swift */,
|
||||||
BFD44605241188C300EAB90A /* CodableServerError.swift */,
|
BFD44605241188C300EAB90A /* CodableError.swift */,
|
||||||
);
|
);
|
||||||
path = "Server Protocol";
|
path = "Server Protocol";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1074,6 +1093,7 @@
|
|||||||
BF18BFFF2485A75F00DD5981 /* Server Protocol */,
|
BF18BFFF2485A75F00DD5981 /* Server Protocol */,
|
||||||
BFF767CF2489AC240097E58C /* Connections */,
|
BFF767CF2489AC240097E58C /* Connections */,
|
||||||
BFF7C92D2578464D00E55F36 /* XPC */,
|
BFF7C92D2578464D00E55F36 /* XPC */,
|
||||||
|
0EE7FDBF2BE8BBBF00D1E390 /* Errors */,
|
||||||
BFF767C32489A6800097E58C /* Extensions */,
|
BFF767C32489A6800097E58C /* Extensions */,
|
||||||
BFF767C42489A6980097E58C /* Categories */,
|
BFF767C42489A6980097E58C /* Categories */,
|
||||||
);
|
);
|
||||||
@@ -1775,6 +1795,7 @@
|
|||||||
children = (
|
children = (
|
||||||
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */,
|
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */,
|
||||||
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */,
|
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */,
|
||||||
|
0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "Error Log";
|
path = "Error Log";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1827,6 +1848,7 @@
|
|||||||
BFAECC5D2501B0BF00528F27 /* ALTConnection.h in Headers */,
|
BFAECC5D2501B0BF00528F27 /* ALTConnection.h in Headers */,
|
||||||
BF66EE942501AEBC007EE018 /* ALTAppPermission.h in Headers */,
|
BF66EE942501AEBC007EE018 /* ALTAppPermission.h in Headers */,
|
||||||
BFAECC602501B0BF00528F27 /* NSError+ALTServerError.h in Headers */,
|
BFAECC602501B0BF00528F27 /* NSError+ALTServerError.h in Headers */,
|
||||||
|
0EE7FDC82BE8CF4800D1E390 /* ALTWrappedError.h in Headers */,
|
||||||
BFAECC5E2501B0BF00528F27 /* CFNotificationName+AltStore.h in Headers */,
|
BFAECC5E2501B0BF00528F27 /* CFNotificationName+AltStore.h in Headers */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -2271,7 +2293,7 @@
|
|||||||
BF1FE358251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
BF1FE358251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
||||||
BFECAC8F24FD950E0077C41F /* Result+Conveniences.swift in Sources */,
|
BFECAC8F24FD950E0077C41F /* Result+Conveniences.swift in Sources */,
|
||||||
BF8CAE472489E772004D6CCE /* DaemonRequestHandler.swift in Sources */,
|
BF8CAE472489E772004D6CCE /* DaemonRequestHandler.swift in Sources */,
|
||||||
BFECAC8824FD950E0077C41F /* CodableServerError.swift in Sources */,
|
BFECAC8824FD950E0077C41F /* CodableError.swift in Sources */,
|
||||||
BFC712C32512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
BFC712C32512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
||||||
BFC712C52512D5F100AB5EBE /* XPCConnectionHandler.swift in Sources */,
|
BFC712C52512D5F100AB5EBE /* XPCConnectionHandler.swift in Sources */,
|
||||||
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */,
|
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */,
|
||||||
@@ -2368,8 +2390,8 @@
|
|||||||
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */,
|
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */,
|
||||||
BF580482246A28F7008AE704 /* ViewController.swift in Sources */,
|
BF580482246A28F7008AE704 /* ViewController.swift in Sources */,
|
||||||
BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */,
|
BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */,
|
||||||
|
0EE7FDC62BE8CEA300D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||||
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */,
|
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */,
|
||||||
BF58049B246A432D008AE704 /* NSError+AltStore.swift in Sources */,
|
|
||||||
BF58047E246A28F7008AE704 /* AppDelegate.swift in Sources */,
|
BF58047E246A28F7008AE704 /* AppDelegate.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -2385,7 +2407,7 @@
|
|||||||
BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */,
|
BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */,
|
||||||
BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */,
|
BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */,
|
||||||
BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */,
|
BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */,
|
||||||
BFAECC522501B0A400528F27 /* CodableServerError.swift in Sources */,
|
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */,
|
||||||
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */,
|
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */,
|
||||||
BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */,
|
BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */,
|
||||||
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */,
|
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */,
|
||||||
@@ -2398,6 +2420,7 @@
|
|||||||
BF66EE972501AEBC007EE018 /* ALTAppPermission.m in Sources */,
|
BF66EE972501AEBC007EE018 /* ALTAppPermission.m in Sources */,
|
||||||
BFAECC552501B0A400528F27 /* Connection.swift in Sources */,
|
BFAECC552501B0A400528F27 /* Connection.swift in Sources */,
|
||||||
BF66EEDA2501AECA007EE018 /* RefreshAttempt.swift in Sources */,
|
BF66EEDA2501AECA007EE018 /* RefreshAttempt.swift in Sources */,
|
||||||
|
0EE7FDCB2BE8D12B00D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||||
BF66EEA92501AEC5007EE018 /* Tier.swift in Sources */,
|
BF66EEA92501AEC5007EE018 /* Tier.swift in Sources */,
|
||||||
BF66EEDB2501AECA007EE018 /* StoreApp.swift in Sources */,
|
BF66EEDB2501AECA007EE018 /* StoreApp.swift in Sources */,
|
||||||
BF66EEDE2501AECA007EE018 /* AppID.swift in Sources */,
|
BF66EEDE2501AECA007EE018 /* AppID.swift in Sources */,
|
||||||
@@ -2406,6 +2429,7 @@
|
|||||||
BF66EEDD2501AECA007EE018 /* AppPermission.swift in Sources */,
|
BF66EEDD2501AECA007EE018 /* AppPermission.swift in Sources */,
|
||||||
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */,
|
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */,
|
||||||
BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */,
|
BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */,
|
||||||
|
0EE7FDC72BE8CF4100D1E390 /* ALTWrappedError.m in Sources */,
|
||||||
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
||||||
BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */,
|
BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */,
|
||||||
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitType.m in Sources */,
|
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitType.m in Sources */,
|
||||||
@@ -2430,6 +2454,7 @@
|
|||||||
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */,
|
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */,
|
||||||
BF66EECC2501AECA007EE018 /* Source.swift in Sources */,
|
BF66EECC2501AECA007EE018 /* Source.swift in Sources */,
|
||||||
BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */,
|
BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */,
|
||||||
|
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */,
|
||||||
BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */,
|
BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */,
|
||||||
BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
||||||
BF66EEA62501AEC5007EE018 /* PatreonAPI.swift in Sources */,
|
BF66EEA62501AEC5007EE018 /* PatreonAPI.swift in Sources */,
|
||||||
@@ -2485,7 +2510,6 @@
|
|||||||
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
||||||
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
||||||
BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */,
|
BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */,
|
||||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */,
|
|
||||||
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */,
|
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */,
|
||||||
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
||||||
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
||||||
@@ -2533,9 +2557,11 @@
|
|||||||
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
||||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||||
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
||||||
|
0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||||
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
|
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
|
||||||
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
||||||
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
||||||
|
0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */,
|
||||||
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
||||||
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
||||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
{
|
|
||||||
"pins" : [
|
|
||||||
{
|
|
||||||
"identity" : "altsign",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/SideStore/AltSign",
|
|
||||||
"state" : {
|
|
||||||
"branch" : "master",
|
|
||||||
"revision" : "cc6189f0f7cd8e5bd24943af9322e0ff9420e9f4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "appcenter-sdk-apple",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "b2dc99cfedead0bad4e6573d86c5228c89cff332",
|
|
||||||
"version" : "4.4.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "imobiledevice.swift",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/SideStore/iMobileDevice.swift",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "74e481106dd155c0cd21bca6795fd9fe5f751654",
|
|
||||||
"version" : "1.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "keychainaccess",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
|
||||||
"version" : "4.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "launchatlogin",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/sindresorhus/LaunchAtLogin.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "e8171b3e38a2816f579f58f3dac1522aa39efe41",
|
|
||||||
"version" : "4.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "nuke",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/kean/Nuke.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "9318d02a8a6d20af56505c9673261c1fd3b3aebe",
|
|
||||||
"version" : "7.6.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "openssl",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/krzyzanowskim/OpenSSL",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "0faf71a188bcfdf0245cab42886b9b240ca71c52",
|
|
||||||
"version" : "1.1.2200"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "plcrashreporter",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/microsoft/PLCrashReporter.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "81cdec2b3827feb03286cb297f4c501a8eb98df1",
|
|
||||||
"version" : "1.10.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "semanticversion",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "a70840d5fca686ae3bd2fcf8aecc5ded0bd4f125",
|
|
||||||
"version" : "0.3.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "sparkle",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/sparkle-project/Sparkle.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "f0ceaf5cc9f3f23daa0ccb6dcebd79fc96ccc7d9",
|
|
||||||
"version" : "2.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "starscream",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/daltoniam/Starscream.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "ac6c0fc9da221873e01bd1a0d4818498a71eef33",
|
|
||||||
"version" : "4.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "stprivilegedtask",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/JoeMatt/STPrivilegedTask.git",
|
|
||||||
"state" : {
|
|
||||||
"branch" : "master",
|
|
||||||
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version" : 2
|
|
||||||
}
|
|
||||||
@@ -108,8 +108,8 @@ private extension AuthenticationViewController
|
|||||||
|
|
||||||
case .failure(let error as NSError):
|
case .failure(let error as NSError):
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let error = error.withLocalizedFailure(NSLocalizedString("Failed to Log In", comment: ""))
|
let error = error.withLocalizedTitle(NSLocalizedString("Failed to Log In", comment: ""))
|
||||||
|
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.textLabel.textColor = .altPink
|
toastView.textLabel.textColor = .altPink
|
||||||
toastView.detailTextLabel.textColor = .altPink
|
toastView.detailTextLabel.textColor = .altPink
|
||||||
|
|||||||
@@ -51,45 +51,28 @@ final class ToastView: RSTToastView
|
|||||||
var error = error as NSError
|
var error = error as NSError
|
||||||
var underlyingError = error.underlyingError
|
var underlyingError = error.underlyingError
|
||||||
|
|
||||||
var preferredDuration: TimeInterval?
|
|
||||||
|
|
||||||
if
|
if
|
||||||
let unwrappedUnderlyingError = underlyingError,
|
let unwrappedUnderlyingError = underlyingError,
|
||||||
error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue
|
error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue
|
||||||
{
|
{
|
||||||
// Treat underlyingError as the primary error.
|
// Treat underlyingError as the primary error, but keep localized title + failure.
|
||||||
|
let nsError = error as NSError
|
||||||
error = unwrappedUnderlyingError as NSError
|
error = unwrappedUnderlyingError as NSError
|
||||||
|
|
||||||
|
if let localizedTitle = nsError.localizedTitle {
|
||||||
|
error = error.withLocalizedTitle(localizedTitle)
|
||||||
|
}
|
||||||
|
if let localizedFailure = nsError.localizedFailure {
|
||||||
|
error = error.withLocalizedFailure(localizedFailure)
|
||||||
|
}
|
||||||
|
|
||||||
underlyingError = nil
|
underlyingError = nil
|
||||||
|
|
||||||
preferredDuration = .longToastViewDuration
|
|
||||||
}
|
}
|
||||||
|
let text = error.localizedTitle ?? NSLocalizedString("Operation Failed", comment: "")
|
||||||
let text: String
|
let detailText = error.localizedDescription
|
||||||
let detailText: String?
|
|
||||||
|
|
||||||
if let failure = error.localizedFailure
|
|
||||||
{
|
|
||||||
text = failure
|
|
||||||
detailText = error.localizedFailureReason ?? error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription ?? error.localizedDescription
|
|
||||||
}
|
|
||||||
else if let reason = error.localizedFailureReason
|
|
||||||
{
|
|
||||||
text = reason
|
|
||||||
detailText = error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text = error.localizedDescription
|
|
||||||
detailText = underlyingError?.localizedDescription ?? error.localizedRecoverySuggestion
|
|
||||||
}
|
|
||||||
|
|
||||||
self.init(text: text, detailText: detailText)
|
self.init(text: text, detailText: detailText)
|
||||||
|
|
||||||
if let preferredDuration = preferredDuration
|
|
||||||
{
|
|
||||||
self.preferredDuration = preferredDuration
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ private extension IntentHandler
|
|||||||
|
|
||||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||||
}
|
}
|
||||||
catch RefreshError.noInstalledApps
|
catch ~RefreshErrorCode.noInstalledApps
|
||||||
{
|
{
|
||||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ extension AppManager
|
|||||||
case .success(let source): fetchedSources.insert(source)
|
case .success(let source): fetchedSources.insert(source)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let source = managedObjectContext.object(with: source.objectID) as! Source
|
let source = managedObjectContext.object(with: source.objectID) as! Source
|
||||||
source.error = (error as NSError).sanitizedForCoreData()
|
source.error = (error as NSError).sanitizedForSerialization()
|
||||||
errors[source] = error
|
errors[source] = error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw context.error ?? OperationError.unknown }
|
guard let result = results.values.first else { throw context.error ?? OperationError.unknown() }
|
||||||
completionHandler(result)
|
completionHandler(result)
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -476,7 +476,7 @@ extension AppManager
|
|||||||
func update(_ app: InstalledApp, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
func update(_ app: InstalledApp, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
||||||
{
|
{
|
||||||
guard let storeApp = app.storeApp else {
|
guard let storeApp = app.storeApp else {
|
||||||
completionHandler(.failure(OperationError.appNotFound))
|
completionHandler(.failure(OperationError.appNotFound(name: app.name)))
|
||||||
return Progress.discreteProgress(totalUnitCount: 1)
|
return Progress.discreteProgress(totalUnitCount: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
completionHandler(result)
|
completionHandler(result)
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -520,8 +520,8 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
assert(installedApp.managedObjectContext != nil)
|
assert(installedApp.managedObjectContext != nil)
|
||||||
|
|
||||||
@@ -559,7 +559,7 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
assert(installedApp.managedObjectContext != nil)
|
assert(installedApp.managedObjectContext != nil)
|
||||||
@@ -585,8 +585,8 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
assert(installedApp.managedObjectContext != nil)
|
assert(installedApp.managedObjectContext != nil)
|
||||||
|
|
||||||
@@ -610,7 +610,7 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
assert(installedApp.managedObjectContext != nil)
|
assert(installedApp.managedObjectContext != nil)
|
||||||
@@ -1269,7 +1269,7 @@ private extension AppManager
|
|||||||
case .success(let installedApp):
|
case .success(let installedApp):
|
||||||
completionHandler(.success(installedApp))
|
completionHandler(.success(installedApp))
|
||||||
|
|
||||||
case .failure(ALTServerError.unknownRequest), .failure(OperationError.appNotFound):
|
case .failure(ALTServerError.unknownRequest), .failure(OperationError.appNotFound(name: app.name)):
|
||||||
// Fall back to installation if AltServer doesn't support newer provisioning profile requests,
|
// Fall back to installation if AltServer doesn't support newer provisioning profile requests,
|
||||||
// OR if the cached app could not be found and we may need to redownload it.
|
// OR if the cached app could not be found and we may need to redownload it.
|
||||||
app.managedObjectContext?.performAndWait { // Must performAndWait to ensure we add operations before we return.
|
app.managedObjectContext?.performAndWait { // Must performAndWait to ensure we add operations before we return.
|
||||||
@@ -1543,7 +1543,7 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard let application = ALTApplication(fileURL: app.fileURL) else {
|
guard let application = ALTApplication(fileURL: app.fileURL) else {
|
||||||
completionHandler(.failure(OperationError.appNotFound))
|
completionHandler(.failure(OperationError.appNotFound(name: app.name)))
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1555,8 +1555,8 @@ private extension AppManager
|
|||||||
let temporaryDirectoryURL = context.temporaryDirectory.appendingPathComponent("AltBackup-" + UUID().uuidString)
|
let temporaryDirectoryURL = context.temporaryDirectory.appendingPathComponent("AltBackup-" + UUID().uuidString)
|
||||||
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
|
||||||
guard let altbackupFileURL = Bundle.main.url(forResource: "AltBackup", withExtension: "ipa") else { throw OperationError.appNotFound }
|
guard let altbackupFileURL = Bundle.main.url(forResource: "AltBackup", withExtension: "ipa") else { throw OperationError.appNotFound(name: app.name) }
|
||||||
|
|
||||||
let unzippedAppBundleURL = try FileManager.default.unzipAppBundle(at: altbackupFileURL, toDirectory: temporaryDirectoryURL)
|
let unzippedAppBundleURL = try FileManager.default.unzipAppBundle(at: altbackupFileURL, toDirectory: temporaryDirectoryURL)
|
||||||
guard let unzippedAppBundle = Bundle(url: unzippedAppBundleURL) else { throw OperationError.invalidApp }
|
guard let unzippedAppBundle = Bundle(url: unzippedAppBundleURL) else { throw OperationError.invalidApp }
|
||||||
|
|
||||||
@@ -1694,8 +1694,32 @@ private extension AppManager
|
|||||||
do { try installedApp.managedObjectContext?.save() }
|
do { try installedApp.managedObjectContext?.save() }
|
||||||
catch { print("Error saving installed app.", error) }
|
catch { print("Error saving installed app.", error) }
|
||||||
}
|
}
|
||||||
catch
|
catch let nsError as NSError
|
||||||
{
|
{
|
||||||
|
var appName: String!
|
||||||
|
if let app = operation.app as? (NSManagedObject & AppProtocol) {
|
||||||
|
if let context = app.managedObjectContext {
|
||||||
|
context.performAndWait {
|
||||||
|
appName = app.name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appName = NSLocalizedString("Unknown App", comment: "")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appName = operation.app.name
|
||||||
|
}
|
||||||
|
|
||||||
|
let localizedTitle: String
|
||||||
|
switch operation {
|
||||||
|
case .install: localizedTitle = String(format: NSLocalizedString("Failed to Install %@", comment: ""), appName)
|
||||||
|
case .refresh: localizedTitle = String(format: NSLocalizedString("Failed to Refresh %@", comment: ""), appName)
|
||||||
|
case .update: localizedTitle = String(format: NSLocalizedString("Failed to Update %@", comment: ""), appName)
|
||||||
|
case .activate: localizedTitle = String(format: NSLocalizedString("Failed to Activate %@", comment: ""), appName)
|
||||||
|
case .deactivate: localizedTitle = String(format: NSLocalizedString("Failed to Deactivate %@", comment: ""), appName)
|
||||||
|
case .backup: localizedTitle = String(format: NSLocalizedString("Failed to Backup %@", comment: ""), appName)
|
||||||
|
case .restore: localizedTitle = String(format: NSLocalizedString("Failed to Restore %@ Backup", comment: ""), appName)
|
||||||
|
}
|
||||||
|
let error = nsError.withLocalizedTitle(localizedTitle)
|
||||||
group.set(.failure(error), forAppWithBundleIdentifier: operation.bundleIdentifier)
|
group.set(.failure(error), forAppWithBundleIdentifier: operation.bundleIdentifier)
|
||||||
|
|
||||||
self.log(error, for: operation)
|
self.log(error, for: operation)
|
||||||
@@ -1726,7 +1750,7 @@ private extension AppManager
|
|||||||
func log(_ error: Error, for operation: AppOperation)
|
func log(_ error: Error, for operation: AppOperation)
|
||||||
{
|
{
|
||||||
// Sanitize NSError on same thread before performing background task.
|
// Sanitize NSError on same thread before performing background task.
|
||||||
let sanitizedError = (error as NSError).sanitizedForCoreData()
|
let sanitizedError = (error as NSError).sanitizedForSerialization()
|
||||||
|
|
||||||
let loggedErrorOperation: LoggedError.Operation = {
|
let loggedErrorOperation: LoggedError.Operation = {
|
||||||
switch operation
|
switch operation
|
||||||
|
|||||||
@@ -22,13 +22,27 @@ extension AppManager
|
|||||||
|
|
||||||
var managedObjectContext: NSManagedObjectContext?
|
var managedObjectContext: NSManagedObjectContext?
|
||||||
|
|
||||||
var errorDescription: String? {
|
var localizedTitle: String? {
|
||||||
if let error = self.primaryError
|
var localizedTitle: String?
|
||||||
{
|
self.managedObjectContext?.performAndWait {
|
||||||
return error.localizedDescription
|
if self.sources?.count == 1 {
|
||||||
|
localizedTitle = NSLocalizedString("Failed to refresh Store", comment: "")
|
||||||
|
} else if self.errors.count == 1 {
|
||||||
|
guard let source = self.errors.keys.first else { return }
|
||||||
|
localizedTitle = String(format: NSLocalizedString("Failed to refresh Source '%@'", comment: ""), source.name)
|
||||||
|
} else {
|
||||||
|
localizedTitle = String(format: NSLocalizedString("Failed to refresh %@ Sources", comment: ""), NSNumber(value: self.errors.count))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
return localizedTitle
|
||||||
{
|
}
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
if let error = self.primaryError {
|
||||||
|
return error.localizedDescription
|
||||||
|
} else if let error = self.errors.values.first, self.errors.count == 1 {
|
||||||
|
return error.localizedDescription
|
||||||
|
} else {
|
||||||
var localizedDescription: String?
|
var localizedDescription: String?
|
||||||
|
|
||||||
self.managedObjectContext?.performAndWait {
|
self.managedObjectContext?.performAndWait {
|
||||||
@@ -67,8 +81,14 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
var errorUserInfo: [String : Any] {
|
var errorUserInfo: [String : Any] {
|
||||||
guard let error = self.errors.values.first, self.errors.count == 1 else { return [:] }
|
let errors = Array(self.errors.values)
|
||||||
return [NSUnderlyingErrorKey: error]
|
var userInfo = [String: Any]()
|
||||||
|
userInfo[ALTLocalizedTitleErrorKey] = self.localizedTitle
|
||||||
|
userInfo[NSUnderlyingErrorKey] = self.primaryError
|
||||||
|
if #available(iOS 14.5, *), !errors.isEmpty {
|
||||||
|
userInfo[NSMultipleUnderlyingErrorsKey] = errors
|
||||||
|
}
|
||||||
|
return userInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ error: Error)
|
init(_ error: Error)
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import AltStoreCore
|
|||||||
import AltSign
|
import AltSign
|
||||||
import minimuxer
|
import minimuxer
|
||||||
|
|
||||||
enum AuthenticationError: LocalizedError
|
typealias AuthenticationError = AuthenticationErrorCode.Error
|
||||||
|
enum AuthenticationErrorCode: Int, ALTErrorEnum, CaseIterable
|
||||||
{
|
{
|
||||||
case noTeam
|
case noTeam
|
||||||
case noCertificate
|
case noCertificate
|
||||||
@@ -23,11 +24,11 @@ enum AuthenticationError: LocalizedError
|
|||||||
case missingPrivateKey
|
case missingPrivateKey
|
||||||
case missingCertificate
|
case missingCertificate
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorFailureReason: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
|
case .noTeam: return NSLocalizedString("Your Apple ID has no developer teams?", comment: "")
|
||||||
|
case .noCertificate: return NSLocalizedString("The developer certificate could not be found.", comment: "")
|
||||||
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
|
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
|
||||||
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
|
|
||||||
case .missingPrivateKey: return NSLocalizedString("The certificate's private key could not be found.", comment: "")
|
case .missingPrivateKey: return NSLocalizedString("The certificate's private key could not be found.", comment: "")
|
||||||
case .missingCertificate: return NSLocalizedString("The certificate could not be found.", comment: "")
|
case .missingCertificate: return NSLocalizedString("The certificate could not be found.", comment: "")
|
||||||
}
|
}
|
||||||
@@ -213,8 +214,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
guard
|
guard
|
||||||
let account = Account.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Account.identifier), altTeam.account.identifier), in: context),
|
let account = Account.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Account.identifier), altTeam.account.identifier), in: context),
|
||||||
let team = Team.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Team.identifier), altTeam.identifier), in: context)
|
let team = Team.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Team.identifier), altTeam.identifier), in: context)
|
||||||
else { throw AuthenticationError.noTeam }
|
else { throw AuthenticationError(.noTeam) }
|
||||||
|
|
||||||
// Account
|
// Account
|
||||||
account.isActiveAccount = true
|
account.isActiveAccount = true
|
||||||
|
|
||||||
@@ -432,7 +433,7 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
completionHandler(.failure(error ?? OperationError.unknown))
|
completionHandler(.failure(error ?? OperationError.unknown()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -449,7 +450,7 @@ private extension AuthenticationOperation
|
|||||||
if let team = teams.first {
|
if let team = teams.first {
|
||||||
return completionHandler(.success(team))
|
return completionHandler(.success(team))
|
||||||
} else {
|
} else {
|
||||||
return completionHandler(.failure(AuthenticationError.noTeam))
|
return completionHandler(.failure(AuthenticationError(.noTeam)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@@ -460,7 +461,7 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
if !self.present(selectTeamViewController)
|
if !self.present(selectTeamViewController)
|
||||||
{
|
{
|
||||||
return completionHandler(.failure(AuthenticationError.noTeam))
|
return completionHandler(.failure(AuthenticationError(.noTeam)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,15 +495,15 @@ private extension AuthenticationOperation
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
let certificate = try Result(certificate, error).get()
|
let certificate = try Result(certificate, error).get()
|
||||||
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
|
guard let privateKey = certificate.privateKey else { throw AuthenticationError(.missingPrivateKey) }
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let certificates = try Result(certificates, error).get()
|
let certificates = try Result(certificates, error).get()
|
||||||
|
|
||||||
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
|
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
|
||||||
throw AuthenticationError.missingCertificate
|
throw AuthenticationError(.missingCertificate)
|
||||||
}
|
}
|
||||||
|
|
||||||
certificate.privateKey = privateKey
|
certificate.privateKey = privateKey
|
||||||
@@ -523,7 +524,7 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
func replaceCertificate(from certificates: [ALTCertificate])
|
func replaceCertificate(from certificates: [ALTCertificate])
|
||||||
{
|
{
|
||||||
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
|
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError(.noCertificate))) }
|
||||||
|
|
||||||
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
|
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
|
||||||
if let error = error, !success
|
if let error = error, !success
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ import AltStoreCore
|
|||||||
import EmotionalDamage
|
import EmotionalDamage
|
||||||
import minimuxer
|
import minimuxer
|
||||||
|
|
||||||
enum RefreshError: LocalizedError
|
typealias RefreshError = RefreshErrorCode.Error
|
||||||
|
enum RefreshErrorCode: Int, ALTErrorEnum, CaseIterable
|
||||||
{
|
{
|
||||||
case noInstalledApps
|
case noInstalledApps
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorFailureReason: String {
|
||||||
switch self
|
switch self
|
||||||
{
|
{
|
||||||
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
||||||
@@ -94,7 +95,7 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
|||||||
super.main()
|
super.main()
|
||||||
|
|
||||||
guard !self.installedApps.isEmpty else {
|
guard !self.installedApps.isEmpty else {
|
||||||
self.finish(.failure(RefreshError.noInstalledApps))
|
self.finish(.failure(RefreshError(.noInstalledApps)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||||
@@ -202,7 +203,7 @@ private extension BackgroundRefreshAppsOperation
|
|||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
|
|
||||||
var shouldPresentAlert = false
|
var shouldPresentAlert = true
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -218,20 +219,18 @@ private extension BackgroundRefreshAppsOperation
|
|||||||
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
||||||
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
||||||
}
|
}
|
||||||
catch RefreshError.noInstalledApps
|
catch ~OperationError.Code.noWiFi, ~RefreshErrorCode.noInstalledApps
|
||||||
{
|
{
|
||||||
shouldPresentAlert = false
|
shouldPresentAlert = false
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
print("Failed to refresh apps in background.", error)
|
print("Failed to refresh apps in background.", error)
|
||||||
|
|
||||||
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||||
content.body = error.localizedDescription
|
content.body = error.localizedDescription
|
||||||
|
|
||||||
shouldPresentAlert = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldPresentAlert
|
if shouldPresentAlert
|
||||||
{
|
{
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
||||||
|
|||||||
@@ -55,12 +55,15 @@ class BackupAppOperation: ResultOperation<Void>
|
|||||||
let appName = installedApp.name
|
let appName = installedApp.name
|
||||||
self.appName = appName
|
self.appName = appName
|
||||||
|
|
||||||
let altstoreOpenURL = URL(string: "sidestore://")!
|
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else {
|
||||||
|
throw OperationError.appNotFound(name: appName)
|
||||||
|
}
|
||||||
|
let altstoreOpenURL = altstoreApp.openAppURL
|
||||||
|
|
||||||
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
||||||
returnURLComponents?.host = "appBackupResponse"
|
returnURLComponents?.host = "appBackupResponse"
|
||||||
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
||||||
|
|
||||||
var openURLComponents = URLComponents()
|
var openURLComponents = URLComponents()
|
||||||
openURLComponents.scheme = installedApp.openAppURL.scheme
|
openURLComponents.scheme = installedApp.openAppURL.scheme
|
||||||
openURLComponents.host = self.action.rawValue
|
openURLComponents.host = self.action.rawValue
|
||||||
|
|||||||
@@ -12,29 +12,13 @@ import Roxas
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
private extension DownloadAppOperation
|
|
||||||
{
|
|
||||||
struct DependencyError: ALTLocalizedError
|
|
||||||
{
|
|
||||||
let dependency: Dependency
|
|
||||||
let error: Error
|
|
||||||
|
|
||||||
var failure: String? {
|
|
||||||
return String(format: NSLocalizedString("Could not download “%@”.", comment: ""), self.dependency.preferredFilename)
|
|
||||||
}
|
|
||||||
|
|
||||||
var underlyingError: Error? {
|
|
||||||
return self.error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc(DownloadAppOperation)
|
@objc(DownloadAppOperation)
|
||||||
final class DownloadAppOperation: ResultOperation<ALTApplication>
|
final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||||
{
|
{
|
||||||
let app: AppProtocol
|
let app: AppProtocol
|
||||||
let context: AppOperationContext
|
let context: AppOperationContext
|
||||||
|
|
||||||
|
private let appName: String
|
||||||
private let bundleIdentifier: String
|
private let bundleIdentifier: String
|
||||||
private var sourceURL: URL?
|
private var sourceURL: URL?
|
||||||
private let destinationURL: URL
|
private let destinationURL: URL
|
||||||
@@ -47,6 +31,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
|
|||||||
self.app = app
|
self.app = app
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
|
self.appName = app.name
|
||||||
self.bundleIdentifier = app.bundleIdentifier
|
self.bundleIdentifier = app.bundleIdentifier
|
||||||
self.sourceURL = app.url
|
self.sourceURL = app.url
|
||||||
self.destinationURL = destinationURL
|
self.destinationURL = destinationURL
|
||||||
@@ -69,8 +54,8 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
|
|||||||
|
|
||||||
print("Downloading App:", self.bundleIdentifier)
|
print("Downloading App:", self.bundleIdentifier)
|
||||||
|
|
||||||
guard let sourceURL = self.sourceURL else { return self.finish(.failure(OperationError.appNotFound)) }
|
guard let sourceURL = self.sourceURL else { return self.finish(.failure(OperationError.appNotFound(name: self.appName))) }
|
||||||
|
|
||||||
self.downloadApp(from: sourceURL) { result in
|
self.downloadApp(from: sourceURL) { result in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -138,8 +123,8 @@ private extension DownloadAppOperation
|
|||||||
let fileURL = try result.get()
|
let fileURL = try result.get()
|
||||||
|
|
||||||
var isDirectory: ObjCBool = false
|
var isDirectory: ObjCBool = false
|
||||||
guard FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory) else { throw OperationError.appNotFound }
|
guard FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory) else { throw OperationError.appNotFound(name: self.appName) }
|
||||||
|
|
||||||
try FileManager.default.createDirectory(at: self.temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
try FileManager.default.createDirectory(at: self.temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
|
||||||
let appBundleURL: URL
|
let appBundleURL: URL
|
||||||
@@ -252,7 +237,7 @@ private extension DownloadAppOperation
|
|||||||
let altstorePlist = try PropertyListDecoder().decode(AltStorePlist.self, from: data)
|
let altstorePlist = try PropertyListDecoder().decode(AltStorePlist.self, from: data)
|
||||||
|
|
||||||
var dependencyURLs = Set<URL>()
|
var dependencyURLs = Set<URL>()
|
||||||
var dependencyError: DependencyError?
|
var dependencyError: Error?
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
let progress = Progress(totalUnitCount: Int64(altstorePlist.dependencies.count), parent: self.progress, pendingUnitCount: 1)
|
let progress = Progress(totalUnitCount: Int64(altstorePlist.dependencies.count), parent: self.progress, pendingUnitCount: 1)
|
||||||
@@ -285,7 +270,7 @@ private extension DownloadAppOperation
|
|||||||
}
|
}
|
||||||
catch let error as DecodingError
|
catch let error as DecodingError
|
||||||
{
|
{
|
||||||
let nsError = (error as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not download dependencies for %@.", comment: ""), application.name))
|
let nsError = (error as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not determine dependencies for %@.", comment: ""), application.name))
|
||||||
completionHandler(.failure(nsError))
|
completionHandler(.failure(nsError))
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -294,7 +279,7 @@ private extension DownloadAppOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func download(_ dependency: Dependency, for application: ALTApplication, progress: Progress, completionHandler: @escaping (Result<URL, DependencyError>) -> Void)
|
func download(_ dependency: Dependency, for application: ALTApplication, progress: Progress, completionHandler: @escaping (Result<URL, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let downloadTask = self.session.downloadTask(with: dependency.downloadURL) { (fileURL, response, error) in
|
let downloadTask = self.session.downloadTask(with: dependency.downloadURL) { (fileURL, response, error) in
|
||||||
do
|
do
|
||||||
@@ -315,9 +300,10 @@ private extension DownloadAppOperation
|
|||||||
|
|
||||||
completionHandler(.success(destinationURL))
|
completionHandler(.success(destinationURL))
|
||||||
}
|
}
|
||||||
catch
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
completionHandler(.failure(DependencyError(dependency: dependency, error: error)))
|
let localizedFailure = String(format: NSLocalizedString("The dependency '%@' could not be downloaded.", comment: ""), dependency.preferredFilename)
|
||||||
|
completionHandler(.failure(error.withLocalizedFailure(localizedFailure)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
progress.addChild(downloadTask.progress, withPendingUnitCount: 1)
|
progress.addChild(downloadTask.progress, withPendingUnitCount: 1)
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProv
|
|||||||
let session = self.context.session
|
let session = self.context.session
|
||||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
|
|
||||||
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound)) }
|
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound(name: nil))) }
|
||||||
|
|
||||||
self.progress.totalUnitCount = Int64(1 + app.appExtensions.count)
|
self.progress.totalUnitCount = Int64(1 + app.appExtensions.count)
|
||||||
|
|
||||||
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
||||||
@@ -260,7 +260,7 @@ extension FetchProvisioningProfilesOperation
|
|||||||
{
|
{
|
||||||
if let expirationDate = sortedExpirationDates.first
|
if let expirationDate = sortedExpirationDates.first
|
||||||
{
|
{
|
||||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
throw OperationError.maximumAppIDLimitReached(appName: application.name, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ extension FetchProvisioningProfilesOperation
|
|||||||
{
|
{
|
||||||
if let expirationDate = sortedExpirationDates.first
|
if let expirationDate = sortedExpirationDates.first
|
||||||
{
|
{
|
||||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
throw OperationError.maximumAppIDLimitReached(appName: application.name, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,81 +8,182 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import AltStoreCore
|
||||||
import minimuxer
|
import minimuxer
|
||||||
|
|
||||||
enum OperationError: LocalizedError
|
extension OperationError
|
||||||
{
|
{
|
||||||
static let domain = OperationError.unknown._domain
|
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||||
|
typealias Error = OperationError
|
||||||
case unknown
|
|
||||||
case unabletoconnectSideJIT
|
// General
|
||||||
case unabletoconSideJITDevice
|
case unknown = 1000
|
||||||
case wrongIP
|
case unknownResult
|
||||||
case SideJITIssue(error: String)
|
case cancelled
|
||||||
case refreshsidejit
|
case timedOut
|
||||||
case unknownResult
|
case unabletoconnectSideJIT
|
||||||
case cancelled
|
case unabletoconSideJITDevice
|
||||||
case timedOut
|
case wrongIP
|
||||||
|
case SideJITIssue(error: String)
|
||||||
case notAuthenticated
|
case refreshsidejit
|
||||||
case appNotFound
|
case notAuthenticated
|
||||||
|
case appNotFound
|
||||||
case unknownUDID
|
case unknownUDID
|
||||||
|
case invalidApp
|
||||||
case invalidApp
|
case invalidParameters
|
||||||
case invalidParameters
|
case maximumAppIDLimitReached//((application: ALTApplication, requiredAppIDs: Int, availableAppIDs: Int, nextExpirationDate: Date)
|
||||||
|
case noSources
|
||||||
case maximumAppIDLimitReached(application: ALTApplication, requiredAppIDs: Int, availableAppIDs: Int, nextExpirationDate: Date)
|
case openAppFailed//(name: String)
|
||||||
|
case missingAppGroup
|
||||||
case noSources
|
|
||||||
|
// Connection
|
||||||
case openAppFailed(name: String)
|
case noWiFi = 1200
|
||||||
case missingAppGroup
|
case tooNewError
|
||||||
|
case anisetteV1Error//(message: String)
|
||||||
case noWiFi
|
case provisioningError//(result: String, message: String?)
|
||||||
case tooNewError
|
case anisetteV3Error//(message: String)
|
||||||
case anisetteV1Error(message: String)
|
|
||||||
case provisioningError(result: String, message: String?)
|
case cacheClearError//(errors: [String])
|
||||||
case anisetteV3Error(message: String)
|
}
|
||||||
|
|
||||||
case cacheClearError(errors: [String])
|
static let unknownResult: OperationError = .init(code: .unknownResult)
|
||||||
|
static let cancelled: OperationError = .init(code: .cancelled)
|
||||||
var failureReason: String? {
|
static let timedOut: OperationError = .init(code: .timedOut)
|
||||||
switch self {
|
static let notAuthenticated: OperationError = .init(code: .notAuthenticated)
|
||||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
static let unknownUDID: OperationError = .init(code: .unknownUDID)
|
||||||
|
static let invalidApp: OperationError = .init(code: .invalidApp)
|
||||||
|
static let invalidParameters: OperationError = .init(code: .invalidParameters)
|
||||||
|
static let noSources: OperationError = .init(code: .noSources)
|
||||||
|
static let missingAppGroup: OperationError = .init(code: .missingAppGroup)
|
||||||
|
|
||||||
|
static let noWiFi: OperationError = .init(code: .missingAppGroup)
|
||||||
|
static let tooNewError: OperationError = .init(code: .tooNewError)
|
||||||
|
static let provisioningError: OperationError = .init(code: .provisioningError)
|
||||||
|
static let anisetteV1Error: OperationError = .init(code: .anisetteV1Error)
|
||||||
|
static let anisetteV3Error: OperationError = .init(code: .anisetteV3Error)
|
||||||
|
|
||||||
|
static let cacheClearError: OperationError = .init(code: .cacheClearError)
|
||||||
|
|
||||||
|
static func unknown(failureReason: String? = nil, file: String = #fileID, line: UInt = #line) -> OperationError {
|
||||||
|
OperationError(code: .unknown, failureReason: failureReason, sourceFile: file, sourceLine: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func appNotFound(name: String?) -> OperationError {
|
||||||
|
OperationError(code: .appNotFound, appName: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func openAppFailed(name: String?) -> OperationError {
|
||||||
|
OperationError(code: .openAppFailed, appName: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func maximumAppIDLimitReached(appName: String, requiredAppIDs: Int, availableAppIDs: Int, expirationDate: Date) -> OperationError {
|
||||||
|
OperationError(code: .maximumAppIDLimitReached, appName: appName, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func provisioningError(result: String, message: String?) -> OperationError {
|
||||||
|
var o = OperationError(code: .provisioningError, failureReason: result)
|
||||||
|
o.errorTitle = message
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
static func cacheClearError(errors: [String]) -> OperationError {
|
||||||
|
OperationError(code: .cacheClearError, failureReason: errors.joined(separator: "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func anisetteV1Error(message: String) -> OperationError {
|
||||||
|
OperationError(code: .anisetteV1Error, failureReason: message)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func anisetteV3Error(message: String) -> OperationError {
|
||||||
|
OperationError(code: .anisetteV3Error, failureReason: message)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct OperationError: ALTLocalizedError {
|
||||||
|
|
||||||
|
let code: Code
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
var appName: String?
|
||||||
|
|
||||||
|
var requiredAppIDs: Int?
|
||||||
|
var availableAppIDs: Int?
|
||||||
|
var expirationDate: Date?
|
||||||
|
|
||||||
|
var sourceFile: String?
|
||||||
|
var sourceLine: UInt?
|
||||||
|
|
||||||
|
private var _failureReason: String?
|
||||||
|
|
||||||
|
private init(code: Code, failureReason: String? = nil,
|
||||||
|
appName: String? = nil, requiredAppIDs: Int? = nil, availableAppIDs: Int? = nil,
|
||||||
|
expirationDate: Date? = nil, sourceFile: String? = nil, sourceLine: UInt? = nil){
|
||||||
|
self.code = code
|
||||||
|
self._failureReason = failureReason
|
||||||
|
|
||||||
|
self.appName = appName
|
||||||
|
self.requiredAppIDs = requiredAppIDs
|
||||||
|
self.availableAppIDs = availableAppIDs
|
||||||
|
self.expirationDate = expirationDate
|
||||||
|
self.sourceFile = sourceFile
|
||||||
|
self.sourceLine = sourceLine
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorFailureReason: String {
|
||||||
|
switch self.code {
|
||||||
|
case .unknown:
|
||||||
|
var failureReason = self._failureReason ?? NSLocalizedString("An unknown error occurred.", comment: "")
|
||||||
|
guard let sourceFile, let sourceLine else { return failureReason }
|
||||||
|
failureReason += " (\(sourceFile) line \(sourceLine)"
|
||||||
|
return failureReason
|
||||||
case .unknownResult: return NSLocalizedString("The operation returned an unknown result.", comment: "")
|
case .unknownResult: return NSLocalizedString("The operation returned an unknown result.", comment: "")
|
||||||
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
|
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
|
||||||
case .timedOut: return NSLocalizedString("The operation timed out.", comment: "")
|
case .timedOut: return NSLocalizedString("The operation timed out.", comment: "")
|
||||||
case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "")
|
case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "")
|
||||||
case .appNotFound: return NSLocalizedString("App not found.", comment: "")
|
case .unknownUDID: return NSLocalizedString("SideStore could not determine this device's UDID.", comment: "")
|
||||||
case .unknownUDID: return NSLocalizedString("Unknown device UDID.", comment: "")
|
case .invalidApp: return NSLocalizedString("The app is in an invalid format.", comment: "")
|
||||||
case .invalidApp: return NSLocalizedString("The app is invalid.", comment: "")
|
|
||||||
case .invalidParameters: return NSLocalizedString("Invalid parameters.", comment: "")
|
case .invalidParameters: return NSLocalizedString("Invalid parameters.", comment: "")
|
||||||
|
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs within a 7 day period.", comment: "")
|
||||||
case .noSources: return NSLocalizedString("There are no SideStore sources.", comment: "")
|
case .noSources: return NSLocalizedString("There are no SideStore sources.", comment: "")
|
||||||
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be accessed.", comment: "")
|
||||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
case .appNotFound:
|
||||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||||
|
return String(format: NSLocalizedString("%@ could not be found.", comment: ""), appName)
|
||||||
|
case .openAppFailed:
|
||||||
|
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||||
|
return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName)
|
||||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi!\nSideStore will never be able to install or refresh applications without WiFi.", comment: "")
|
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi!\nSideStore will never be able to install or refresh applications without WiFi.", comment: "")
|
||||||
case .unabletoconnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer Please check that you are on the Same Wi-Fi and your Firewall has been set correctly", comment: "")
|
|
||||||
case .unabletoconSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice Please make sure you have paired your Device by doing 'SideJITServer -y' or try Refreshing SideJITServer from Settings", comment: "")
|
|
||||||
case .wrongIP: return NSLocalizedString("Incorrect SideJITServer IP Please make sure that you are on the Samw Wifi as SideJITServer", comment: "")
|
|
||||||
case .refreshsidejit: return NSLocalizedString("Unable to find App Please try Refreshing SideJITServer from Settings", comment: "")
|
|
||||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it without the use of SideJITServer at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it without the use of SideJITServer at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||||
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
||||||
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
||||||
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
||||||
case .cacheClearError(let errors): return String(format: NSLocalizedString("An error occurred while clearing cache: %@", comment: ""), errors.joined(separator: "\n"))
|
case .cacheClearError(let errors): return String(format: NSLocalizedString("An error occurred while clearing cache: %@", comment: ""), errors.joined(separator: "\n"))
|
||||||
case .SideJITIssue(let errors): return NSLocalizedString("SideJITServer Error: \(errors)", comment: "")
|
case .SideJITIssue(let errors): return NSLocalizedString("SideJITServer Error: \(errors)", comment: "")
|
||||||
|
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||||
|
case .unabletoconnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer Please check that you are on the Same Wi-Fi and your Firewall has been set correctly", comment: "")
|
||||||
|
case .unabletoconSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice Please make sure you have paired your Device by doing 'SideJITServer -y' or try Refreshing SideJITServer from Settings", comment: "")
|
||||||
|
case .wrongIP: return NSLocalizedString("Incorrect SideJITServer IP Please make sure that you are on the Samw Wifi as SideJITServer", comment: "")
|
||||||
|
case .refreshsidejit: return NSLocalizedString("Unable to find App Please try Refreshing SideJITServer from Settings", comment: "")
|
||||||
|
case .anisetteV1Error: return NSLocalizedString("An error occurred when getting anisette data from a V1 server. Try using another anisette server.", comment: "")
|
||||||
|
case .provisioningError: return NSLocalizedString("An error occurred when provisioning: @@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||||
|
case .anisetteV3Error: return NSLocalizedString("An error occurred when getting anisette data from a V3 server. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||||
|
case .cacheClearError: return NSLocalizedString("An error occurred while clearing cache: %@", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var recoverySuggestion: String? {
|
var recoverySuggestion: String? {
|
||||||
switch self
|
switch self.code
|
||||||
{
|
{
|
||||||
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
|
case .noWiFi: return NSLocalizedString("Make sure the VPN is toggled on and you are connected to any WiFi network!", comment: "")
|
||||||
|
case .maximumAppIDLimitReached:
|
||||||
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||||
let message: String
|
guard let appName, let requiredAppIDs, let availableAppIDs, let expirationDate else { return baseMessage }
|
||||||
|
var message: String
|
||||||
|
|
||||||
if requiredAppIDs > 1
|
if requiredAppIDs > 1
|
||||||
{
|
{
|
||||||
let availableText: String
|
let availableText: String
|
||||||
@@ -94,23 +195,23 @@ enum OperationError: LocalizedError
|
|||||||
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
|
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), appName, NSNumber(value: requiredAppIDs), availableText)
|
||||||
message = prefixMessage + " " + baseMessage
|
message = prefixMessage + " " + baseMessage + "\n\n"
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
|
message = baseMessage + " "
|
||||||
|
|
||||||
let dateComponentsFormatter = DateComponentsFormatter()
|
|
||||||
dateComponentsFormatter.maximumUnitCount = 1
|
|
||||||
dateComponentsFormatter.unitsStyle = .full
|
|
||||||
|
|
||||||
let remainingTime = dateComponentsFormatter.string(from: dateComponents)!
|
|
||||||
|
|
||||||
let remainingTimeMessage = String(format: NSLocalizedString("You can register another App ID in %@.", comment: ""), remainingTime)
|
|
||||||
message = baseMessage + " " + remainingTimeMessage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: expirationDate)
|
||||||
|
let dateFormatter = DateComponentsFormatter()
|
||||||
|
dateFormatter.maximumUnitCount = 1
|
||||||
|
dateFormatter.unitsStyle = .full
|
||||||
|
|
||||||
|
let remainingTime = dateFormatter.string(from: dateComponents)!
|
||||||
|
|
||||||
|
message += String(format: NSLocalizedString("You can register another App ID in %@.", comment: ""), remainingTime)
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
default: return nil
|
default: return nil
|
||||||
|
|||||||
@@ -25,22 +25,38 @@ protocol PatchAppContext
|
|||||||
var error: Error? { get }
|
var error: Error? { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PatchAppError: LocalizedError
|
extension PatchAppError
|
||||||
{
|
{
|
||||||
case unsupportedOperatingSystemVersion(OperatingSystemVersion)
|
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||||
|
typealias Error = PatchAppError
|
||||||
var errorDescription: String? {
|
|
||||||
switch self
|
case unsupportedOperatingSystemVersion
|
||||||
{
|
}
|
||||||
case .unsupportedOperatingSystemVersion(let osVersion):
|
|
||||||
var osVersionString = "\(osVersion.majorVersion).\(osVersion.minorVersion)"
|
static func unsupportedOperatingSystemVersion(_ osVersion: OperatingSystemVersion) -> PatchAppError {
|
||||||
if osVersion.patchVersion != 0
|
PatchAppError(code: .unsupportedOperatingSystemVersion, osVersion: osVersion)
|
||||||
{
|
}
|
||||||
osVersionString += ".\(osVersion.patchVersion)"
|
}
|
||||||
|
|
||||||
|
struct PatchAppError: ALTLocalizedError {
|
||||||
|
let code: Code
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
var osVersion: OperatingSystemVersion?
|
||||||
|
|
||||||
|
var errorFailureReason: String {
|
||||||
|
switch self.code {
|
||||||
|
case .unsupportedOperatingSystemVersion:
|
||||||
|
let osVersionString: String
|
||||||
|
|
||||||
|
if let osVersion = self.osVersion?.stringValue {
|
||||||
|
osVersionString = NSLocalizedString("iOS", comment: "") + " " + osVersion
|
||||||
|
} else {
|
||||||
|
osVersionString = NSLocalizedString("your device's iOS version", comment: "")
|
||||||
}
|
}
|
||||||
|
return String(format: NSLocalizedString("The OTA download URL for %@ could not be determined.", comment: ""), osVersionString)
|
||||||
let errorDescription = String(format: NSLocalizedString("The OTA download URL for iOS %@ could not be determined.", comment: ""), osVersionString)
|
|
||||||
return errorDescription
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ private extension PatchViewController
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let (bundleIdentifier, result) = results.first else { throw refreshGroup?.context.error ?? OperationError.unknown }
|
guard let (bundleIdentifier, result) = results.first else { throw refreshGroup?.context.error ?? OperationError.unknown() }
|
||||||
_ = try result.get()
|
_ = try result.get()
|
||||||
|
|
||||||
if var patchedApps = UserDefaults.standard.patchedApps, let index = patchedApps.firstIndex(of: bundleIdentifier)
|
if var patchedApps = UserDefaults.standard.patchedApps, let index = patchedApps.firstIndex(of: bundleIdentifier)
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
if let error = self.context.error { return self.finish(.failure(error)) }
|
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||||
|
|
||||||
guard let profiles = self.context.provisioningProfiles else { return self.finish(.failure(OperationError.invalidParameters)) }
|
guard let profiles = self.context.provisioningProfiles else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound)) }
|
guard let app = self.context.app else { return self.finish(.failure(OperationError(.appNotFound(name: nil)))) }
|
||||||
|
|
||||||
for p in profiles {
|
for p in profiles {
|
||||||
do {
|
do {
|
||||||
let bytes = p.value.data.toRustByteSlice()
|
let bytes = p.value.data.toRustByteSlice()
|
||||||
@@ -49,14 +49,13 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
}
|
}
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
print("Sending refresh app request...")
|
|
||||||
|
|
||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
||||||
self.managedObjectContext.perform {
|
self.managedObjectContext.perform {
|
||||||
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
||||||
self.finish(.failure(OperationError.appNotFound))
|
self.finish(.failure(OperationError(.appNotFound(name: app.name))))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
installedApp.update(provisioningProfile: p.value)
|
installedApp.update(provisioningProfile: p.value)
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ final class SendAppOperation: ResultOperation<()>
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print("IPA doesn't exist????")
|
print("IPA doesn't exist????")
|
||||||
self.finish(.failure(OperationError.appNotFound))
|
self.finish(.failure(OperationError(.appNotFound(name: resignedApp.name))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,48 +8,71 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
enum VerificationError: ALTLocalizedError
|
extension VerificationError
|
||||||
{
|
{
|
||||||
case privateEntitlements(ALTApplication, entitlements: [String: Any])
|
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||||
case mismatchedBundleIdentifiers(ALTApplication, sourceBundleID: String)
|
typealias Error = VerificationError
|
||||||
case iOSVersionNotSupported(ALTApplication)
|
|
||||||
|
case privateEntitlements
|
||||||
var app: ALTApplication {
|
case mismatchedBundleIdentifiers
|
||||||
switch self
|
case iOSVersionNotSupported
|
||||||
{
|
|
||||||
case .privateEntitlements(let app, _): return app
|
|
||||||
case .mismatchedBundleIdentifiers(let app, _): return app
|
|
||||||
case .iOSVersionNotSupported(let app): return app
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var failure: String? {
|
static func privateEntitlements(_ entitlements: [String: Any], app: ALTApplication) -> VerificationError {
|
||||||
return String(format: NSLocalizedString("“%@” could not be installed.", comment: ""), app.name)
|
VerificationError(code: .privateEntitlements, app: app, entitlements: entitlements)
|
||||||
}
|
}
|
||||||
|
|
||||||
var failureReason: String? {
|
static func mismatchedBundleIdentifiers(sourceBundleID: String, app: ALTApplication) -> VerificationError {
|
||||||
switch self
|
VerificationError(code: .mismatchedBundleIdentifiers, app: app, sourceBundleID: sourceBundleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func iOSVersionNotSupported(app: ALTApplication) -> VerificationError {
|
||||||
|
VerificationError(code: .iOSVersionNotSupported, app: app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VerificationError: ALTLocalizedError {
|
||||||
|
let code: Code
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
var app: ALTApplication?
|
||||||
|
var entitlements: [String: Any]?
|
||||||
|
var sourceBundleID: String?
|
||||||
|
|
||||||
|
var errorFailureReason: String {
|
||||||
|
switch self.code
|
||||||
{
|
{
|
||||||
case .privateEntitlements(let app, _):
|
case .privateEntitlements:
|
||||||
return String(format: NSLocalizedString("“%@” requires private permissions.", comment: ""), app.name)
|
let appName = (self.app?.name as String?).map { String(format: NSLocalizedString("'%@'", comment: ""), $0) } ??
|
||||||
|
NSLocalizedString("Unknown app", comment: "")
|
||||||
case .mismatchedBundleIdentifiers(let app, let sourceBundleID):
|
return String(format: NSLocalizedString("“%@” requires private permissions.", comment: ""), appName)
|
||||||
return String(format: NSLocalizedString("The bundle ID “%@” does not match the one specified by the source (“%@”).", comment: ""), app.bundleIdentifier, sourceBundleID)
|
|
||||||
|
case .mismatchedBundleIdentifiers:
|
||||||
case .iOSVersionNotSupported(let app):
|
if let app, let sourceBundleID {
|
||||||
let name = app.name
|
return String(format: NSLocalizedString("The bundle ID '%@' does not match the one specified by the source ('%@').", comment: ""), app.bundleIdentifier, sourceBundleID)
|
||||||
|
} else {
|
||||||
var version = "iOS \(app.minimumiOSVersion.majorVersion).\(app.minimumiOSVersion.minorVersion)"
|
return NSLocalizedString("The bundle ID does not match the one specified by the source.", comment: "")
|
||||||
if app.minimumiOSVersion.patchVersion > 0
|
|
||||||
{
|
|
||||||
version += ".\(app.minimumiOSVersion.patchVersion)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let localizedDescription = String(format: NSLocalizedString("%@ requires %@.", comment: ""), name, version)
|
case .iOSVersionNotSupported:
|
||||||
return localizedDescription
|
var failureReason: String!
|
||||||
|
if let app {
|
||||||
|
var version = "iOS \(app.minimumiOSVersion.majorVersion).\(app.minimumiOSVersion.minorVersion)"
|
||||||
|
if app.minimumiOSVersion.patchVersion > 0 {
|
||||||
|
version += ".\(app.minimumiOSVersion.patchVersion)"
|
||||||
|
}
|
||||||
|
failureReason = String(format: NSLocalizedString("%@ requires %@.", comment: ""), app.name, version)
|
||||||
|
} else {
|
||||||
|
let version = ProcessInfo.processInfo.operatingSystemVersionString
|
||||||
|
failureReason = String(format: NSLocalizedString("This app does not support iOS %@.", comment: ""), version)
|
||||||
|
}
|
||||||
|
return failureReason
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,11 +104,11 @@ final class VerifyAppOperation: ResultOperation<Void>
|
|||||||
guard let app = self.context.app else { throw OperationError.invalidParameters }
|
guard let app = self.context.app else { throw OperationError.invalidParameters }
|
||||||
|
|
||||||
guard app.bundleIdentifier == self.context.bundleIdentifier else {
|
guard app.bundleIdentifier == self.context.bundleIdentifier else {
|
||||||
throw VerificationError.mismatchedBundleIdentifiers(app, sourceBundleID: self.context.bundleIdentifier)
|
throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard ProcessInfo.processInfo.isOperatingSystemAtLeast(app.minimumiOSVersion) else {
|
guard ProcessInfo.processInfo.isOperatingSystemAtLeast(app.minimumiOSVersion) else {
|
||||||
throw VerificationError.iOSVersionNotSupported(app)
|
throw VerificationError.iOSVersionNotSupported(app: app)
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(iOS 13.5, *)
|
if #available(iOS 13.5, *)
|
||||||
@@ -116,7 +139,7 @@ final class VerifyAppOperation: ResultOperation<Void>
|
|||||||
let entitlements = try PropertyListSerialization.propertyList(from: entitlementsPlist.data(using: .utf8)!, options: [], format: nil) as! [String: Any]
|
let entitlements = try PropertyListSerialization.propertyList(from: entitlementsPlist.data(using: .utf8)!, options: [], format: nil) as! [String: Any]
|
||||||
|
|
||||||
app.hasPrivateEntitlements = true
|
app.hasPrivateEntitlements = true
|
||||||
let error = VerificationError.privateEntitlements(app, entitlements: entitlements)
|
let error = VerificationError.privateEntitlements(entitlements, app: app)
|
||||||
self.process(error) { (result) in
|
self.process(error) { (result) in
|
||||||
self.finish(result.mapError { $0 as Error })
|
self.finish(result.mapError { $0 as Error })
|
||||||
}
|
}
|
||||||
@@ -145,9 +168,10 @@ private extension VerifyAppOperation
|
|||||||
guard let presentingViewController = self.context.presentingViewController else { return completion(.failure(error)) }
|
guard let presentingViewController = self.context.presentingViewController else { return completion(.failure(error)) }
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch error
|
switch error.code
|
||||||
{
|
{
|
||||||
case .privateEntitlements(_, let entitlements):
|
case .privateEntitlements:
|
||||||
|
guard let entitlements = error.entitlements else { return completion(.failure(error)) }
|
||||||
let permissions = entitlements.keys.sorted().joined(separator: "\n")
|
let permissions = entitlements.keys.sorted().joined(separator: "\n")
|
||||||
let message = String(format: NSLocalizedString("""
|
let message = String(format: NSLocalizedString("""
|
||||||
You must allow access to these private permissions before continuing:
|
You must allow access to these private permissions before continuing:
|
||||||
|
|||||||
53
AltStore/Settings/Error Log/ErrorDetailsViewController.swift
Normal file
53
AltStore/Settings/Error Log/ErrorDetailsViewController.swift
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// ErrorDetailsViewController.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 10/5/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
class ErrorDetailsViewController: UIViewController
|
||||||
|
{
|
||||||
|
var loggedError: LoggedError?
|
||||||
|
|
||||||
|
@IBOutlet private var textView: UITextView!
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
if let error = self.loggedError?.error
|
||||||
|
{
|
||||||
|
self.title = error.localizedErrorCode
|
||||||
|
|
||||||
|
let font = self.textView.font ?? UIFont.preferredFont(forTextStyle: .body)
|
||||||
|
let detailedDescription = error.formattedDetailedDescription(with: font)
|
||||||
|
self.textView.attributedText = detailedDescription
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.title = NSLocalizedString("Error Details", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.navigationController?.navigationBar.tintColor = .altPrimary
|
||||||
|
|
||||||
|
if #available(iOS 15, *), let sheetController = self.navigationController?.sheetPresentationController
|
||||||
|
{
|
||||||
|
sheetController.detents = [.medium(), .large()]
|
||||||
|
sheetController.selectedDetentIdentifier = .medium
|
||||||
|
sheetController.prefersGrabberVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLayoutSubviews()
|
||||||
|
{
|
||||||
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
|
self.textView.textContainerInset.left = self.view.layoutMargins.left
|
||||||
|
self.textView.textContainerInset.right = self.view.layoutMargins.right
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,15 @@ final class ErrorLogViewController: UITableViewController
|
|||||||
self.tableView.dataSource = self.dataSource
|
self.tableView.dataSource = self.dataSource
|
||||||
self.tableView.prefetchDataSource = self.dataSource
|
self.tableView.prefetchDataSource = self.dataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
|
guard let loggedError = sender as? LoggedError, segue.identifier == "showErrorDetails" else { return }
|
||||||
|
|
||||||
|
let navigationController = segue.destination as! UINavigationController
|
||||||
|
|
||||||
|
let errorDetailsViewController = navigationController.viewControllers.first as! ErrorDetailsViewController
|
||||||
|
errorDetailsViewController.loggedError = loggedError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ErrorLogViewController
|
private extension ErrorLogViewController
|
||||||
@@ -60,14 +69,8 @@ private extension ErrorLogViewController
|
|||||||
let cell = cell as! ErrorLogTableViewCell
|
let cell = cell as! ErrorLogTableViewCell
|
||||||
cell.dateLabel.text = self.timeFormatter.string(from: loggedError.date)
|
cell.dateLabel.text = self.timeFormatter.string(from: loggedError.date)
|
||||||
cell.errorFailureLabel.text = loggedError.localizedFailure ?? NSLocalizedString("Operation Failed", comment: "")
|
cell.errorFailureLabel.text = loggedError.localizedFailure ?? NSLocalizedString("Operation Failed", comment: "")
|
||||||
|
cell.errorCodeLabel.text = loggedError.error.localizedErrorCode
|
||||||
switch loggedError.domain
|
|
||||||
{
|
|
||||||
case AltServerErrorDomain: cell.errorCodeLabel?.text = String(format: NSLocalizedString("AltServer Error %@", comment: ""), NSNumber(value: loggedError.code))
|
|
||||||
case OperationError.domain: cell.errorCodeLabel?.text = String(format: NSLocalizedString("AltStore Error %@", comment: ""), NSNumber(value: loggedError.code))
|
|
||||||
default: cell.errorCodeLabel?.text = loggedError.error.localizedErrorCode
|
|
||||||
}
|
|
||||||
|
|
||||||
let nsError = loggedError.error as NSError
|
let nsError = loggedError.error as NSError
|
||||||
let errorDescription = [nsError.localizedDescription, nsError.localizedRecoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
|
let errorDescription = [nsError.localizedDescription, nsError.localizedRecoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
|
||||||
cell.errorDescriptionTextView.text = errorDescription
|
cell.errorDescriptionTextView.text = errorDescription
|
||||||
@@ -93,6 +96,9 @@ private extension ErrorLogViewController
|
|||||||
},
|
},
|
||||||
UIAction(title: NSLocalizedString("Search FAQ", comment: ""), image: UIImage(systemName: "magnifyingglass")) { [weak self] _ in
|
UIAction(title: NSLocalizedString("Search FAQ", comment: ""), image: UIImage(systemName: "magnifyingglass")) { [weak self] _ in
|
||||||
self?.searchFAQ(for: loggedError)
|
self?.searchFAQ(for: loggedError)
|
||||||
|
},
|
||||||
|
UIAction(title: NSLocalizedString("View More Details", comment: ""), image: UIImage(systemName: "ellipsis.circle")) { [weak self] _ in
|
||||||
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -235,13 +241,17 @@ private extension ErrorLogViewController
|
|||||||
let baseURL = URL(string: "https://faq.altstore.io/getting-started/troubleshooting-guide")!
|
let baseURL = URL(string: "https://faq.altstore.io/getting-started/troubleshooting-guide")!
|
||||||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
|
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
|
||||||
|
|
||||||
let query = [loggedError.domain, "\(loggedError.code)"].joined(separator: "+")
|
let query = [loggedError.domain, "\(loggedError.error.displayCode)"].joined(separator: "+")
|
||||||
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
||||||
|
|
||||||
let safariViewController = SFSafariViewController(url: components.url ?? baseURL)
|
let safariViewController = SFSafariViewController(url: components.url ?? baseURL)
|
||||||
safariViewController.preferredControlTintColor = .altPrimary
|
safariViewController.preferredControlTintColor = .altPrimary
|
||||||
self.present(safariViewController, animated: true)
|
self.present(safariViewController, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func viewMoreDetails(for loggedError: LoggedError) {
|
||||||
|
self.performSegue(withIdentifier: "showErrorDetails", sender: loggedError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ErrorLogViewController
|
extension ErrorLogViewController
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||||
<rect key="frame" x="0.0" y="1340" width="375" height="25"/>
|
<rect key="frame" x="0.0" y="1296" width="375" height="25"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -351,22 +351,22 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||||
<rect key="frame" x="30" y="15.5" width="86" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="86" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||||
<rect key="frame" x="187.5" y="15.5" width="157.5" height="20.5"/>
|
<rect key="frame" x="187.5" y="15.5" width="157.5" height="20.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="125.5" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="125.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||||
<rect key="frame" x="139.5" y="0.0" width="18" height="20.5"/>
|
<rect key="frame" x="139.5" y="0.0" width="18" height="20.5"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -395,22 +395,22 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
|
||||||
<rect key="frame" x="30" y="15.5" width="89" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="89" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
|
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
|
||||||
<rect key="frame" x="198" y="15.5" width="147" height="20.5"/>
|
<rect key="frame" x="198" y="15.5" width="147" height="20.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="115" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="115" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
|
||||||
<rect key="frame" x="129" y="0.0" width="18" height="20.5"/>
|
<rect key="frame" x="129" y="0.0" width="18" height="20.5"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -655,7 +655,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eZ3-BT-q4D" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eZ3-BT-q4D" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1118" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -688,7 +688,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1169" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -721,7 +721,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1220" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1176" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -754,7 +754,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1271" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1227" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -1199,11 +1199,73 @@ Settings by i cons from the Noun Project</string>
|
|||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
</rightBarButtonItems>
|
</rightBarButtonItems>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
|
<connections>
|
||||||
|
<segue destination="7gm-d1-zWK" kind="presentation" identifier="showErrorDetails" id="9vz-y6-evp"/>
|
||||||
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="rU1-TZ-TD8" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="rU1-TZ-TD8" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1697" y="1774"/>
|
<point key="canvasLocation" x="1697" y="1774"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--Error Details View Controller-->
|
||||||
|
<scene sceneID="XNO-Yg-I7t">
|
||||||
|
<objects>
|
||||||
|
<viewController id="xB2-Se-VVg" customClass="ErrorDetailsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="eBQ-se-VIy">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="ctd-NB-4ov">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<color key="textColor" systemColor="labelColor"/>
|
||||||
|
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="Nm8-69-Ngi"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="ctd-NB-4ov" firstAttribute="leading" secondItem="eBQ-se-VIy" secondAttribute="leading" id="Cv1-Te-gBH"/>
|
||||||
|
<constraint firstItem="ctd-NB-4ov" firstAttribute="top" secondItem="eBQ-se-VIy" secondAttribute="top" id="HRY-Rg-iMI"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="ctd-NB-4ov" secondAttribute="trailing" id="Lc1-K7-iuq"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="ctd-NB-4ov" secondAttribute="bottom" id="zCz-Cy-Y5z"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<navigationItem key="navigationItem" id="XpE-V9-EaY">
|
||||||
|
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="rnr-dX-4Ev">
|
||||||
|
<connections>
|
||||||
|
<segue destination="ZSp-1n-UJ9" kind="unwind" unwindAction="unwindFromErrorDetails:" id="TFu-zD-QyF"/>
|
||||||
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
</navigationItem>
|
||||||
|
<connections>
|
||||||
|
<outlet property="textView" destination="ctd-NB-4ov" id="x2C-9R-Xz1"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="8AM-Vx-XTN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
<exit id="ZSp-1n-UJ9" userLabel="Exit" sceneMemberID="exit"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="3389.5999999999999" y="1772.5637181409297"/>
|
||||||
|
</scene>
|
||||||
|
<!--Navigation Controller-->
|
||||||
|
<scene sceneID="4LJ-Od-dCK">
|
||||||
|
<objects>
|
||||||
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="7gm-d1-zWK" sceneMemberID="viewController">
|
||||||
|
<toolbarItems/>
|
||||||
|
<navigationBar key="navigationBar" contentMode="scaleToFill" id="dI0-sh-yGf">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="56"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</navigationBar>
|
||||||
|
<nil name="viewControllers"/>
|
||||||
|
<connections>
|
||||||
|
<segue destination="xB2-Se-VVg" kind="relationship" relationship="rootViewController" id="RpP-UM-JfJ"/>
|
||||||
|
</connections>
|
||||||
|
</navigationController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="OXW-bf-HIj" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="2554" y="1773"/>
|
||||||
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="Next" width="18" height="18"/>
|
<image name="Next" width="18" height="18"/>
|
||||||
@@ -1218,5 +1280,8 @@ Settings by i cons from the Noun Project</string>
|
|||||||
<systemColor name="labelColor">
|
<systemColor name="labelColor">
|
||||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</systemColor>
|
</systemColor>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -12,17 +12,22 @@ import CoreData
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
struct SourceError: LocalizedError
|
struct SourceError: ALTLocalizedError
|
||||||
{
|
{
|
||||||
enum Code
|
enum Code: Int, ALTErrorCode
|
||||||
{
|
{
|
||||||
|
typealias Error = SourceError
|
||||||
|
|
||||||
case unsupported
|
case unsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
var code: Code
|
var code: Code
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
@Managed var source: Source
|
@Managed var source: Source
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorFailureReason: String {
|
||||||
switch self.code
|
switch self.code
|
||||||
{
|
{
|
||||||
case .unsupported: return String(format: NSLocalizedString("The source “%@” is not supported by this version of SideStore.", comment: ""), self.$source.name)
|
case .unsupported: return String(format: NSLocalizedString("The source “%@” is not supported by this version of SideStore.", comment: ""), self.$source.name)
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ FOUNDATION_EXPORT const unsigned char AltStoreCoreVersionString[];
|
|||||||
// Shared
|
// Shared
|
||||||
#import <AltStoreCore/ALTConstants.h>
|
#import <AltStoreCore/ALTConstants.h>
|
||||||
#import <AltStoreCore/ALTConnection.h>
|
#import <AltStoreCore/ALTConnection.h>
|
||||||
|
#import <AltStoreCore/ALTWrappedError.h>
|
||||||
#import <AltStoreCore/NSError+ALTServerError.h>
|
#import <AltStoreCore/NSError+ALTServerError.h>
|
||||||
#import <AltStoreCore/CFNotificationName+AltStore.h>
|
#import <AltStoreCore/CFNotificationName+AltStore.h>
|
||||||
|
|||||||
@@ -8,9 +8,16 @@
|
|||||||
|
|
||||||
#import "NSError+ALTServerError.h"
|
#import "NSError+ALTServerError.h"
|
||||||
|
|
||||||
NSErrorDomain const AltServerErrorDomain = @"com.rileytestut.AltServer";
|
#if TARGET_OS_OSX
|
||||||
NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServer.Installation";
|
#import "AltServer-Swift.h"
|
||||||
NSErrorDomain const AltServerConnectionErrorDomain = @"com.rileytestut.AltServer.Connection";
|
#else
|
||||||
|
#import "AltStoreCore/AltStoreCore-Swift.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NSErrorDomain const AltServerErrorDomain = @"AltServer.ServerError";
|
||||||
|
NSErrorDomain const AltServerInstallationErrorDomain = @"AltServer.InstallationError";
|
||||||
|
NSErrorDomain const AltServerConnectionErrorDomain = @"AltServer.ConnectionError";
|
||||||
|
|
||||||
|
|
||||||
NSErrorUserInfoKey const ALTUnderlyingErrorDomainErrorKey = @"underlyingErrorDomain";
|
NSErrorUserInfoKey const ALTUnderlyingErrorDomainErrorKey = @"underlyingErrorDomain";
|
||||||
NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode";
|
NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode";
|
||||||
@@ -24,8 +31,16 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
|
|
||||||
+ (void)load
|
+ (void)load
|
||||||
{
|
{
|
||||||
[NSError setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
[NSError alt_setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
||||||
if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey])
|
||||||
|
{
|
||||||
|
return [error altserver_localizedDescription];
|
||||||
|
}
|
||||||
|
else if ([userInfoKey isEqualToString:NSLocalizedFailureErrorKey])
|
||||||
|
{
|
||||||
|
return [error altserver_localizedFailure];
|
||||||
|
}
|
||||||
|
else if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
||||||
{
|
{
|
||||||
return [error altserver_localizedFailureReason];
|
return [error altserver_localizedFailureReason];
|
||||||
}
|
}
|
||||||
@@ -41,10 +56,10 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return nil;
|
return nil;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[NSError setUserInfoValueProviderForDomain:AltServerConnectionErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
[NSError alt_setUserInfoValueProviderForDomain:AltServerConnectionErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
||||||
if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey])
|
if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
||||||
{
|
{
|
||||||
return [error altserver_connection_localizedDescription];
|
return [error altserver_connection_localizedFailureReason];
|
||||||
}
|
}
|
||||||
else if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey])
|
else if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey])
|
||||||
{
|
{
|
||||||
@@ -55,6 +70,53 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (nullable NSString *)altserver_localizedDescription
|
||||||
|
{
|
||||||
|
switch ((ALTServerError)self.code)
|
||||||
|
{
|
||||||
|
case ALTServerErrorUnderlyingError:
|
||||||
|
{
|
||||||
|
// We're wrapping another error, so return the wrapped error's localized description.
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
return underlyingError.localizedDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (nullable NSString *)altserver_localizedFailure
|
||||||
|
{
|
||||||
|
switch ((ALTServerError)self.code)
|
||||||
|
{
|
||||||
|
case ALTServerErrorUnderlyingError:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
return underlyingError.alt_localizedFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ALTServerErrorConnectionFailed:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
if (underlyingError.localizedFailureReason != nil)
|
||||||
|
{
|
||||||
|
// Only return localized failure if there is an underlying error with failure reason.
|
||||||
|
#if TARGET_OS_OSX
|
||||||
|
return NSLocalizedString(@"There was an error connecting to the device.", @"");
|
||||||
|
#else
|
||||||
|
return NSLocalizedString(@"AltServer could not establish a connection to SideStore.", @"");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (nullable NSString *)altserver_localizedFailureReason
|
- (nullable NSString *)altserver_localizedFailureReason
|
||||||
{
|
{
|
||||||
switch ((ALTServerError)self.code)
|
switch ((ALTServerError)self.code)
|
||||||
@@ -80,12 +142,21 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return NSLocalizedString(@"An unknown error occured.", @"");
|
return NSLocalizedString(@"An unknown error occured.", @"");
|
||||||
|
|
||||||
case ALTServerErrorConnectionFailed:
|
case ALTServerErrorConnectionFailed:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
if (underlyingError.localizedFailureReason != nil)
|
||||||
|
{
|
||||||
|
return underlyingError.localizedFailureReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return fallback failure reason if there isn't an underlying error with failure reason.
|
||||||
#if TARGET_OS_OSX
|
#if TARGET_OS_OSX
|
||||||
return NSLocalizedString(@"There was an error connecting to the device.", @"");
|
return NSLocalizedString(@"There was an error connecting to the device.", @"");
|
||||||
#else
|
#else
|
||||||
return NSLocalizedString(@"Could not connect to SideStore.", @"");
|
return NSLocalizedString(@"Could not connect to SideStore.", @"");
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
case ALTServerErrorLostConnection:
|
case ALTServerErrorLostConnection:
|
||||||
return NSLocalizedString(@"Lost connection to SideStore.", @"");
|
return NSLocalizedString(@"Lost connection to SideStore.", @"");
|
||||||
|
|
||||||
@@ -93,8 +164,8 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return NSLocalizedString(@"SideStore could not find this device.", @"");
|
return NSLocalizedString(@"SideStore could not find this device.", @"");
|
||||||
|
|
||||||
case ALTServerErrorDeviceWriteFailed:
|
case ALTServerErrorDeviceWriteFailed:
|
||||||
return NSLocalizedString(@"Failed to write app data to device.", @"");
|
return NSLocalizedString(@"SideStore could not write data to this device.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInvalidRequest:
|
case ALTServerErrorInvalidRequest:
|
||||||
return NSLocalizedString(@"SideStore received an invalid request.", @"");
|
return NSLocalizedString(@"SideStore received an invalid request.", @"");
|
||||||
|
|
||||||
@@ -102,14 +173,20 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return NSLocalizedString(@"SideStore sent an invalid response.", @"");
|
return NSLocalizedString(@"SideStore sent an invalid response.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInvalidApp:
|
case ALTServerErrorInvalidApp:
|
||||||
return NSLocalizedString(@"The app is invalid.", @"");
|
return NSLocalizedString(@"The app is in an invalid format.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInstallationFailed:
|
case ALTServerErrorInstallationFailed:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
if (underlyingError != nil) {
|
||||||
|
return underlyingError.localizedFailureReason ?: underlyingError.localizedDescription;
|
||||||
|
}
|
||||||
return NSLocalizedString(@"An error occured while installing the app.", @"");
|
return NSLocalizedString(@"An error occured while installing the app.", @"");
|
||||||
|
}
|
||||||
|
|
||||||
case ALTServerErrorMaximumFreeAppLimitReached:
|
case ALTServerErrorMaximumFreeAppLimitReached:
|
||||||
return NSLocalizedString(@"Cannot activate more than 3 apps with a non-developer Apple ID.", @"");
|
return NSLocalizedString(@"You cannot activate more than 3 apps with a non-developer Apple ID.", @"");
|
||||||
|
|
||||||
case ALTServerErrorUnsupportediOSVersion:
|
case ALTServerErrorUnsupportediOSVersion:
|
||||||
return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install SideStore.", @"");
|
return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install SideStore.", @"");
|
||||||
|
|
||||||
@@ -117,8 +194,8 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return NSLocalizedString(@"SideStore does not support this request.", @"");
|
return NSLocalizedString(@"SideStore does not support this request.", @"");
|
||||||
|
|
||||||
case ALTServerErrorUnknownResponse:
|
case ALTServerErrorUnknownResponse:
|
||||||
return NSLocalizedString(@"Received an unknown response from SideStore.", @"");
|
return NSLocalizedString(@"SideStore received an unknown response from SideStore.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInvalidAnisetteData:
|
case ALTServerErrorInvalidAnisetteData:
|
||||||
return NSLocalizedString(@"The provided anisette data is invalid.", @"");
|
return NSLocalizedString(@"The provided anisette data is invalid.", @"");
|
||||||
|
|
||||||
@@ -153,7 +230,19 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
{
|
{
|
||||||
switch ((ALTServerError)self.code)
|
switch ((ALTServerError)self.code)
|
||||||
{
|
{
|
||||||
|
case ALTServerErrorUnderlyingError:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
return underlyingError.localizedRecoverySuggestion;
|
||||||
|
}
|
||||||
case ALTServerErrorConnectionFailed:
|
case ALTServerErrorConnectionFailed:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
if (underlyingError.localizedRecoverySuggestion != nil){
|
||||||
|
return underlyingError.localizedRecoverySuggestion;
|
||||||
|
}
|
||||||
|
// If there is no underlying error found, fall through to AltServerErrorDeviceNotFound
|
||||||
|
}
|
||||||
case ALTServerErrorDeviceNotFound:
|
case ALTServerErrorDeviceNotFound:
|
||||||
return NSLocalizedString(@"Make sure you have trusted this device with your computer and Wi-Fi sync is enabled.", @"");
|
return NSLocalizedString(@"Make sure you have trusted this device with your computer and Wi-Fi sync is enabled.", @"");
|
||||||
|
|
||||||
@@ -182,6 +271,13 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
{
|
{
|
||||||
switch ((ALTServerError)self.code)
|
switch ((ALTServerError)self.code)
|
||||||
{
|
{
|
||||||
|
case ALTServerErrorUnderlyingError:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
return underlyingError.alt_localizedDebugDescription;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
case ALTServerErrorIncompatibleDeveloperDisk:
|
case ALTServerErrorIncompatibleDeveloperDisk:
|
||||||
{
|
{
|
||||||
NSString *path = self.userInfo[NSFilePathErrorKey];
|
NSString *path = self.userInfo[NSFilePathErrorKey];
|
||||||
@@ -191,7 +287,7 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString *osVersion = [self altserver_osVersion] ?: NSLocalizedString(@"this device's OS version", @"");
|
NSString *osVersion = [self altserver_osVersion] ?: NSLocalizedString(@"this device's OS version", @"");
|
||||||
NSString *debugDescription = [NSString stringWithFormat:NSLocalizedString(@"The Developer disk located at\n\n%@\n\nis incompatible with %@.", @""), path, osVersion];
|
NSString *debugDescription = [NSString stringWithFormat:NSLocalizedString(@"The Developer disk located at %@ is incompatible with %@.", @""), path, osVersion];
|
||||||
return debugDescription;
|
return debugDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +328,7 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
|
|
||||||
#pragma mark - AltServerConnectionErrorDomain -
|
#pragma mark - AltServerConnectionErrorDomain -
|
||||||
|
|
||||||
- (nullable NSString *)altserver_connection_localizedDescription
|
- (nullable NSString *)altserver_connection_localizedFailureReason
|
||||||
{
|
{
|
||||||
switch ((ALTServerConnectionError)self.code)
|
switch ((ALTServerConnectionError)self.code)
|
||||||
{
|
{
|
||||||
|
|||||||
182
Shared/Errors/ALTLocalizedError.swift
Normal file
182
Shared/Errors/ALTLocalizedError.swift
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
//
|
||||||
|
// ALTLocalizedError.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 10/14/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
public let ALTLocalizedTitleErrorKey = "ALTLocalizedTitle"
|
||||||
|
public let ALTLocalizedDescriptionKey = "ALTLocalizedDescription"
|
||||||
|
|
||||||
|
public protocol ALTLocalizedError<Code>: LocalizedError, CustomNSError, CustomStringConvertible
|
||||||
|
{
|
||||||
|
associatedtype Code: ALTErrorCode
|
||||||
|
|
||||||
|
var code: Code { get }
|
||||||
|
var errorFailureReason: String { get }
|
||||||
|
|
||||||
|
var errorTitle: String? { get set }
|
||||||
|
var errorFailure: String? { get set }
|
||||||
|
|
||||||
|
var sourceFile: String? { get set }
|
||||||
|
var sourceLine: UInt? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ALTLocalizedError
|
||||||
|
{
|
||||||
|
var sourceFile: String? {
|
||||||
|
get { nil }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceLine: UInt? {
|
||||||
|
get { nil }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ALTErrorCode: RawRepresentable where RawValue == Int
|
||||||
|
{
|
||||||
|
associatedtype Error: ALTLocalizedError where Error.Code == Self
|
||||||
|
|
||||||
|
static var errorDomain: String { get } // Optional
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ALTErrorEnum: ALTErrorCode
|
||||||
|
{
|
||||||
|
associatedtype Error = DefaultLocalizedError<Self>
|
||||||
|
|
||||||
|
var errorFailureReason: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LocalizedError & CustomNSError & CustomStringConvertible
|
||||||
|
public extension ALTLocalizedError
|
||||||
|
{
|
||||||
|
var errorCode: Int { self.code.rawValue }
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
guard (self as NSError).localizedFailure == nil else {
|
||||||
|
// Error has localizedFailure, so return nil to construct localizedDescription from it + localizedFailureReason.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, return failureReason for localizedDescription to avoid system prepending "Operation Failed" message.
|
||||||
|
return self.failureReason
|
||||||
|
}
|
||||||
|
|
||||||
|
var failureReason: String? {
|
||||||
|
return self.errorFailureReason
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorUserInfo: [String : Any] {
|
||||||
|
let userInfo: [String: Any?] = [
|
||||||
|
NSLocalizedFailureErrorKey: self.errorFailure,
|
||||||
|
ALTLocalizedTitleErrorKey: self.errorTitle,
|
||||||
|
// ALTSourceFileErrorKey: self.sourceFile, // TODO: Figure out where these come from
|
||||||
|
// ALTSourceLineErrorKey: self.sourceLine,
|
||||||
|
]
|
||||||
|
|
||||||
|
return userInfo.compactMapValues { $0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
let description = "\(self.localizedErrorCode) “\(self.localizedDescription)”"
|
||||||
|
return description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default Implementations
|
||||||
|
public extension ALTLocalizedError where Code: ALTErrorEnum
|
||||||
|
{
|
||||||
|
static var errorDomain: String {
|
||||||
|
return Code.errorDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALTErrorEnum Codes provide their failure reason directly.
|
||||||
|
var errorFailureReason: String {
|
||||||
|
return self.code.errorFailureReason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default Implementations
|
||||||
|
public extension ALTErrorCode
|
||||||
|
{
|
||||||
|
static var errorDomain: String {
|
||||||
|
let typeName = String(reflecting: Self.self) // "\(Self.self)" doesn't include module name, but String(reflecting:) does.
|
||||||
|
let errorDomain = typeName.replacingOccurrences(of: "ErrorCode", with: "Error")
|
||||||
|
return errorDomain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ALTLocalizedError
|
||||||
|
{
|
||||||
|
// Allows us to initialize errors with localizedTitle + localizedFailure
|
||||||
|
// while still using the error's custom initializer at callsite.
|
||||||
|
init(_ error: Self, localizedTitle: String? = nil, localizedFailure: String? = nil)
|
||||||
|
{
|
||||||
|
self = error
|
||||||
|
|
||||||
|
if let localizedTitle
|
||||||
|
{
|
||||||
|
self.errorTitle = localizedTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
if let localizedFailure
|
||||||
|
{
|
||||||
|
self.errorFailure = localizedFailure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DefaultLocalizedError<Code: ALTErrorEnum>: ALTLocalizedError
|
||||||
|
{
|
||||||
|
public let code: Code
|
||||||
|
|
||||||
|
public var errorTitle: String?
|
||||||
|
public var errorFailure: String?
|
||||||
|
public var sourceFile: String?
|
||||||
|
public var sourceLine: UInt?
|
||||||
|
|
||||||
|
public init(_ code: Code, localizedTitle: String? = nil, localizedFailure: String? = nil, sourceFile: String? = #fileID, sourceLine: UInt? = #line)
|
||||||
|
{
|
||||||
|
self.code = code
|
||||||
|
self.errorTitle = localizedTitle
|
||||||
|
self.errorFailure = localizedFailure
|
||||||
|
self.sourceFile = sourceFile
|
||||||
|
self.sourceLine = sourceLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Custom Operators
|
||||||
|
/// These allow us to pattern match ALTErrorCodes against arbitrary errors via ~ prefix.
|
||||||
|
prefix operator ~
|
||||||
|
public prefix func ~<Code: ALTErrorCode>(expression: Code) -> NSError
|
||||||
|
{
|
||||||
|
let nsError = NSError(domain: Code.errorDomain, code: expression.rawValue)
|
||||||
|
return nsError
|
||||||
|
}
|
||||||
|
|
||||||
|
public func ~=(pattern: any Swift.Error, value: any Swift.Error) -> Bool
|
||||||
|
{
|
||||||
|
let isMatch = pattern._domain == value._domain && pattern._code == value._code
|
||||||
|
return isMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// These operators *should* allow us to match ALTErrorCodes against arbitrary errors,
|
||||||
|
// but they don't work as of iOS 16.1 and Swift 5.7.
|
||||||
|
//
|
||||||
|
//public func ~=<Error: ALTLocalizedError>(pattern: Error, value: Swift.Error) -> Bool
|
||||||
|
//{
|
||||||
|
// let isMatch = pattern._domain == value._domain && pattern._code == value._code
|
||||||
|
// return isMatch
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//public func ~=<Code: ALTErrorCode>(pattern: Code, value: Swift.Error) -> Bool
|
||||||
|
//{
|
||||||
|
// let isMatch = Code.errorDomain == value._domain && pattern.rawValue == value._code
|
||||||
|
// return isMatch
|
||||||
|
//}
|
||||||
25
Shared/Errors/ALTWrappedError.h
Normal file
25
Shared/Errors/ALTWrappedError.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// ALTWrappedError.h
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 11/28/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
// Overrides localizedDescription to check userInfoValueProvider for failure reason
|
||||||
|
// instead of default behavior which just returns NSLocalizedFailureErrorKey if present.
|
||||||
|
//
|
||||||
|
// Must be written in Objective-C for Swift.Error <-> NSError bridging to work correctly.
|
||||||
|
@interface ALTWrappedError : NSError
|
||||||
|
|
||||||
|
@property (copy, nonatomic) NSError *wrappedError;
|
||||||
|
|
||||||
|
- (instancetype)initWithError:(NSError *)error userInfo:(NSDictionary<NSString *, id> *)userInfo;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
73
Shared/Errors/ALTWrappedError.m
Normal file
73
Shared/Errors/ALTWrappedError.m
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//
|
||||||
|
// ALTWrappedError.m
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 11/28/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ALTWrappedError.h"
|
||||||
|
|
||||||
|
@implementation ALTWrappedError
|
||||||
|
|
||||||
|
+ (BOOL)supportsSecureCoding
|
||||||
|
{
|
||||||
|
// Required in order to serialize errors for legacy AltServer communication.
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithError:(NSError *)error userInfo:(NSDictionary<NSString *,id> *)userInfo
|
||||||
|
{
|
||||||
|
self = [super initWithDomain:error.domain code:error.code userInfo:userInfo];
|
||||||
|
if (self)
|
||||||
|
{
|
||||||
|
if ([error isKindOfClass:[ALTWrappedError class]])
|
||||||
|
{
|
||||||
|
_wrappedError = [(ALTWrappedError *)error wrappedError];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_wrappedError = [error copy];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)localizedDescription
|
||||||
|
{
|
||||||
|
NSString *localizedFailure = self.userInfo[NSLocalizedFailureErrorKey];
|
||||||
|
if (localizedFailure != nil)
|
||||||
|
{
|
||||||
|
NSString *wrappedLocalizedDescription = self.wrappedError.userInfo[NSLocalizedDescriptionKey];
|
||||||
|
NSString *localizedFailureReason = wrappedLocalizedDescription ?: self.wrappedError.localizedFailureReason ?: self.wrappedError.localizedDescription;
|
||||||
|
|
||||||
|
NSString *localizedDescription = [NSString stringWithFormat:@"%@ %@", localizedFailure, localizedFailureReason];
|
||||||
|
return localizedDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
// localizedFailure is nil, so return wrappedError's localizedDescription.
|
||||||
|
return self.wrappedError.localizedDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)localizedFailureReason
|
||||||
|
{
|
||||||
|
return self.wrappedError.localizedFailureReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)localizedRecoverySuggestion
|
||||||
|
{
|
||||||
|
return self.wrappedError.localizedRecoverySuggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)debugDescription
|
||||||
|
{
|
||||||
|
return self.wrappedError.debugDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)helpAnchor
|
||||||
|
{
|
||||||
|
return self.wrappedError.helpAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -22,12 +22,8 @@ public extension ALTServerError
|
|||||||
case is EncodingError: self = ALTServerError(.invalidResponse, underlyingError: error)
|
case is EncodingError: self = ALTServerError(.invalidResponse, underlyingError: error)
|
||||||
case let error as NSError:
|
case let error as NSError:
|
||||||
var userInfo = error.userInfo
|
var userInfo = error.userInfo
|
||||||
if !userInfo.keys.contains(NSUnderlyingErrorKey)
|
userInfo[NSUnderlyingErrorKey] = error
|
||||||
{
|
|
||||||
// Assign underlying error (if there isn't already one).
|
|
||||||
userInfo[NSUnderlyingErrorKey] = error
|
|
||||||
}
|
|
||||||
|
|
||||||
self = ALTServerError(.underlyingError, userInfo: userInfo)
|
self = ALTServerError(.underlyingError, userInfo: userInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,17 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension NSError
|
#if canImport(UIKit)
|
||||||
|
import UIKit
|
||||||
|
public typealias ALTFont = UIFont
|
||||||
|
#elseif canImport(AppKit)
|
||||||
|
import AppKit
|
||||||
|
public typealias ALTFont = NSFont
|
||||||
|
#endif
|
||||||
|
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
public extension NSError
|
||||||
{
|
{
|
||||||
@objc(alt_localizedFailure)
|
@objc(alt_localizedFailure)
|
||||||
var localizedFailure: String? {
|
var localizedFailure: String? {
|
||||||
@@ -21,52 +31,52 @@ extension NSError
|
|||||||
let debugDescription = (self.userInfo[NSDebugDescriptionErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSDebugDescriptionErrorKey) as? String)
|
let debugDescription = (self.userInfo[NSDebugDescriptionErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSDebugDescriptionErrorKey) as? String)
|
||||||
return debugDescription
|
return debugDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc(alt_localizedTitle)
|
||||||
|
var localizedTitle: String? {
|
||||||
|
return self.userInfo[ALTLocalizedTitleErrorKey] as? String
|
||||||
|
}
|
||||||
|
|
||||||
@objc(alt_errorWithLocalizedFailure:)
|
@objc(alt_errorWithLocalizedFailure:)
|
||||||
func withLocalizedFailure(_ failure: String) -> NSError
|
func withLocalizedFailure(_ failure: String) -> NSError
|
||||||
{
|
{
|
||||||
var userInfo = self.userInfo
|
switch self
|
||||||
userInfo[NSLocalizedFailureErrorKey] = failure
|
|
||||||
|
|
||||||
if let failureReason = self.localizedFailureReason
|
|
||||||
{
|
{
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason
|
case var error as any ALTLocalizedError:
|
||||||
|
error.errorFailure = failure
|
||||||
|
return error as NSError
|
||||||
|
|
||||||
|
default:
|
||||||
|
var userInfo = self.userInfo
|
||||||
|
userInfo[NSLocalizedFailureReasonErrorKey] = failure
|
||||||
|
|
||||||
|
return ALTWrappedError(error: self, userInfo: userInfo)
|
||||||
}
|
}
|
||||||
else if self.localizedFailure == nil && self.localizedFailureReason == nil && self.localizedDescription.contains(self.localizedErrorCode)
|
}
|
||||||
|
@objc(alt_errorWithLocalizedTitle:)
|
||||||
|
func withLocalizedTitle(_ title: String) -> NSError {
|
||||||
|
switch self
|
||||||
{
|
{
|
||||||
// Default localizedDescription, so replace with just the localized error code portion.
|
case var error as any ALTLocalizedError:
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = "(\(self.localizedErrorCode).)"
|
error.errorTitle = title
|
||||||
|
return error as NSError
|
||||||
|
|
||||||
|
default:
|
||||||
|
var userInfo = self.userInfo
|
||||||
|
userInfo[ALTLocalizedTitleErrorKey] = title
|
||||||
|
return ALTWrappedError(error: self, userInfo: userInfo)
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
if let localizedDescription = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String
|
|
||||||
{
|
|
||||||
userInfo[NSLocalizedDescriptionKey] = localizedDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't accidentally remove localizedDescription from dictionary
|
|
||||||
// userInfo[NSLocalizedDescriptionKey] = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String
|
|
||||||
|
|
||||||
if let recoverySuggestion = self.localizedRecoverySuggestion
|
|
||||||
{
|
|
||||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = recoverySuggestion
|
|
||||||
}
|
|
||||||
|
|
||||||
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
|
|
||||||
return error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizedForCoreData() -> NSError
|
func sanitizedForSerialization() -> NSError
|
||||||
{
|
{
|
||||||
var userInfo = self.userInfo
|
var userInfo = self.userInfo
|
||||||
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
|
|
||||||
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
|
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
|
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
|
||||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
|
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
|
||||||
|
userInfo[NSDebugDescriptionErrorKey] = self.localizedDebugDescription
|
||||||
|
|
||||||
// Remove userInfo values that don't conform to NSSecureEncoding.
|
// Remove userInfo values that don't conform to NSSecureEncoding.
|
||||||
userInfo = userInfo.filter { (key, value) in
|
userInfo = userInfo.filter { (key, value) in
|
||||||
return (value as AnyObject) is NSSecureCoding
|
return (value as AnyObject) is NSSecureCoding
|
||||||
@@ -75,69 +85,165 @@ extension NSError
|
|||||||
// Sanitize underlying errors.
|
// Sanitize underlying errors.
|
||||||
if let underlyingError = userInfo[NSUnderlyingErrorKey] as? Error
|
if let underlyingError = userInfo[NSUnderlyingErrorKey] as? Error
|
||||||
{
|
{
|
||||||
let sanitizedError = (underlyingError as NSError).sanitizedForCoreData()
|
let sanitizedError = (underlyingError as NSError).sanitizedForSerialization()
|
||||||
userInfo[NSUnderlyingErrorKey] = sanitizedError
|
userInfo[NSUnderlyingErrorKey] = sanitizedError
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(iOS 14.5, macOS 11.3, *), let underlyingErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? [Error]
|
if #available(iOS 14.5, macOS 11.3, *), let underlyingErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? [Error]
|
||||||
{
|
{
|
||||||
let sanitizedErrors = underlyingErrors.map { ($0 as NSError).sanitizedForCoreData() }
|
let sanitizedErrors = underlyingErrors.map { ($0 as NSError).sanitizedForSerialization() }
|
||||||
userInfo[NSMultipleUnderlyingErrorsKey] = sanitizedErrors
|
userInfo[NSMultipleUnderlyingErrorsKey] = sanitizedErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
|
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
func formattedDetailedDescription(with font: ALTFont) -> NSAttributedString {
|
||||||
|
#if canImport(UIKit)
|
||||||
|
let boldFontDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) ?? font.fontDescriptor
|
||||||
|
#else
|
||||||
|
let boldFontDescriptor = font.fontDescriptor.withSymbolicTraits(.bold)
|
||||||
|
#endif
|
||||||
|
let boldFont = ALTFont(descriptor: boldFontDescriptor, size: font.pointSize) ?? font
|
||||||
|
|
||||||
|
var preferredKeyOrder = [
|
||||||
|
NSDebugDescriptionErrorKey,
|
||||||
|
NSLocalizedDescriptionKey,
|
||||||
|
NSLocalizedFailureErrorKey,
|
||||||
|
NSLocalizedFailureReasonErrorKey,
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey,
|
||||||
|
ALTLocalizedTitleErrorKey,
|
||||||
|
// ALTSourceFileErrorKey,
|
||||||
|
// ALTSourceLineErrorKey,
|
||||||
|
NSUnderlyingErrorKey
|
||||||
|
]
|
||||||
|
|
||||||
|
if #available(iOS 14.5, macOS 11.3, *) {
|
||||||
|
preferredKeyOrder.append(NSMultipleUnderlyingErrorsKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo = self.userInfo
|
||||||
|
userInfo[NSDebugDescriptionErrorKey] = self.localizedDebugDescription
|
||||||
|
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
|
||||||
|
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
|
||||||
|
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
|
||||||
|
|
||||||
|
let sortedUserInfo = userInfo.sorted { a, b in
|
||||||
|
let indexA = preferredKeyOrder.firstIndex(of: a.key)
|
||||||
|
let indexB = preferredKeyOrder.firstIndex(of: b.key)
|
||||||
|
switch (indexA, indexB) {
|
||||||
|
case (let indexA?, let indexB?): return indexA < indexB
|
||||||
|
case (_?, nil): return true // indexA exists, indexB is nil, indexA should come first
|
||||||
|
case (nil, _?): return false // indexB exists, indexB is nil, indexB should come first
|
||||||
|
case (nil, nil): return a.key < b.key // both nil, so sort alphabetically
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let detailedDescription = NSMutableAttributedString()
|
||||||
|
|
||||||
|
for (key, value) in sortedUserInfo
|
||||||
|
{
|
||||||
|
let keyName: String
|
||||||
|
switch key
|
||||||
|
{
|
||||||
|
case NSDebugDescriptionErrorKey: keyName = NSLocalizedString("Debug Description", comment: "")
|
||||||
|
case NSLocalizedDescriptionKey: keyName = NSLocalizedString("Error Description", comment: "")
|
||||||
|
case NSLocalizedFailureErrorKey: keyName = NSLocalizedString("Failure", comment: "")
|
||||||
|
case NSLocalizedFailureReasonErrorKey: keyName = NSLocalizedString("Failure Reason", comment: "")
|
||||||
|
case NSLocalizedRecoverySuggestionErrorKey: keyName = NSLocalizedString("Recovery Suggestion", comment: "")
|
||||||
|
case ALTLocalizedTitleErrorKey: keyName = NSLocalizedString("Title", comment: "")
|
||||||
|
// case ALTSourceFileErrorKey: keyName = NSLocalizedString("Source File", comment: "")
|
||||||
|
// case ALTSourceLineErrorKey: keyName = NSLocalizedString("Source Line", comment: "")
|
||||||
|
case NSUnderlyingErrorKey: keyName = NSLocalizedString("Underlying Error", comment: "")
|
||||||
|
default:
|
||||||
|
if #available(iOS 14.5, macOS 11.3, *), key == NSMultipleUnderlyingErrorsKey
|
||||||
|
{
|
||||||
|
keyName = NSLocalizedString("Underlying Errors", comment: "")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keyName = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let attributedKey = NSAttributedString(string: keyName, attributes: [.font: boldFont])
|
||||||
|
let attributedValue = NSAttributedString(string: String(describing: value), attributes: [.font: font])
|
||||||
|
|
||||||
|
let attributedString = NSMutableAttributedString(attributedString: attributedKey)
|
||||||
|
attributedString.mutableString.append("\n")
|
||||||
|
attributedString.append(attributedValue)
|
||||||
|
|
||||||
|
if !detailedDescription.string.isEmpty
|
||||||
|
{
|
||||||
|
detailedDescription.mutableString.append("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
detailedDescription.append(attributedString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support dark mode
|
||||||
|
#if canImport(UIKit)
|
||||||
|
if #available(iOS 13, *)
|
||||||
|
{
|
||||||
|
detailedDescription.addAttribute(.foregroundColor, value: UIColor.label, range: NSMakeRange(0, detailedDescription.length))
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
detailedDescription.addAttribute(.foregroundColor, value: NSColor.labelColor, range: NSMakeRange(0, detailedDescription.length))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return detailedDescription
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Error
|
public extension NSError
|
||||||
|
{
|
||||||
|
typealias UserInfoProvider = (Error, String) -> Any?
|
||||||
|
|
||||||
|
@objc
|
||||||
|
class func alt_setUserInfoValueProvider(forDomain domain: String, provider: UserInfoProvider?) {
|
||||||
|
NSError.setUserInfoValueProvider(forDomain: domain) { error, key in
|
||||||
|
let nsError = error as NSError
|
||||||
|
|
||||||
|
switch key{
|
||||||
|
case NSLocalizedDescriptionKey:
|
||||||
|
if nsError.localizedFailure != nil {
|
||||||
|
// Error has localizedFailure, so return nil to construct localizedDescription from it + localizedFailureReason
|
||||||
|
return nil
|
||||||
|
} else if let localizedDescription = provider?(error, NSLocalizedDescriptionKey) as? String {
|
||||||
|
// Only call provider() if there is no localizedFailure
|
||||||
|
return localizedDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise return failureReason for localizedDescription to avoid system prepending "Operation Failed" message
|
||||||
|
Do NOT return provider(NSLocalizedFailureReason) which might be unexpectedly nil if unrecognized error code. */
|
||||||
|
|
||||||
|
return nsError.localizedFailureReason
|
||||||
|
|
||||||
|
default:
|
||||||
|
return provider?(error, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Error
|
||||||
{
|
{
|
||||||
var underlyingError: Error? {
|
var underlyingError: Error? {
|
||||||
let underlyingError = (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error
|
return (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error
|
||||||
return underlyingError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var localizedErrorCode: String {
|
var localizedErrorCode: String {
|
||||||
let localizedErrorCode = String(format: NSLocalizedString("%@ error %@", comment: ""), (self as NSError).domain, (self as NSError).code as NSNumber)
|
return String(format: NSLocalizedString("%@ %@", comment: ""), (self as NSError).domain, self.displayCode as NSNumber)
|
||||||
return localizedErrorCode
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protocol ALTLocalizedError: LocalizedError, CustomNSError
|
var displayCode: Int {
|
||||||
{
|
guard let serverError = self as? ALTServerError else {
|
||||||
var failure: String? { get }
|
return (self as NSError).code
|
||||||
|
}
|
||||||
var underlyingError: Error? { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ALTLocalizedError
|
/* We want AltServerError codes to start at 2000, but we can't change them without breaking AltServer compatibility.
|
||||||
{
|
Instead we just add 2000 when displaying code to the user to make it appear as if the codes start at 2000 anyway.
|
||||||
var errorUserInfo: [String : Any] {
|
*/
|
||||||
let userInfo = ([
|
return 2000 + serverError.code.rawValue
|
||||||
NSLocalizedDescriptionKey: self.errorDescription,
|
|
||||||
NSLocalizedFailureReasonErrorKey: self.failureReason,
|
|
||||||
NSLocalizedFailureErrorKey: self.failure,
|
|
||||||
NSUnderlyingErrorKey: self.underlyingError
|
|
||||||
] as [String: Any?]).compactMapValues { $0 }
|
|
||||||
return userInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var underlyingError: Error? {
|
|
||||||
// Error's default implementation calls errorUserInfo,
|
|
||||||
// but ALTLocalizedError.errorUserInfo calls underlyingError.
|
|
||||||
// Return nil to prevent infinite recursion.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorDescription: String? {
|
|
||||||
guard let errorFailure = self.failure else { return (self.underlyingError as NSError?)?.localizedDescription }
|
|
||||||
guard let failureReason = self.failureReason else { return errorFailure }
|
|
||||||
|
|
||||||
let errorDescription = errorFailure + " " + failureReason
|
|
||||||
return errorDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
var failureReason: String? { (self.underlyingError as NSError?)?.localizedDescription }
|
|
||||||
var recoverySuggestion: String? { (self.underlyingError as NSError?)?.localizedRecoverySuggestion }
|
|
||||||
var helpAnchor: String? { (self.underlyingError as NSError?)?.helpAnchor }
|
|
||||||
}
|
}
|
||||||
|
|||||||
249
Shared/Server Protocol/CodableError.swift
Normal file
249
Shared/Server Protocol/CodableError.swift
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
//
|
||||||
|
// CodableError.swift
|
||||||
|
// AltKit
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 3/5/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
|
||||||
|
extension ALTServerError.Code: Codable {}
|
||||||
|
|
||||||
|
private extension ErrorUserInfoKey
|
||||||
|
{
|
||||||
|
static let altLocalizedDescription: String = "ALTLocalizedDescription"
|
||||||
|
static let altLocalizedFailureReason: String = "ALTLocalizedFailureReason"
|
||||||
|
static let altLocalizedRecoverySuggestion: String = "ALTLocalizedRecoverySuggestion"
|
||||||
|
static let altDebugDescription: String = "ALTDebugDescription"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CodableError
|
||||||
|
{
|
||||||
|
enum UserInfoValue: Codable
|
||||||
|
{
|
||||||
|
case unknown
|
||||||
|
case string(String)
|
||||||
|
case number(Int)
|
||||||
|
case error(NSError)
|
||||||
|
case codableError(CodableError)
|
||||||
|
indirect case array([UserInfoValue])
|
||||||
|
indirect case dictionary([String: UserInfoValue])
|
||||||
|
|
||||||
|
var value: Any? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .unknown: return nil
|
||||||
|
case .string(let string): return string
|
||||||
|
case .number(let number): return number
|
||||||
|
case .error(let error): return error
|
||||||
|
case .codableError(let error): return error.error
|
||||||
|
case .array(let array): return array.compactMap { $0.value } // .compactMap instead of .map to ensure nil values are removed.
|
||||||
|
case .dictionary(let dictionary): return dictionary.compactMapValues { $0.value } // .compactMapValues instead of .mapValues to ensure nil values are removed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var codableValue: Codable? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .unknown, .string, .number: return self.value as? Codable
|
||||||
|
case .codableError(let error): return error
|
||||||
|
case .error(let nsError):
|
||||||
|
// Ignore error because we don't want to fail completely if error contains invalid user info value.
|
||||||
|
let sanitizedError = nsError.sanitizedForSerialization()
|
||||||
|
let data = try? NSKeyedArchiver.archivedData(withRootObject: sanitizedError, requiringSecureCoding: true)
|
||||||
|
return data
|
||||||
|
|
||||||
|
case .array(let array): return array
|
||||||
|
case .dictionary(let dictionary): return dictionary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ rawValue: Any?)
|
||||||
|
{
|
||||||
|
switch rawValue
|
||||||
|
{
|
||||||
|
case let string as String: self = .string(string)
|
||||||
|
case let number as Int: self = .number(number)
|
||||||
|
case let error as NSError: self = .codableError(CodableError(error: error))
|
||||||
|
case let array as [Any]: self = .array(array.compactMap(UserInfoValue.init))
|
||||||
|
case let dictionary as [String: Any]: self = .dictionary(dictionary.compactMapValues(UserInfoValue.init))
|
||||||
|
default: self = .unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
|
if
|
||||||
|
let data = try? container.decode(Data.self),
|
||||||
|
let error = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSError.self, from: data)
|
||||||
|
{
|
||||||
|
self = .error(error)
|
||||||
|
}
|
||||||
|
else if let codableError = try? container.decode(CodableError.self)
|
||||||
|
{
|
||||||
|
self = .codableError(codableError)
|
||||||
|
}
|
||||||
|
else if let string = try? container.decode(String.self)
|
||||||
|
{
|
||||||
|
self = .string(string)
|
||||||
|
}
|
||||||
|
else if let number = try? container.decode(Int.self)
|
||||||
|
{
|
||||||
|
self = .number(number)
|
||||||
|
}
|
||||||
|
else if let array = try? container.decode([UserInfoValue].self)
|
||||||
|
{
|
||||||
|
self = .array(array)
|
||||||
|
}
|
||||||
|
else if let dictionary = try? container.decode([String: UserInfoValue].self)
|
||||||
|
{
|
||||||
|
self = .dictionary(dictionary)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self = .unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws
|
||||||
|
{
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
|
if let value = self.codableValue
|
||||||
|
{
|
||||||
|
try container.encode(value)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try container.encodeNil()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CodableError: Codable
|
||||||
|
{
|
||||||
|
var error: Error {
|
||||||
|
return self.rawError ?? NSError(domain: self.errorDomain, code: self.errorCode, userInfo: self.userInfo ?? [:])
|
||||||
|
}
|
||||||
|
private var rawError: Error?
|
||||||
|
|
||||||
|
private var errorDomain: String
|
||||||
|
private var errorCode: Int
|
||||||
|
private var userInfo: [String: Any]?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey
|
||||||
|
{
|
||||||
|
case errorDomain
|
||||||
|
case errorCode
|
||||||
|
case legacyUserInfo = "userInfo"
|
||||||
|
case errorUserInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
init(error: Error)
|
||||||
|
{
|
||||||
|
self.rawError = error
|
||||||
|
|
||||||
|
let nsError = error as NSError
|
||||||
|
self.errorDomain = nsError.domain
|
||||||
|
self.errorCode = nsError.code
|
||||||
|
|
||||||
|
if !nsError.userInfo.isEmpty
|
||||||
|
{
|
||||||
|
self.userInfo = nsError.userInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
// Assume ALTServerError.errorDomain if no explicit domain provided.
|
||||||
|
self.errorDomain = try container.decodeIfPresent(String.self, forKey: .errorDomain) ?? ALTServerError.errorDomain
|
||||||
|
self.errorCode = try container.decode(Int.self, forKey: .errorCode)
|
||||||
|
|
||||||
|
if let rawUserInfo = try container.decodeIfPresent([String: UserInfoValue].self, forKey: .errorUserInfo)
|
||||||
|
{
|
||||||
|
// Attempt decoding from .errorUserInfo first, because it will gracefully handle unknown user info values.
|
||||||
|
|
||||||
|
// Copy ALTLocalized... values to NSLocalized... if provider is nil or if error is unrecognized.
|
||||||
|
// This ensures we preserve error messages if receiving an unknown error.
|
||||||
|
var userInfo = rawUserInfo.compactMapValues { $0.value }
|
||||||
|
|
||||||
|
// Recognized == the provider returns value for NSLocalizedFailureReasonErrorKey, or error is ALTServerError.underlyingError.
|
||||||
|
let provider = NSError.userInfoValueProvider(forDomain: self.errorDomain)
|
||||||
|
let isRecognizedError = (
|
||||||
|
provider?(self.error, NSLocalizedFailureReasonErrorKey) != nil ||
|
||||||
|
(self.error._domain == ALTServerError.errorDomain && self.error._code == ALTServerError.underlyingError.rawValue)
|
||||||
|
)
|
||||||
|
|
||||||
|
if !isRecognizedError
|
||||||
|
{
|
||||||
|
// Error not recognized, so copy over NSLocalizedDescriptionKey and NSLocalizedFailureReasonErrorKey.
|
||||||
|
userInfo[NSLocalizedDescriptionKey] = userInfo[ErrorUserInfoKey.altLocalizedDescription]
|
||||||
|
userInfo[NSLocalizedFailureReasonErrorKey] = userInfo[ErrorUserInfoKey.altLocalizedFailureReason]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over NSLocalizedRecoverySuggestionErrorKey and NSDebugDescriptionErrorKey if provider returns nil.
|
||||||
|
if provider?(self.error, NSLocalizedRecoverySuggestionErrorKey) == nil
|
||||||
|
{
|
||||||
|
userInfo[NSLocalizedRecoverySuggestionErrorKey] = userInfo[ErrorUserInfoKey.altLocalizedRecoverySuggestion]
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider?(self.error, NSDebugDescriptionErrorKey) == nil
|
||||||
|
{
|
||||||
|
userInfo[NSDebugDescriptionErrorKey] = userInfo[ErrorUserInfoKey.altDebugDescription]
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedDescription] = nil
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedFailureReason] = nil
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedRecoverySuggestion] = nil
|
||||||
|
userInfo[ErrorUserInfoKey.altDebugDescription] = nil
|
||||||
|
|
||||||
|
self.userInfo = userInfo
|
||||||
|
}
|
||||||
|
else if let rawUserInfo = try container.decodeIfPresent([String: UserInfoValue].self, forKey: .legacyUserInfo)
|
||||||
|
{
|
||||||
|
// Fall back to decoding .legacyUserInfo, which only supports String and NSError values.
|
||||||
|
let userInfo = rawUserInfo.compactMapValues { $0.value }
|
||||||
|
self.userInfo = userInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws
|
||||||
|
{
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(self.errorDomain, forKey: .errorDomain)
|
||||||
|
try container.encode(self.errorCode, forKey: .errorCode)
|
||||||
|
|
||||||
|
let rawLegacyUserInfo = self.userInfo?.compactMapValues { (value) -> UserInfoValue? in
|
||||||
|
// .legacyUserInfo only supports String and NSError values.
|
||||||
|
switch value
|
||||||
|
{
|
||||||
|
case let string as String: return .string(string)
|
||||||
|
case let error as NSError: return .error(error) // Must use .error, not .codableError for backwards compatibility.
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try container.encodeIfPresent(rawLegacyUserInfo, forKey: .legacyUserInfo)
|
||||||
|
|
||||||
|
let nsError = self.error as NSError
|
||||||
|
|
||||||
|
var userInfo = self.userInfo ?? [:]
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedDescription] = nsError.localizedDescription
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedFailureReason] = nsError.localizedFailureReason
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedRecoverySuggestion] = nsError.localizedRecoverySuggestion
|
||||||
|
userInfo[ErrorUserInfoKey.altDebugDescription] = nsError.localizedDebugDescription
|
||||||
|
|
||||||
|
// No need to use alternate key. This is a no-op if userInfo already contains localizedFailure,
|
||||||
|
// but it caches the UserInfoProvider value if one exists.
|
||||||
|
userInfo[NSLocalizedFailureErrorKey] = nsError.localizedFailure
|
||||||
|
|
||||||
|
let rawUserInfo = userInfo.compactMapValues { UserInfoValue($0) }
|
||||||
|
try container.encodeIfPresent(rawUserInfo, forKey: .errorUserInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
//
|
|
||||||
// CodableServerError.swift
|
|
||||||
// AltKit
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 3/5/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
|
|
||||||
extension ALTServerError.Code: Codable {}
|
|
||||||
|
|
||||||
extension CodableServerError
|
|
||||||
{
|
|
||||||
enum UserInfoValue: Codable
|
|
||||||
{
|
|
||||||
case string(String)
|
|
||||||
case error(NSError)
|
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws
|
|
||||||
{
|
|
||||||
let container = try decoder.singleValueContainer()
|
|
||||||
|
|
||||||
if
|
|
||||||
let data = try? container.decode(Data.self),
|
|
||||||
let error = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSError.self, from: data)
|
|
||||||
{
|
|
||||||
self = .error(error)
|
|
||||||
}
|
|
||||||
else if let string = try? container.decode(String.self)
|
|
||||||
{
|
|
||||||
self = .string(string)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "UserInfoValue value cannot be decoded")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws
|
|
||||||
{
|
|
||||||
var container = encoder.singleValueContainer()
|
|
||||||
|
|
||||||
switch self
|
|
||||||
{
|
|
||||||
case .string(let string): try container.encode(string)
|
|
||||||
case .error(let error):
|
|
||||||
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: error, requiringSecureCoding: true) else {
|
|
||||||
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "UserInfoValue value \(self) cannot be encoded")
|
|
||||||
throw EncodingError.invalidValue(self, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
try container.encode(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CodableServerError: Codable
|
|
||||||
{
|
|
||||||
var error: ALTServerError {
|
|
||||||
return ALTServerError(self.errorCode, userInfo: self.userInfo ?? [:])
|
|
||||||
}
|
|
||||||
|
|
||||||
private var errorCode: ALTServerError.Code
|
|
||||||
private var userInfo: [String: Any]?
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey
|
|
||||||
{
|
|
||||||
case errorCode
|
|
||||||
case userInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
init(error: ALTServerError)
|
|
||||||
{
|
|
||||||
self.errorCode = error.code
|
|
||||||
|
|
||||||
var userInfo = error.userInfo
|
|
||||||
if let localizedRecoverySuggestion = (error as NSError).localizedRecoverySuggestion
|
|
||||||
{
|
|
||||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = localizedRecoverySuggestion
|
|
||||||
}
|
|
||||||
|
|
||||||
if !userInfo.isEmpty
|
|
||||||
{
|
|
||||||
self.userInfo = userInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws
|
|
||||||
{
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
let errorCode = try container.decode(Int.self, forKey: .errorCode)
|
|
||||||
self.errorCode = ALTServerError.Code(rawValue: errorCode) ?? .unknown
|
|
||||||
|
|
||||||
let rawUserInfo = try container.decodeIfPresent([String: UserInfoValue].self, forKey: .userInfo)
|
|
||||||
|
|
||||||
let userInfo = rawUserInfo?.mapValues { (value) -> Any in
|
|
||||||
switch value
|
|
||||||
{
|
|
||||||
case .string(let string): return string
|
|
||||||
case .error(let error): return error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.userInfo = userInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws
|
|
||||||
{
|
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
||||||
try container.encode(self.error.code.rawValue, forKey: .errorCode)
|
|
||||||
|
|
||||||
let rawUserInfo = self.userInfo?.compactMapValues { (value) -> UserInfoValue? in
|
|
||||||
switch value
|
|
||||||
{
|
|
||||||
case let string as String: return .string(string)
|
|
||||||
case let error as NSError: return .error(error)
|
|
||||||
default: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try container.encodeIfPresent(rawUserInfo, forKey: .userInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -197,20 +197,20 @@ public enum ServerResponse: Decodable
|
|||||||
// from easily changing response format for a request in the future.
|
// from easily changing response format for a request in the future.
|
||||||
public struct ErrorResponse: ServerMessageProtocol
|
public struct ErrorResponse: ServerMessageProtocol
|
||||||
{
|
{
|
||||||
public var version = 2
|
public var version = 3
|
||||||
public var identifier = "ErrorResponse"
|
public var identifier = "ErrorResponse"
|
||||||
|
|
||||||
public var error: ALTServerError {
|
public var error: ALTServerError {
|
||||||
return self.serverError?.error ?? ALTServerError(self.errorCode)
|
return self.serverError.map { ALTServerError($0.error) } ?? ALTServerError(self.errorCode)
|
||||||
}
|
}
|
||||||
private var serverError: CodableServerError?
|
private var serverError: CodableError?
|
||||||
|
|
||||||
// Legacy (v1)
|
// Legacy (v1)
|
||||||
private var errorCode: ALTServerError.Code
|
private var errorCode: ALTServerError.Code
|
||||||
|
|
||||||
public init(error: ALTServerError)
|
public init(error: ALTServerError)
|
||||||
{
|
{
|
||||||
self.serverError = CodableServerError(error: error)
|
self.serverError = CodableError(error: error)
|
||||||
self.errorCode = error.code
|
self.errorCode = error.code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user