mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 23:03:27 +01:00
Merge branch 'logging'
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
D51AD27C29356B7B00967AAA /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = "<group>"; };
|
||||
D51AD27D29356B7B00967AAA /* ALTWrappedError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWrappedError.m; sourceTree = "<group>"; };
|
||||
D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+AltStore.swift"; sourceTree = "<group>"; };
|
||||
D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = "<group>"; };
|
||||
D52E988928D002D30032BE6B /* AltStore 11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 11.xcdatamodel"; sourceTree = "<group>"; };
|
||||
D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailCollectionViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -1580,6 +1582,7 @@
|
||||
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
||||
D5F48B4729CCF21B002B52A4 /* AltStore+Async.swift */,
|
||||
D5893F7E2A14183200E767CD /* NSManagedObjectContext+Conveniences.swift */,
|
||||
D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -103,7 +103,7 @@ class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledA
|
||||
}
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||
Logger.sideload.notice("Refreshing apps in background: \(self.installedApps.map(\.bundleIdentifier), privacy: .public)")
|
||||
|
||||
self.startListeningForRunningApps()
|
||||
|
||||
@@ -114,7 +114,10 @@ class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledA
|
||||
self.managedObjectContext.perform {
|
||||
|
||||
let filteredApps = self.installedApps.filter { !self.runningApplications.contains($0.bundleIdentifier) }
|
||||
print("Filtered Apps to Refresh:", filteredApps.map { $0.bundleIdentifier })
|
||||
if !self.runningApplications.isEmpty
|
||||
{
|
||||
Logger.sideload.notice("Skipping refreshing running apps: \(self.runningApplications, privacy: .public)")
|
||||
}
|
||||
|
||||
let group = AppManager.shared.refresh(filteredApps, presentingViewController: nil)
|
||||
group.beginInstallationHandler = { (installedApp) in
|
||||
@@ -225,7 +228,7 @@ private extension BackgroundRefreshAppsOperation
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to refresh apps in background.", error)
|
||||
Logger.sideload.error("Failed to refresh apps in background. \(error.localizedDescription, privacy: .public)")
|
||||
|
||||
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||
content.body = error.localizedDescription
|
||||
@@ -269,7 +272,7 @@ private extension BackgroundRefreshAppsOperation
|
||||
_ = RefreshAttempt(identifier: self.refreshIdentifier, result: result, context: context)
|
||||
|
||||
do { try context.save() }
|
||||
catch { print("Failed to save refresh attempt.", error) }
|
||||
catch { Logger.sideload.error("Failed to save refresh attempt. \(error.localizedDescription, privacy: .public)") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,8 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
// 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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,14 @@ class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
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<InstalledApp>
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
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<ALTApplication>
|
||||
}
|
||||
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
|
||||
|
||||
@@ -43,6 +43,8 @@ class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
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<Context: EnableJITContext>: ResultOperation<Void>
|
||||
{
|
||||
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<Context: EnableJITContext>: ResultOperation<Void>
|
||||
let result = Future<Result<Void, Error>, 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))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,32 +34,42 @@ class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
||||
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -48,6 +48,8 @@ class FindServerOperation: ResultOperation<Server>
|
||||
return
|
||||
}
|
||||
|
||||
Logger.sideload.notice("Discovering AltServers...")
|
||||
|
||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||
|
||||
@@ -63,22 +65,30 @@ class FindServerOperation: ResultOperation<Server>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
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<InstalledApp>
|
||||
})
|
||||
}
|
||||
|
||||
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<InstalledApp>
|
||||
}
|
||||
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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,15 @@ class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
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<String>?
|
||||
if UserDefaults.standard.activeAppsLimit != nil
|
||||
@@ -65,20 +67,24 @@ class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
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<InstalledApp>
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
@objc(RemoveAppBackupOperation)
|
||||
class RemoveAppBackupOperation: ResultOperation<Void>
|
||||
{
|
||||
@@ -36,6 +38,9 @@ class RemoveAppBackupOperation: ResultOperation<Void>
|
||||
}
|
||||
|
||||
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<Void>
|
||||
|
||||
#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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||
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<InstalledApp>
|
||||
{
|
||||
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
|
||||
{
|
||||
|
||||
@@ -43,6 +43,8 @@ class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
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<ALTApplication>
|
||||
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<ALTApplication>
|
||||
|
||||
// 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
|
||||
|
||||
@@ -41,6 +41,8 @@ class SendAppOperation: ResultOperation<ServerConnection>
|
||||
|
||||
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(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,10 +87,11 @@ class UpdatePatronsOperation: ResultOperation<Void>
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ class VerifyAppOperation: ResultOperation<Void>
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<Connection, Error>) -> 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)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Void, Never>.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<Void, Error>) in
|
||||
Task<Void, Never> { @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
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
@@ -1095,13 +1095,22 @@ Settings by i cons from the Noun Project</string>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Error Log" largeTitleDisplayMode="never" id="a1p-3W-bSi">
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="trash" id="BnQ-Eh-1gC">
|
||||
<connections>
|
||||
<action selector="clearLoggedErrors:" destination="g8a-Rf-zWa" id="faq-89-H5j"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<rightBarButtonItems>
|
||||
<barButtonItem systemItem="trash" id="BnQ-Eh-1gC">
|
||||
<connections>
|
||||
<action selector="clearLoggedErrors:" destination="g8a-Rf-zWa" id="faq-89-H5j"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem systemItem="action" id="BNj-HE-KHr">
|
||||
<connections>
|
||||
<action selector="exportDetailedLog:" destination="g8a-Rf-zWa" id="Kbw-Q5-9WO"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</rightBarButtonItems>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="clearLogButton" destination="BnQ-Eh-1gC" id="MHl-Kh-ick"/>
|
||||
<outlet property="exportLogButton" destination="BNj-HE-KHr" id="w9g-yA-0Yx"/>
|
||||
<segue destination="7gm-d1-zWK" kind="presentation" identifier="showErrorDetails" id="9vz-y6-evp"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
|
||||
37
AltStoreCore/Extensions/Logger+AltStore.swift
Normal file
37
AltStoreCore/Extensions/Logger+AltStore.swift
Normal file
@@ -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: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user