diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index e37bbc62..9a843c98 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -355,6 +355,7 @@ D51AD27E29356B7B00967AAA /* ALTWrappedError.h in Headers */ = {isa = PBXBuildFile; fileRef = D51AD27C29356B7B00967AAA /* ALTWrappedError.h */; settings = {ATTRIBUTES = (Public, ); }; }; D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; }; D51AD28029356B8000967AAA /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; }; + D52A2F972ACB40F700BDF8E3 /* Logger+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */; }; D52C08EE28AEC37A006C4AE5 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */; }; D52DD35E2AAA89A600A7F2B6 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = D52DD35D2AAA89A600A7F2B6 /* AltSign-Dynamic */; }; D52EF2BE2A0594550096C377 /* AppDetailCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */; }; @@ -956,6 +957,7 @@ D5189C002A01BC6800F44625 /* UserInfoValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoValue.swift; sourceTree = ""; }; D51AD27C29356B7B00967AAA /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = ""; }; D51AD27D29356B7B00967AAA /* ALTWrappedError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWrappedError.m; sourceTree = ""; }; + D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+AltStore.swift"; sourceTree = ""; }; D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; D52E988928D002D30032BE6B /* AltStore 11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 11.xcdatamodel"; sourceTree = ""; }; D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailCollectionViewController.swift; sourceTree = ""; }; @@ -1580,6 +1582,7 @@ BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */, D5F48B4729CCF21B002B52A4 /* AltStore+Async.swift */, D5893F7E2A14183200E767CD /* NSManagedObjectContext+Conveniences.swift */, + D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */, ); path = Extensions; sourceTree = ""; @@ -2999,6 +3002,7 @@ BFAECC572501B0A400528F27 /* ConnectionManager.swift in Sources */, BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */, D519AD46292D665B004B12F9 /* Managed.swift in Sources */, + D52A2F972ACB40F700BDF8E3 /* Logger+AltStore.swift in Sources */, BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */, D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */, D5708417292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift in Sources */, diff --git a/AltStore/Operations/AuthenticationOperation.swift b/AltStore/Operations/AuthenticationOperation.swift index b29245c9..732ed35c 100644 --- a/AltStore/Operations/AuthenticationOperation.swift +++ b/AltStore/Operations/AuthenticationOperation.swift @@ -202,7 +202,11 @@ class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppl { guard !self.isFinished else { return } - print("Finished authenticating with result:", result.error?.localizedDescription ?? "success") + switch result + { + case .failure(let error): Logger.sideload.error("Failed to authenticate account. \(error.localizedDescription, privacy: .public)") + case .success((let team, _, _)): Logger.sideload.notice("Authenticated account for team \(team.identifier, privacy: .public).") + } let context = DatabaseManager.shared.persistentContainer.newBackgroundContext() context.perform { @@ -347,6 +351,8 @@ private extension AuthenticationOperation if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword { + Logger.sideload.notice("Authenticating Apple ID...") + self.authenticate(appleID: appleID, password: password) { (result) in switch result { diff --git a/AltStore/Operations/BackgroundRefreshAppsOperation.swift b/AltStore/Operations/BackgroundRefreshAppsOperation.swift index 407a776e..16049d8b 100644 --- a/AltStore/Operations/BackgroundRefreshAppsOperation.swift +++ b/AltStore/Operations/BackgroundRefreshAppsOperation.swift @@ -103,7 +103,7 @@ class BackgroundRefreshAppsOperation: ResultOperation<[String: Result // Failed too quickly for human to respond to alert, possibly still finalizing installation. // Try again in a couple seconds. - print("Failed too quickly, retrying after a few seconds...") - + Logger.sideload.error("Failed to open app too quickly, retrying after a few seconds...") + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { UIApplication.shared.open(openURL, options: [:]) { (success) in if success diff --git a/AltStore/Operations/ClearAppCacheOperation.swift b/AltStore/Operations/ClearAppCacheOperation.swift index 614dc75f..d8c30c8a 100644 --- a/AltStore/Operations/ClearAppCacheOperation.swift +++ b/AltStore/Operations/ClearAppCacheOperation.swift @@ -108,12 +108,12 @@ private extension ClearAppCacheOperation { do { - print("[ALTLog] Removing item from temporary directory:", fileURL.lastPathComponent) + Logger.main.debug("Removing item from temporary directory: \(fileURL.lastPathComponent, privacy: .public)") try FileManager.default.removeItem(at: fileURL) } catch { - print("[ALTLog] Failed to remove \(fileURL.lastPathComponent) from temporary directory.", error) + Logger.main.error("Failed to remove \(fileURL.lastPathComponent) from temporary directory. \(error.localizedDescription, privacy: .public)") errors.append(error) } } @@ -171,13 +171,13 @@ private extension ClearAppCacheOperation if isDirectory && !installedAppBundleIDs.contains(bundleID) && !AppManager.shared.isActivelyManagingApp(withBundleID: bundleID) { - print("[ALTLog] Removing backup directory for uninstalled app:", bundleID) + Logger.main.debug("Removing backup directory for uninstalled app: \(bundleID, privacy: .public)") try FileManager.default.removeItem(at: backupDirectory) } } catch { - print("[ALTLog] Failed to remove app backup directory:", error) + Logger.main.error("Failed to remove app backup directory. \(error.localizedDescription, privacy: .public)") errors.append(error) } } @@ -194,7 +194,7 @@ private extension ClearAppCacheOperation } catch { - print("[ALTLog] Failed to remove app backup directory:", error) + Logger.main.error("Failed to remove app backup directory. \(error.localizedDescription, privacy: .public)") completion(.failure(error)) } } diff --git a/AltStore/Operations/DeactivateAppOperation.swift b/AltStore/Operations/DeactivateAppOperation.swift index 6afa521b..2dd425e7 100644 --- a/AltStore/Operations/DeactivateAppOperation.swift +++ b/AltStore/Operations/DeactivateAppOperation.swift @@ -39,12 +39,14 @@ class DeactivateAppOperation: ResultOperation guard let server = self.context.server else { return self.finish(.failure(OperationError.invalidParameters)) } guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return self.finish(.failure(OperationError.unknownUDID)) } + Logger.sideload.notice("Deactivating app \(self.app.bundleIdentifier, privacy: .public)...") + ServerManager.shared.connect(to: server) { (result) in switch result { case .failure(let error): self.finish(.failure(error)) case .success(let connection): - print("Sending deactivate app request...") + Logger.sideload.notice("Sending deactivate app request...") DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in let installedApp = context.object(with: self.app.objectID) as! InstalledApp @@ -54,21 +56,28 @@ class DeactivateAppOperation: ResultOperation let request = RemoveProvisioningProfilesRequest(udid: udid, bundleIdentifiers: Set(allIdentifiers)) connection.send(request) { (result) in - print("Sent deactive app request!") - switch result { - case .failure(let error): self.finish(.failure(error)) + case .failure(let error): + Logger.sideload.error("Failed to send deactivate app request. \(error.localizedDescription, privacy: .public)") + self.finish(.failure(error)) + case .success: - print("Waiting for deactivate app response...") + Logger.sideload.debug("Waiting for deactivate app response...") connection.receiveResponse() { (result) in - print("Receiving deactivate app response:", result) - switch result { - case .failure(let error): self.finish(.failure(error)) - case .success(.error(let response)): self.finish(.failure(response.error)) + case .failure(let error): + Logger.sideload.error("Failed to receive deactivate app response. \(error.localizedDescription, privacy: .public)") + self.finish(.failure(error)) + + case .success(.error(let response)): + Logger.sideload.error("Failed to deactivate app. \(response.error.localizedDescription, privacy: .public)") + self.finish(.failure(response.error)) + case .success(.removeProvisioningProfiles): + Logger.sideload.notice("Successfully deactivated app \(self.app.bundleIdentifier, privacy: .public)!") + DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in self.progress.completedUnitCount += 1 diff --git a/AltStore/Operations/DownloadAppOperation.swift b/AltStore/Operations/DownloadAppOperation.swift index d5c05a19..7fb2e597 100644 --- a/AltStore/Operations/DownloadAppOperation.swift +++ b/AltStore/Operations/DownloadAppOperation.swift @@ -50,7 +50,7 @@ class DownloadAppOperation: ResultOperation return } - print("Downloading App:", self.bundleIdentifier) + Logger.sideload.notice("Downloading app \(self.bundleIdentifier, privacy: .public)...") // Set _after_ checking self.context.error to prevent overwriting localized failure for previous errors. self.localizedFailure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), self.appName) @@ -108,7 +108,7 @@ class DownloadAppOperation: ResultOperation } catch { - print("Failed to remove DownloadAppOperation temporary directory: \(self.temporaryDirectory).", error) + Logger.sideload.error("Failed to remove DownloadAppOperation temporary directory: \(self.temporaryDirectory, privacy: .public). \(error.localizedDescription, privacy: .public)") } super.finish(result) @@ -172,6 +172,9 @@ private extension DownloadAppOperation try FileManager.default.copyItem(at: application.fileURL, to: self.destinationURL, shouldReplace: true) guard let copiedApplication = ALTApplication(fileURL: self.destinationURL) else { throw OperationError.invalidApp } + + Logger.sideload.notice("Downloaded app \(copiedApplication.bundleIdentifier, privacy: .public) from \(sourceURL, privacy: .public)") + self.finish(.success(copiedApplication)) self.progress.completedUnitCount += 1 diff --git a/AltStore/Operations/EnableJITOperation.swift b/AltStore/Operations/EnableJITOperation.swift index 713823ca..c75d75dd 100644 --- a/AltStore/Operations/EnableJITOperation.swift +++ b/AltStore/Operations/EnableJITOperation.swift @@ -43,6 +43,8 @@ class EnableJITOperation: ResultOperation guard let server = self.context.server, let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) } guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return self.finish(.failure(OperationError.unknownUDID)) } + Logger.altjit.notice("Enabling JIT for app \(installedApp.bundleIdentifier, privacy: .public)...") + installedApp.managedObjectContext?.perform { guard let bundle = Bundle(url: installedApp.fileURL), let processName = bundle.executableURL?.lastPathComponent @@ -56,7 +58,7 @@ class EnableJITOperation: ResultOperation { case .failure(let error): self.finish(.failure(error)) case .success(let connection): - print("Sending enable JIT request...") + Logger.altjit.debug("Sending enable JIT request...") DispatchQueue.main.async { @@ -69,22 +71,31 @@ class EnableJITOperation: ResultOperation let result = Future, Never> { promise in let request = EnableUnsignedCodeExecutionRequest(udid: udid, processName: processName) connection.send(request) { result in - print("Sent enable JIT request!") + Logger.altjit.debug("Sent enable JIT request!") switch result { case .failure(let error): promise(.success(.failure(error))) case .success: - print("Waiting for enable JIT response...") + Logger.altjit.debug("Waiting for enable JIT response...") connection.receiveResponse() { result in - print("Received enable JIT response:", result) - switch result { - case .failure(let error): promise(.success(.failure(error))) - case .success(.error(let response)): promise(.success(.failure(response.error))) - case .success(.enableUnsignedCodeExecution): promise(.success(.success(()))) - case .success: promise(.success(.failure(ALTServerError(.unknownResponse)))) + case .failure(let error): + Logger.altjit.error("Failed to receive enable JIT response. \(error.localizedDescription, privacy: .public)") + promise(.success(.failure(error))) + + case .success(.error(let response)): + Logger.altjit.error("Failed to enable JIT. \(response.error.localizedDescription, privacy: .public)") + promise(.success(.failure(response.error))) + + case .success(.enableUnsignedCodeExecution): + Logger.altjit.notice("Successfully enabled JIT!") + promise(.success(.success(()))) + + case .success: + Logger.altjit.notice("Received unknown enable JIT response.") + promise(.success(.failure(ALTServerError(.unknownResponse)))) } } } diff --git a/AltStore/Operations/FetchAnisetteDataOperation.swift b/AltStore/Operations/FetchAnisetteDataOperation.swift index 97873cd6..2ce31d88 100644 --- a/AltStore/Operations/FetchAnisetteDataOperation.swift +++ b/AltStore/Operations/FetchAnisetteDataOperation.swift @@ -34,32 +34,42 @@ class FetchAnisetteDataOperation: ResultOperation guard let server = self.context.server else { return self.finish(.failure(OperationError.invalidParameters)) } + Logger.sideload.notice("Fetching anisette data...") + ServerManager.shared.connect(to: server) { (result) in switch result { case .failure(let error): self.finish(.failure(error)) case .success(let connection): - print("Sending anisette data request...") + Logger.sideload.debug("Sending anisette data request...") let request = AnisetteDataRequest() connection.send(request) { (result) in - print("Sent anisette data request!") - switch result { - case .failure(let error): self.finish(.failure(error)) + case .failure(let error): + Logger.sideload.error("Failed to send anisette data request. \(error.localizedDescription, privacy: .public)") + self.finish(.failure(error)) + case .success: - print("Waiting for anisette data...") + Logger.sideload.debug("Waiting for anisette data...") connection.receiveResponse() { (result) in - print("Receiving anisette data:", result.error?.localizedDescription ?? "success") - switch result { - case .failure(let error): self.finish(.failure(error)) - case .success(.error(let response)): self.finish(.failure(response.error)) - case .success(.anisetteData(let response)): self.finish(.success(response.anisetteData)) - case .success: self.finish(.failure(ALTServerError(.unknownRequest))) + case .failure(let error): + Logger.sideload.error("Failed to receive anisette data response. \(error.localizedDescription, privacy: .public)") + self.finish(.failure(error)) + + case .success(.error(let response)): + Logger.sideload.error("Failed to receive anisette data. \(response.error.localizedDescription, privacy: .public)") + self.finish(.failure(response.error)) + + case .success(.anisetteData(let response)): + Logger.sideload.info("Successfully received anisette data!") + self.finish(.success(response.anisetteData)) + + case .success: self.finish(.failure(ALTServerError(.unknownResponse))) } } } diff --git a/AltStore/Operations/FetchProvisioningProfilesOperation.swift b/AltStore/Operations/FetchProvisioningProfilesOperation.swift index a23eaeb5..daccd4aa 100644 --- a/AltStore/Operations/FetchProvisioningProfilesOperation.swift +++ b/AltStore/Operations/FetchProvisioningProfilesOperation.swift @@ -47,6 +47,8 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound(name: nil))) } + Logger.sideload.notice("Fetching provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)...") + self.progress.totalUnitCount = Int64(1 + app.appExtensions.count) self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in @@ -245,6 +247,8 @@ extension FetchProvisioningProfilesOperation if let appID = appIDs.first(where: { $0.bundleIdentifier.lowercased() == bundleIdentifier.lowercased() }) { + Logger.sideload.notice("Using existing App ID \(appID.bundleIdentifier, privacy: .public)") + completionHandler(.success(appID)) } else @@ -275,6 +279,9 @@ extension FetchProvisioningProfilesOperation do { let appID = try Result(appID, error).get() + + Logger.sideload.notice("Registered new App ID \(appID.bundleIdentifier, privacy: .public)") + completionHandler(.success(appID)) } catch ALTAppleAPIError.maximumAppIDLimitReached @@ -358,8 +365,15 @@ extension FetchProvisioningProfilesOperation let appID = appID.copy() as! ALTAppID appID.features = features - ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in - completionHandler(Result(appID, error)) + ALTAppleAPI.shared.update(appID, team: team, session: session) { (updatedAppID, error) in + let result = Result(updatedAppID, error) + switch result + { + case .success(let appID): Logger.sideload.notice("Updated features for App ID \(appID.bundleIdentifier, privacy: .public).") + case .failure(let error): Logger.sideload.error("Failed to update features for App ID \(appID.bundleIdentifier, privacy: .public). \(error.localizedDescription, privacy: .public)") + } + + completionHandler(result) } } else @@ -377,6 +391,7 @@ extension FetchProvisioningProfilesOperation } guard var applicationGroups = entitlements[.appGroups] as? [String], !applicationGroups.isEmpty else { + Logger.sideload.notice("App ID \(appID.bundleIdentifier, privacy: .public) has no app groups, skipping assignment.") // Assigning an App ID to an empty app group array fails, // so just do nothing if there are no app groups. return completionHandler(.success(appID)) @@ -414,7 +429,10 @@ extension FetchProvisioningProfilesOperation ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in switch Result(groups, error) { - case .failure(let error): finish(.failure(error)) + case .failure(let error): + Logger.sideload.error("Failed to fetch app groups for team \(team.identifier, privacy: .public). \(error.localizedDescription, privacy: .public)") + finish(.failure(error)) + case .success(let fetchedGroups): let dispatchGroup = DispatchGroup() @@ -439,8 +457,13 @@ extension FetchProvisioningProfilesOperation ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in switch Result(group, error) { - case .success(let group): groups.append(group) - case .failure(let error): errors.append(error) + case .success(let group): + Logger.sideload.notice("Created new App Group \(group.groupIdentifier, privacy: .public).") + groups.append(group) + + case .failure(let error): + Logger.sideload.notice("Failed to create new App Group \(adjustedGroupIdentifier, privacy: .public). \(error.localizedDescription, privacy: .public)") + errors.append(error) } dispatchGroup.leave() @@ -456,8 +479,17 @@ extension FetchProvisioningProfilesOperation else { ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in - let result = Result(success, error) - finish(result.map { _ in appID }) + let groupIDs = groups.map { $0.groupIdentifier } + switch Result(success, error) + { + case .success: + Logger.sideload.notice("Assigned App ID \(appID.bundleIdentifier, privacy: .public) to App Groups \(groupIDs.description, privacy: .public).") + finish(.success(appID)) + + case .failure(let error): + Logger.sideload.error("Failed to assign App ID \(appID.bundleIdentifier, privacy: .public) to App Groups \(groupIDs.description, privacy: .public). \(error.localizedDescription, privacy: .public)") + finish(.failure(error)) + } } } } @@ -484,6 +516,8 @@ extension FetchProvisioningProfilesOperation completionHandler(.success(profile)) case .success: + Logger.sideload.notice("Generating new free provisioning profile for App ID \(appID.bundleIdentifier, privacy: .public).") + // Fetch new provisioning profile ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in completionHandler(Result(profile, error)) diff --git a/AltStore/Operations/FindServerOperation.swift b/AltStore/Operations/FindServerOperation.swift index 78e2ac4e..35ff2a81 100644 --- a/AltStore/Operations/FindServerOperation.swift +++ b/AltStore/Operations/FindServerOperation.swift @@ -48,6 +48,8 @@ class FindServerOperation: ResultOperation return } + Logger.sideload.notice("Discovering AltServers...") + let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() let observer = Unmanaged.passUnretained(self).toOpaque() @@ -63,22 +65,30 @@ class FindServerOperation: ResultOperation DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { if let machServiceName = self.localServerMachServiceName { + Logger.sideload.notice("Found AltDaemon!") + // Prefer background daemon, if it exists and is running. let server = Server(connectionType: .local, machServiceName: machServiceName) self.finish(.success(server)) } else if self.isWiredServerConnectionAvailable { + Logger.sideload.notice("Found AltServer connected via USB!") + let server = Server(connectionType: .wired) self.finish(.success(server)) } else if let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred }) { + Logger.sideload.notice("Found preferred AltServer! \(server.localizedName ?? "nil", privacy: .public)") + // Preferred server. self.finish(.success(server)) } else if let server = ServerManager.shared.discoveredServers.first { + Logger.sideload.notice("Found AltServer! \(server.localizedName ?? "nil", privacy: .public)") + // Any available server. self.finish(.success(server)) } @@ -113,7 +123,7 @@ fileprivate extension FindServerOperation connection.connect { (result) in switch result { - case .failure(let error): print("Could not connect to AltDaemon XPC service \(machServiceName).", error) + case .failure(let error): Logger.sideload.notice("Could not connect to AltDaemon XPC service \(machServiceName, privacy: .public). \(error.localizedDescription, privacy: .public)") case .success: self.localServerMachServiceName = machServiceName } } diff --git a/AltStore/Operations/InstallAppOperation.swift b/AltStore/Operations/InstallAppOperation.swift index 032f193b..60938195 100644 --- a/AltStore/Operations/InstallAppOperation.swift +++ b/AltStore/Operations/InstallAppOperation.swift @@ -45,6 +45,8 @@ class InstallAppOperation: ResultOperation let connection = self.context.installationConnection else { return self.finish(.failure(OperationError.invalidParameters)) } + Logger.sideload.notice("Installing resigned app \(resignedApp.bundleIdentifier, privacy: .public)...") + @Managed var appVersion = self.context.appVersion let storeBuildVersion = $appVersion.buildVersion @@ -152,23 +154,32 @@ class InstallAppOperation: ResultOperation }) } - let request = BeginInstallationRequest(activeProfiles: activeProfiles, bundleIdentifier: installedApp.resignedBundleIdentifier) + let resignedBundleID = installedApp.resignedBundleIdentifier + + let request = BeginInstallationRequest(activeProfiles: activeProfiles, bundleIdentifier: resignedBundleID) connection.send(request) { (result) in switch result { - case .failure(let error): self.finish(.failure(error)) + case .failure(let error): + Logger.sideload.notice("Failed to send begin installation request for resigned app \(resignedBundleID, privacy: .public). \(error.localizedDescription, privacy: .public)") + self.finish(.failure(error)) + case .success: + Logger.sideload.notice("Sent begin installation request for resigned app \(resignedBundleID, privacy: .public).") self.receive(from: connection) { (result) in switch result { case .success: backgroundContext.perform { + Logger.sideload.notice("Successfully installed resigned app \(resignedBundleID, privacy: .public)!") + installedApp.refreshedDate = Date() self.finish(.success(installedApp)) } case .failure(let error): + Logger.sideload.notice("Failed to install resigned app \(resignedBundleID, privacy: .public). \(error.localizedDescription, privacy: .public)") self.finish(.failure(error)) } } @@ -192,7 +203,7 @@ class InstallAppOperation: ResultOperation } catch { - print("Failed to remove refreshed .ipa:", error) + Logger.sideload.error("Failed to remove refreshed .ipa: \(error.localizedDescription, privacy: .public)") } } @@ -208,11 +219,12 @@ private extension InstallAppOperation do { let response = try result.get() - print(response) - + switch response { case .installationProgress(let response): + Logger.sideload.debug("Installing \(self.context.resignedApp?.bundleIdentifier ?? self.context.bundleIdentifier, privacy: .public)... \((response.progress * 100).rounded())%") + if response.progress == 1.0 { self.progress.completedUnitCount = self.progress.totalUnitCount @@ -249,7 +261,7 @@ private extension InstallAppOperation } catch { - print("Failed to remove temporary directory.", error) + Logger.sideload.error("Failed to remove temporary directory: \(error.localizedDescription, privacy: .public)") } } } diff --git a/AltStore/Operations/Patch App/PatchAppOperation.swift b/AltStore/Operations/Patch App/PatchAppOperation.swift index 1c6c4259..951f5924 100644 --- a/AltStore/Operations/Patch App/PatchAppOperation.swift +++ b/AltStore/Operations/Patch App/PatchAppOperation.swift @@ -198,7 +198,7 @@ private extension PatchAppOperation } } - print("Downloaded OTA archive.") + Logger.fugu14.notice("Downloaded iOS OTA archive.") return archiveURL #endif @@ -228,7 +228,7 @@ private extension PatchAppOperation return .ok } - print("Extracted Spotlight from OTA archive.") + Logger.fugu14.notice("Extracted Spotlight from OTA archive.") return spotlightFileURL #endif @@ -250,7 +250,7 @@ private extension PatchAppOperation let appBinaryURL = temporaryAppURL.appendingPathComponent(appName, isDirectory: false) try self.appPatcher.patchAppBinary(at: appBinaryURL, withBinaryAt: patchFileURL) - print("Patched \(app.name).") + Logger.fugu14.notice("Patched \(app.name, privacy: .public)!") return temporaryAppURL } .mapError { ($0 as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not patch %@ placeholder.", comment: ""), app.name)) } diff --git a/AltStore/Operations/Patch App/PatchViewController.swift b/AltStore/Operations/Patch App/PatchViewController.swift index 37b1a6c8..540397a4 100644 --- a/AltStore/Operations/Patch App/PatchViewController.swift +++ b/AltStore/Operations/Patch App/PatchViewController.swift @@ -80,7 +80,7 @@ class PatchViewController: UIViewController } catch { - print("Failed to create temporary directory:", error) + Logger.fugu14.error("Failed to create temporary directory \(self.temporaryDirectory.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)") } self.update() @@ -196,7 +196,7 @@ private extension PatchViewController } catch { - print("Failed to remove temporary directory:", error) + Logger.fugu14.error("Failed to remove temporary directory \(self.temporaryDirectory.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)") } if let observation = self.didEnterBackgroundObservation @@ -312,7 +312,7 @@ private extension PatchViewController } catch { - print("Error unzipping app bundle:", error) + Logger.fugu14.error("Error unzipping app bundle: \(error.localizedDescription, privacy: .public)") unzippingError = error } } diff --git a/AltStore/Operations/RefreshAppOperation.swift b/AltStore/Operations/RefreshAppOperation.swift index 00306097..8d0d35a5 100644 --- a/AltStore/Operations/RefreshAppOperation.swift +++ b/AltStore/Operations/RefreshAppOperation.swift @@ -44,13 +44,15 @@ class RefreshAppOperation: ResultOperation guard let app = self.context.app else { throw OperationError.appNotFound(name: nil) } guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID } + Logger.sideload.notice("Refreshing provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)...") + ServerManager.shared.connect(to: server) { (result) in switch result { case .failure(let error): self.finish(.failure(error)) case .success(let connection): DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in - print("Sending refresh app request...") + Logger.sideload.debug("Sending refresh app request...") var activeProfiles: Set? if UserDefaults.standard.activeAppsLimit != nil @@ -65,20 +67,24 @@ class RefreshAppOperation: ResultOperation let request = InstallProvisioningProfilesRequest(udid: udid, provisioningProfiles: Set(profiles.values), activeProfiles: activeProfiles) connection.send(request) { (result) in - print("Sent refresh app request!") + Logger.sideload.debug("Sent refresh app request!") switch result { case .failure(let error): self.finish(.failure(error)) case .success: - print("Waiting for refresh app response...") + Logger.sideload.debug("Waiting for refresh app response...") + connection.receiveResponse() { (result) in - print("Receiving refresh app response:", result) - switch result { - case .failure(let error): self.finish(.failure(error)) - case .success(.error(let response)): self.finish(.failure(response.error)) + case .failure(let error): + Logger.sideload.error("Failed to receive refresh app response. \(error.localizedDescription, privacy: .public)") + self.finish(.failure(error)) + + case .success(.error(let response)): + Logger.sideload.debug("Failed to refresh app \(self.context.bundleIdentifier, privacy: .public). \(response.error.localizedDescription, privacy: .public)") + self.finish(.failure(response.error)) case .success(.installProvisioningProfiles): self.managedObjectContext.perform { @@ -100,10 +106,13 @@ class RefreshAppOperation: ResultOperation installedExtension.update(provisioningProfile: provisioningProfile) } + Logger.sideload.notice("Refreshed provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)") self.finish(.success(installedApp)) } - case .success: self.finish(.failure(ALTServerError(.unknownRequest))) + case .success: + Logger.sideload.notice("Received unknown refresh app response for app \(self.context.bundleIdentifier, privacy: .public)") + self.finish(.failure(ALTServerError(.unknownResponse))) } } } diff --git a/AltStore/Operations/RemoveAppBackupOperation.swift b/AltStore/Operations/RemoveAppBackupOperation.swift index 6e40b72d..60424a04 100644 --- a/AltStore/Operations/RemoveAppBackupOperation.swift +++ b/AltStore/Operations/RemoveAppBackupOperation.swift @@ -8,6 +8,8 @@ import Foundation +import AltStoreCore + @objc(RemoveAppBackupOperation) class RemoveAppBackupOperation: ResultOperation { @@ -36,6 +38,9 @@ class RemoveAppBackupOperation: ResultOperation } guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) } + + Logger.sideload.notice("Removing backup for app \(self.context.bundleIdentifier, privacy: .public)...") + installedApp.managedObjectContext?.perform { guard let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return self.finish(.failure(OperationError.missingAppGroup)) } @@ -61,14 +66,14 @@ class RemoveAppBackupOperation: ResultOperation #else - print("Failed to remove app backup directory:", error) + Logger.sideload.error("Failed to remove app backup directory \(backupDirectoryURL.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)") self.finish(.failure(error)) #endif } catch { - print("Failed to remove app backup directory:", error) + Logger.sideload.error("Failed to remove app backup directory \(backupDirectoryURL.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)") self.finish(.failure(error)) } } diff --git a/AltStore/Operations/RemoveAppOperation.swift b/AltStore/Operations/RemoveAppOperation.swift index 4ba53f12..5b6e8dd4 100644 --- a/AltStore/Operations/RemoveAppOperation.swift +++ b/AltStore/Operations/RemoveAppOperation.swift @@ -35,6 +35,8 @@ class RemoveAppOperation: ResultOperation guard let server = self.context.server, let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) } guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return self.finish(.failure(OperationError.unknownUDID)) } + Logger.sideload.notice("Removing app \(self.context.bundleIdentifier, privacy: .public)...") + installedApp.managedObjectContext?.perform { let resignedBundleIdentifier = installedApp.resignedBundleIdentifier @@ -43,19 +45,24 @@ class RemoveAppOperation: ResultOperation { case .failure(let error): self.finish(.failure(error)) case .success(let connection): - print("Sending remove app request...") + Logger.sideload.debug("Sending remove app request...") let request = RemoveAppRequest(udid: udid, bundleIdentifier: resignedBundleIdentifier) connection.send(request) { (result) in - print("Sent remove app request!") - switch result { - case .failure(let error): self.finish(.failure(error)) + case .failure(let error): + Logger.sideload.error("Failed to send remove app request. \(error.localizedDescription, privacy: .public)") + self.finish(.failure(error)) + case .success: - print("Waiting for remove app response...") + Logger.sideload.debug("Waiting for remove app response...") connection.receiveResponse() { (result) in - print("Receiving remove app response:", result) + switch result + { + case .failure(let error): Logger.sideload.error("Failed to remove app. \(error.localizedDescription, privacy: .public)") + case .success: Logger.sideload.info("Successfully removed app \(self.context.bundleIdentifier, privacy: .public)!") + } switch result { diff --git a/AltStore/Operations/ResignAppOperation.swift b/AltStore/Operations/ResignAppOperation.swift index 563f84e8..926cc3e5 100644 --- a/AltStore/Operations/ResignAppOperation.swift +++ b/AltStore/Operations/ResignAppOperation.swift @@ -43,6 +43,8 @@ class ResignAppOperation: ResultOperation let certificate = self.context.certificate else { return self.finish(.failure(OperationError.invalidParameters)) } + Logger.sideload.notice("Resigning app \(self.context.bundleIdentifier, privacy: .public)...") + // Prepare app bundle let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2) self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3) @@ -50,8 +52,6 @@ class ResignAppOperation: ResultOperation let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in guard let appBundleURL = self.process(result) else { return } - print("Resigning App:", self.context.bundleIdentifier) - // Resign app bundle let resignProgress = self.resignAppBundle(at: appBundleURL, team: team, certificate: certificate, profiles: Array(profiles.values)) { (result) in guard let resignedURL = self.process(result) else { return } @@ -64,6 +64,9 @@ class ResignAppOperation: ResultOperation // Use appBundleURL since we need an app bundle, not .ipa. guard let resignedApplication = ALTApplication(fileURL: appBundleURL) else { throw OperationError.invalidApp } + + Logger.sideload.notice("Resigned app \(self.context.bundleIdentifier, privacy: .public) to \(resignedApplication.bundleIdentifier, privacy: .public).") + self.finish(.success(resignedApplication)) } catch diff --git a/AltStore/Operations/SendAppOperation.swift b/AltStore/Operations/SendAppOperation.swift index 2b363e48..2fd61569 100644 --- a/AltStore/Operations/SendAppOperation.swift +++ b/AltStore/Operations/SendAppOperation.swift @@ -41,6 +41,8 @@ class SendAppOperation: ResultOperation guard let resignedApp = self.context.resignedApp, let server = self.context.server else { return self.finish(.failure(OperationError.invalidParameters)) } + Logger.sideload.notice("Sending app \(self.context.bundleIdentifier, privacy: .public) to AltServer \(server.localizedName ?? "nil", privacy: .public)...") + // self.context.resignedApp.fileURL points to the app bundle, but we want the .ipa. let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL) let fileURL = InstalledApp.refreshedIPAURL(for: app) @@ -99,17 +101,17 @@ private extension SendAppOperation } else { - print("Sending app data (\(appData.count) bytes)...") + Logger.sideload.debug("Sending app data (\(appData.count) bytes)...") connection.send(appData, prependSize: false) { (result) in switch result { case .failure(let error): - print("Failed to send app data (\(appData.count) bytes)") + Logger.sideload.error("Failed to send app to AltServer \(connection.server.localizedName ?? "nil", privacy: .public). \(error.localizedDescription, privacy: .public)") completionHandler(.failure(error)) case .success: - print("Successfully sent app data (\(appData.count) bytes)") + Logger.sideload.notice("Finished sending app to AltServer \(connection.server.localizedName ?? "nil", privacy: .public)!") completionHandler(.success(())) } } diff --git a/AltStore/Operations/UpdatePatronsOperation.swift b/AltStore/Operations/UpdatePatronsOperation.swift index d2594572..35100970 100644 --- a/AltStore/Operations/UpdatePatronsOperation.swift +++ b/AltStore/Operations/UpdatePatronsOperation.swift @@ -87,10 +87,11 @@ class UpdatePatronsOperation: ResultOperation self.finish(.success(())) - print("Updated Friend Zone Patrons!") + Logger.main.notice("Updated Friend Zone Patrons! Refresh ID: \(response.refreshID, privacy: .public)") } catch { + Logger.main.error("Failed to update Friend Zone Patrons. \(error.localizedDescription, privacy: .public)") self.finish(.failure(error)) } } diff --git a/AltStore/Operations/VerifyAppOperation.swift b/AltStore/Operations/VerifyAppOperation.swift index d2cede60..360f7392 100644 --- a/AltStore/Operations/VerifyAppOperation.swift +++ b/AltStore/Operations/VerifyAppOperation.swift @@ -63,6 +63,8 @@ class VerifyAppOperation: ResultOperation guard let app = self.context.app else { throw OperationError.invalidParameters } + Logger.sideload.notice("Verifying app \(self.context.bundleIdentifier, privacy: .public)...") + guard app.bundleIdentifier == self.context.bundleIdentifier else { throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app) } @@ -149,9 +151,9 @@ private extension VerifyAppOperation let data = try Data(contentsOf: ipaURL) let sha256Hash = SHA256.hash(data: data) let hashString = sha256Hash.compactMap { String(format: "%02x", $0) }.joined() - - print("[ALTLog] Comparing app hash (\(hashString)) against expected hash (\(expectedHash))...") - + + Logger.sideload.debug("Comparing app hash (\(hashString, privacy: .public)) against expected hash (\(expectedHash, privacy: .public))...") + guard hashString == expectedHash else { throw VerificationError.mismatchedHash(hashString, expectedHash: expectedHash, app: app) } } diff --git a/AltStore/Server/Server.swift b/AltStore/Server/Server.swift index f2a55633..ec608775 100644 --- a/AltStore/Server/Server.swift +++ b/AltStore/Server/Server.swift @@ -31,6 +31,10 @@ struct Server: Equatable extension Server { + var localizedName: String? { + return self.service?.name ?? self.identifier + } + // Defined in extension so we can still use the automatically synthesized initializer. init?(service: NetService, txtData: Data) { diff --git a/AltStore/Server/ServerManager.swift b/AltStore/Server/ServerManager.swift index 04eef659..9cd20dc2 100644 --- a/AltStore/Server/ServerManager.swift +++ b/AltStore/Server/ServerManager.swift @@ -80,7 +80,7 @@ extension ServerManager case .wired: guard let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore else { return finish(.failure(ALTServerError(.connectionFailed))) } - print("Waiting for incoming connection...") + Logger.sideload.debug("Waiting for incoming connection...") let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() @@ -104,7 +104,7 @@ extension ServerManager case .wireless: guard let service = server.service else { return finish(.failure(ALTServerError(.connectionFailed))) } - print("Connecting to service:", service) + Logger.sideload.debug("Connecting to AltServer: \(service.name, privacy: .public)") let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: .tcp) self.connectToRemoteServer(server, connection: connection, completion: finish(_:)) @@ -166,17 +166,32 @@ private extension ServerManager func connectToRemoteServer(_ server: Server, connection: NWConnection, completion: @escaping (Result) -> Void) { + let serverName: String + if let localizedName = server.localizedName + { + serverName = String(format: NSLocalizedString("remote AltServer %@", comment: ""), localizedName) + } + else if server.connectionType == .wired + { + serverName = NSLocalizedString("wired AltServer", comment: "") + } + else + { + serverName = NSLocalizedString("AltServer", comment: "") + } + connection.stateUpdateHandler = { [unowned connection] (state) in switch state { case .failed(let error): - print("Failed to connect to service \(server.service?.name ?? "").", error) + Logger.sideload.error("Failed to connect to \(serverName, privacy: .public). \(error.localizedDescription, privacy: .public)") completion(.failure(OperationError.connectionFailed)) case .cancelled: completion(.failure(OperationError.cancelled)) case .ready: + Logger.sideload.notice("Connected to \(serverName, privacy: .public)!") let connection = NetworkConnection(connection) completion(.success(connection)) @@ -201,10 +216,12 @@ private extension ServerManager switch result { case .failure(let error): - print("Could not connect to AltDaemon XPC service \(machServiceName).", error) + Logger.sideload.error("Could not connect to AltDaemon XPC service \(machServiceName, privacy: .public). \(error.localizedDescription, privacy: .public)") completion(.failure(error)) - case .success: completion(.success(connection)) + case .success: + Logger.sideload.notice("Connected to AltDaemon XPC service \(machServiceName, privacy: .public)!") + completion(.success(connection)) } } } @@ -214,17 +231,17 @@ extension ServerManager: NetServiceBrowserDelegate { func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) { - print("Discovering servers...") + Logger.main.notice("Discovering AltServers...") } func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) { - print("Stopped discovering servers.") + Logger.main.notice("Stopped discovering AltServers.") } func netServiceBrowser(_ browser: NetServiceBrowser, didNotSearch errorDict: [String : NSNumber]) { - print("Failed to discovering servers.", errorDict) + Logger.main.error("Failed to discover AltServers. \(errorDict, privacy: .public)") } func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) @@ -263,12 +280,12 @@ extension ServerManager: NetServiceDelegate func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) { - print("Error resolving net service \(sender).", errorDict) + Logger.main.error("Failed to resolve Bonjour service \(sender.name, privacy: .public). \(errorDict, privacy: .public)") } func netService(_ sender: NetService, didUpdateTXTRecord data: Data) { let txtDict = NetService.dictionary(fromTXTRecord: data) - print("Service \(sender) updated TXT Record:", txtDict) + Logger.main.debug("Bonjour service \(sender.name, privacy: .public) updated TXT Record: \(txtDict, privacy: .public)") } } diff --git a/AltStore/Settings/Error Log/ErrorLogViewController.swift b/AltStore/Settings/Error Log/ErrorLogViewController.swift index a350f3b4..d3fe949e 100644 --- a/AltStore/Settings/Error Log/ErrorLogViewController.swift +++ b/AltStore/Settings/Error Log/ErrorLogViewController.swift @@ -33,6 +33,9 @@ class ErrorLogViewController: UITableViewController return dateFormatter }() + @IBOutlet private var exportLogButton: UIBarButtonItem! + @IBOutlet private var clearLogButton: UIBarButtonItem! + override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } @@ -43,6 +46,14 @@ class ErrorLogViewController: UITableViewController self.tableView.dataSource = self.dataSource self.tableView.prefetchDataSource = self.dataSource + + self.exportLogButton.activityIndicatorView.color = .white + + if #unavailable(iOS 15) + { + // Assign just clearLogButton to hide export button. + self.navigationItem.rightBarButtonItems = [self.clearLogButton] + } } override func prepare(for segue: UIStoryboardSegue, sender: Any?) @@ -260,6 +271,77 @@ private extension ErrorLogViewController { self.performSegue(withIdentifier: "showErrorDetails", sender: loggedError) } + + @available(iOS 15, *) + @IBAction func exportDetailedLog(_ sender: UIBarButtonItem) + { + self.exportLogButton.isIndicatingActivity = true + + Task.detached(priority: .userInitiated) { + do + { + let store = try OSLogStore(scope: .currentProcessIdentifier) + + // All logs since the app launched. + let position = store.position(timeIntervalSinceLatestBoot: 0) + + let entries = try store.getEntries(at: position) + .compactMap { $0 as? OSLogEntryLog } + .filter { $0.subsystem.contains(Logger.altstoreSubsystem) } + .map { "[\($0.date.formatted())] [\($0.category)] [\($0.level.localizedName)] \($0.composedMessage)" } + + let outputText = entries.joined(separator: "\n") + + let outputDirectory = FileManager.default.uniqueTemporaryURL() + try FileManager.default.createDirectory(at: outputDirectory, withIntermediateDirectories: true) + + defer { + do + { + try FileManager.default.removeItem(at: outputDirectory) + } + catch + { + Logger.main.error("Failed to remove temporary log directory \(outputDirectory.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)") + } + } + + let outputURL = outputDirectory.appendingPathComponent("altlog.txt") + try outputText.write(to: outputURL, atomically: true, encoding: .utf8) + + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + Task { @MainActor in + let activityViewController = UIActivityViewController(activityItems: [outputURL], applicationActivities: nil) + activityViewController.completionWithItemsHandler = { (activityType, completed, _, error) in + if let error + { + continuation.resume(throwing: error) + } + else + { + continuation.resume() + } + } + self.present(activityViewController, animated: true) + } + } + } + catch + { + Logger.main.error("Failed to export OSLog entries. \(error.localizedDescription, privacy: .public)") + + await MainActor.run { + let alertController = UIAlertController(title: NSLocalizedString("Unable to Export Detailed Log", comment: ""), message: error.localizedDescription, preferredStyle: .alert) + alertController.addAction(.ok) + self.present(alertController, animated: true) + } + } + + await MainActor.run { + self.exportLogButton.isIndicatingActivity = false + } + } + } } extension ErrorLogViewController diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard index f430b8e9..b7f22684 100644 --- a/AltStore/Settings/Settings.storyboard +++ b/AltStore/Settings/Settings.storyboard @@ -1,9 +1,9 @@ - + - + @@ -1095,13 +1095,22 @@ Settings by i cons from the Noun Project - - - - - + + + + + + + + + + + + + + diff --git a/AltStoreCore/Extensions/Logger+AltStore.swift b/AltStoreCore/Extensions/Logger+AltStore.swift new file mode 100644 index 00000000..4e02169a --- /dev/null +++ b/AltStoreCore/Extensions/Logger+AltStore.swift @@ -0,0 +1,37 @@ +// +// Logger+AltStore.swift +// AltStoreCore +// +// Created by Riley Testut on 10/2/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +@_exported import OSLog + +public extension Logger +{ + static let altstoreSubsystem = Bundle.main.bundleIdentifier! + + static let main = Logger(subsystem: altstoreSubsystem, category: "Main") + static let sideload = Logger(subsystem: altstoreSubsystem, category: "Sideload") + static let altjit = Logger(subsystem: altstoreSubsystem, category: "AltJIT") + + static let fugu14 = Logger(subsystem: altstoreSubsystem, category: "Fugu14") +} + +@available(iOS 15, *) +public extension OSLogEntryLog.Level +{ + var localizedName: String { + switch self + { + case .undefined: return NSLocalizedString("Undefined", comment: "") + case .debug: return NSLocalizedString("Debug", comment: "") + case .info: return NSLocalizedString("Info", comment: "") + case .notice: return NSLocalizedString("Notice", comment: "") + case .error: return NSLocalizedString("Error", comment: "") + case .fault: return NSLocalizedString("Fault", comment: "") + @unknown default: return NSLocalizedString("Unknown", comment: "") + } + } +}