mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
* Change error from Swift.Error to NSError
* Adds ResultOperation.localizedFailure
* Finish Riley's monster commit
3b38d725d7
May the Gods have mercy on my soul.
* Fix format strings I broke
* Include "Enable JIT" errors in Error Log
* Fix minimuxer status checking
* [skip ci] Update the no wifi message to include VPN
* Opens Error Log when tapping ToastView
* Fixes Error Log context menu covering cell content
* Fixes Error Log context menu appearing while scrolling
* Fixes incorrect Search FAQ URL
* Fix Error Log showing UIAlertController on iOS 14+
* Fix Error Log not showing UIAlertController on iOS <=13
* Fix wrong color in AuthenticationViewController
* Fix typo
* Fixes logging non-AltServerErrors as AltServerError.underlyingError
* Limits quitting other AltStore/SideStore processes to database migrations
* Skips logging cancelled errors
* Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion
We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors.
However, we kept the underlying Core Data property name the same to avoid extra migration.
* Conforms OperatingSystemVersion to Comparable
* Parses AppVersion.minOSVersion/maxOSVersion from source JSON
* Supports non-NSManagedObjects for @Managed properties
This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before.
* Supports optional @Managed properties
* Conforms AppVersion to AppProtocol
* Verifies min/max OS version before downloading app + asks user to download older app version if necessary
* Improves error message when file does not exist at AppVersion.downloadURL
* Removes unnecessary StoreApp convenience properties
* Removes unnecessary StoreApp convenience properties as well as fix other issues
* Remove Settings bundle, add SwiftUI view instead
Fix refresh all shortcut intent
* Update AuthenticationOperation.swift
Signed-off-by: June Park <rjp2030@outlook.com>
* Fix build issues given by develop
* Add availability check to fix CI build(?)
* If it's gonna be that way...
---------
Signed-off-by: June Park <rjp2030@outlook.com>
Co-authored-by: nythepegasus <nythepegasus84@gmail.com>
Co-authored-by: Riley Testut <riley@rileytestut.com>
Co-authored-by: ny <me@nythepegas.us>
250 lines
9.7 KiB
Swift
250 lines
9.7 KiB
Swift
//
|
|
// 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)
|
|
}
|
|
}
|