From 728dcd85239dc670f9070de25f18052f7b8cacfa Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 8 Sep 2022 16:14:28 -0500 Subject: [PATCH] Adds LoggedError Core Data entity Allows us to save certain errors to disk so that they can be viewed again later from an error log. --- AltStore.xcodeproj/project.pbxproj | 4 + .../AltStore 10.xcdatamodel/contents | 29 ++-- AltStoreCore/Model/InstalledApp.swift | 2 + AltStoreCore/Model/LoggedError.swift | 126 ++++++++++++++++++ AltStoreCore/Model/StoreApp.swift | 2 + 5 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 AltStoreCore/Model/LoggedError.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index b651376e..e8218e6e 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -327,6 +327,7 @@ D57DF63F271E51E400677701 /* ALTAppPatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = D57DF63E271E51E400677701 /* ALTAppPatcher.m */; }; D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */; }; D57F2C9426E01BC700B9FA39 /* UIDevice+Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */; }; + D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58916FD28C7C55C00E39C8B /* LoggedError.swift */; }; D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; }; D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; }; D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */; }; @@ -825,6 +826,7 @@ D57DF63E271E51E400677701 /* ALTAppPatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTAppPatcher.m; sourceTree = ""; }; D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableJITOperation.swift; sourceTree = ""; }; D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Vibration.swift"; sourceTree = ""; }; + D58916FD28C7C55C00E39C8B /* LoggedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedError.swift; sourceTree = ""; }; D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = ""; }; D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = ""; }; D5CA0C4C280E242500469595 /* AltStore 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 10.xcdatamodel"; sourceTree = ""; }; @@ -1296,6 +1298,7 @@ BF66EECA2501AECA007EE018 /* DatabaseManager.swift */, BF66EEC02501AECA007EE018 /* InstalledApp.swift */, BF66EECB2501AECA007EE018 /* InstalledExtension.swift */, + D58916FD28C7C55C00E39C8B /* LoggedError.swift */, BF66EEC52501AECA007EE018 /* MergePolicy.swift */, BF66EEBF2501AECA007EE018 /* NewsItem.swift */, BF66EEC82501AECA007EE018 /* PatreonAccount.swift */, @@ -2333,6 +2336,7 @@ BF66EECF2501AECA007EE018 /* AltStoreToAltStore2.xcmappingmodel in Sources */, BF66EEA82501AEC5007EE018 /* Patron.swift in Sources */, BF66EEDD2501AECA007EE018 /* AppPermission.swift in Sources */, + D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */, BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */, D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */, BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */, diff --git a/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 10.xcdatamodel/contents b/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 10.xcdatamodel/contents index fec61865..82d3a30c 100644 --- a/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 10.xcdatamodel/contents +++ b/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 10.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -45,6 +45,7 @@ + @@ -63,6 +64,17 @@ + + + + + + + + + + + @@ -146,6 +158,7 @@ + @@ -170,18 +183,4 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/AltStoreCore/Model/InstalledApp.swift b/AltStoreCore/Model/InstalledApp.swift index 3caca0de..f2ea3003 100644 --- a/AltStoreCore/Model/InstalledApp.swift +++ b/AltStoreCore/Model/InstalledApp.swift @@ -53,6 +53,8 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol @NSManaged public var team: Team? @NSManaged public var appExtensions: Set + @NSManaged public private(set) var loggedErrors: NSSet /* Set */ // Use NSSet to avoid eagerly fetching values. + public var isSideloaded: Bool { return self.storeApp == nil } diff --git a/AltStoreCore/Model/LoggedError.swift b/AltStoreCore/Model/LoggedError.swift new file mode 100644 index 00000000..81835e51 --- /dev/null +++ b/AltStoreCore/Model/LoggedError.swift @@ -0,0 +1,126 @@ +// +// LoggedError.swift +// AltStoreCore +// +// Created by Riley Testut on 9/6/22. +// Copyright © 2022 Riley Testut. All rights reserved. +// + +import CoreData + +extension LoggedError +{ + public enum Operation: String + { + case install + case update + case refresh + case activate + case deactivate + case backup + case restore + } +} + +@objc(LoggedError) +public class LoggedError: NSManagedObject, Fetchable +{ + /* Properties */ + @NSManaged public private(set) var date: Date + + @nonobjc public var operation: Operation? { + guard let rawOperation = self._operation else { return nil } + + let operation = Operation(rawValue: rawOperation) + return operation + } + @NSManaged @objc(operation) private var _operation: String? + + @NSManaged public private(set) var domain: String + @NSManaged public private(set) var code: Int32 + @NSManaged public private(set) var userInfo: [String: Any] + + @NSManaged public private(set) var appName: String + @NSManaged public private(set) var appBundleID: String + + /* Relationships */ + @NSManaged public private(set) var storeApp: StoreApp? + @NSManaged public private(set) var installedApp: InstalledApp? + + private static let dateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .none + return dateFormatter + }() + + private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) + { + super.init(entity: entity, insertInto: context) + } + + public init(error: Error, app: AppProtocol, date: Date = Date(), operation: Operation? = nil, context: NSManagedObjectContext) + { + super.init(entity: LoggedError.entity(), insertInto: context) + + self.date = date + self._operation = operation?.rawValue + + let nsError = error as NSError + self.domain = nsError.domain + self.code = Int32(nsError.code) + self.userInfo = nsError.userInfo + + self.appName = app.name + self.appBundleID = app.bundleIdentifier + + switch app + { + case let storeApp as StoreApp: self.storeApp = storeApp + case let installedApp as InstalledApp: self.installedApp = installedApp + default: break + } + } +} + +public extension LoggedError +{ + var app: AppProtocol { + // `as AppProtocol` needed to fix "cannot convert AnyApp to StoreApp" compiler error with Xcode 14. + let app = self.installedApp ?? self.storeApp ?? AnyApp(name: self.appName, bundleIdentifier: self.appBundleID, url: nil) as AppProtocol + return app + } + + var error: Error { + let nsError = NSError(domain: self.domain, code: Int(self.code), userInfo: self.userInfo) + return nsError + } + + @objc + var localizedDateString: String { + let localizedDateString = LoggedError.dateFormatter.string(from: self.date) + return localizedDateString + } + + var localizedFailure: String? { + guard let operation = self.operation else { return nil } + switch operation + { + case .install: return String(format: NSLocalizedString("Install %@ Failed", comment: ""), self.appName) + case .update: return String(format: NSLocalizedString("Update %@ Failed", comment: ""), self.appName) + case .refresh: return String(format: NSLocalizedString("Refresh %@ Failed", comment: ""), self.appName) + case .activate: return String(format: NSLocalizedString("Activate %@ Failed", comment: ""), self.appName) + case .deactivate: return String(format: NSLocalizedString("Deactivate %@ Failed", comment: ""), self.appName) + case .backup: return String(format: NSLocalizedString("Backup %@ Failed", comment: ""), self.appName) + case .restore: return String(format: NSLocalizedString("Restore %@ Failed", comment: ""), self.appName) + } + } +} + +public extension LoggedError +{ + @nonobjc class func fetchRequest() -> NSFetchRequest + { + return NSFetchRequest(entityName: "LoggedError") + } +} diff --git a/AltStoreCore/Model/StoreApp.swift b/AltStoreCore/Model/StoreApp.swift index c76c54f8..6c145140 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -129,6 +129,8 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable @NSManaged @objc(source) public var _source: Source? @NSManaged @objc(permissions) public var _permissions: NSOrderedSet + @NSManaged public private(set) var loggedErrors: NSSet /* Set */ // Use NSSet to avoid eagerly fetching values. + @nonobjc public var source: Source? { set { self._source = newValue