mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-18 03:03:31 +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, ); }; };
|
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 */; };
|
D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; };
|
||||||
D51AD28029356B8000967AAA /* 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 */; };
|
D52C08EE28AEC37A006C4AE5 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */; };
|
||||||
D52DD35E2AAA89A600A7F2B6 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = D52DD35D2AAA89A600A7F2B6 /* AltSign-Dynamic */; };
|
D52DD35E2AAA89A600A7F2B6 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = D52DD35D2AAA89A600A7F2B6 /* AltSign-Dynamic */; };
|
||||||
D52EF2BE2A0594550096C377 /* AppDetailCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
@@ -1580,6 +1582,7 @@
|
|||||||
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
||||||
D5F48B4729CCF21B002B52A4 /* AltStore+Async.swift */,
|
D5F48B4729CCF21B002B52A4 /* AltStore+Async.swift */,
|
||||||
D5893F7E2A14183200E767CD /* NSManagedObjectContext+Conveniences.swift */,
|
D5893F7E2A14183200E767CD /* NSManagedObjectContext+Conveniences.swift */,
|
||||||
|
D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2999,6 +3002,7 @@
|
|||||||
BFAECC572501B0A400528F27 /* ConnectionManager.swift in Sources */,
|
BFAECC572501B0A400528F27 /* ConnectionManager.swift in Sources */,
|
||||||
BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */,
|
BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */,
|
||||||
D519AD46292D665B004B12F9 /* Managed.swift in Sources */,
|
D519AD46292D665B004B12F9 /* Managed.swift in Sources */,
|
||||||
|
D52A2F972ACB40F700BDF8E3 /* Logger+AltStore.swift in Sources */,
|
||||||
BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
||||||
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */,
|
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */,
|
||||||
D5708417292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift in Sources */,
|
D5708417292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift in Sources */,
|
||||||
|
|||||||
@@ -202,7 +202,11 @@ class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppl
|
|||||||
{
|
{
|
||||||
guard !self.isFinished else { return }
|
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()
|
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
context.perform {
|
context.perform {
|
||||||
@@ -347,6 +351,8 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
|
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
|
self.authenticate(appleID: appleID, password: password) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledA
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.managedObjectContext.perform {
|
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()
|
self.startListeningForRunningApps()
|
||||||
|
|
||||||
@@ -114,7 +114,10 @@ class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledA
|
|||||||
self.managedObjectContext.perform {
|
self.managedObjectContext.perform {
|
||||||
|
|
||||||
let filteredApps = self.installedApps.filter { !self.runningApplications.contains($0.bundleIdentifier) }
|
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)
|
let group = AppManager.shared.refresh(filteredApps, presentingViewController: nil)
|
||||||
group.beginInstallationHandler = { (installedApp) in
|
group.beginInstallationHandler = { (installedApp) in
|
||||||
@@ -225,7 +228,7 @@ private extension BackgroundRefreshAppsOperation
|
|||||||
}
|
}
|
||||||
catch
|
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.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||||
content.body = error.localizedDescription
|
content.body = error.localizedDescription
|
||||||
@@ -269,7 +272,7 @@ private extension BackgroundRefreshAppsOperation
|
|||||||
_ = RefreshAttempt(identifier: self.refreshIdentifier, result: result, context: context)
|
_ = RefreshAttempt(identifier: self.refreshIdentifier, result: result, context: context)
|
||||||
|
|
||||||
do { try context.save() }
|
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.
|
// Failed too quickly for human to respond to alert, possibly still finalizing installation.
|
||||||
// Try again in a couple seconds.
|
// 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) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
||||||
UIApplication.shared.open(openURL, options: [:]) { (success) in
|
UIApplication.shared.open(openURL, options: [:]) { (success) in
|
||||||
if success
|
if success
|
||||||
|
|||||||
@@ -108,12 +108,12 @@ private extension ClearAppCacheOperation
|
|||||||
{
|
{
|
||||||
do
|
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)
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
}
|
}
|
||||||
catch
|
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)
|
errors.append(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,13 +171,13 @@ private extension ClearAppCacheOperation
|
|||||||
|
|
||||||
if isDirectory && !installedAppBundleIDs.contains(bundleID) && !AppManager.shared.isActivelyManagingApp(withBundleID: bundleID)
|
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)
|
try FileManager.default.removeItem(at: backupDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
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)
|
errors.append(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ private extension ClearAppCacheOperation
|
|||||||
}
|
}
|
||||||
catch
|
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))
|
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 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)) }
|
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
|
ServerManager.shared.connect(to: server) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error): self.finish(.failure(error))
|
||||||
case .success(let connection):
|
case .success(let connection):
|
||||||
print("Sending deactivate app request...")
|
Logger.sideload.notice("Sending deactivate app request...")
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
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))
|
let request = RemoveProvisioningProfilesRequest(udid: udid, bundleIdentifiers: Set(allIdentifiers))
|
||||||
connection.send(request) { (result) in
|
connection.send(request) { (result) in
|
||||||
print("Sent deactive app request!")
|
|
||||||
|
|
||||||
switch result
|
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:
|
case .success:
|
||||||
print("Waiting for deactivate app response...")
|
Logger.sideload.debug("Waiting for deactivate app response...")
|
||||||
connection.receiveResponse() { (result) in
|
connection.receiveResponse() { (result) in
|
||||||
print("Receiving deactivate app response:", result)
|
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error):
|
||||||
case .success(.error(let response)): self.finish(.failure(response.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):
|
case .success(.removeProvisioningProfiles):
|
||||||
|
Logger.sideload.notice("Successfully deactivated app \(self.app.bundleIdentifier, privacy: .public)!")
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class DownloadAppOperation: ResultOperation<ALTApplication>
|
|||||||
return
|
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.
|
// 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)
|
self.localizedFailure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), self.appName)
|
||||||
@@ -108,7 +108,7 @@ class DownloadAppOperation: ResultOperation<ALTApplication>
|
|||||||
}
|
}
|
||||||
catch
|
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)
|
super.finish(result)
|
||||||
@@ -172,6 +172,9 @@ private extension DownloadAppOperation
|
|||||||
try FileManager.default.copyItem(at: application.fileURL, to: self.destinationURL, shouldReplace: true)
|
try FileManager.default.copyItem(at: application.fileURL, to: self.destinationURL, shouldReplace: true)
|
||||||
|
|
||||||
guard let copiedApplication = ALTApplication(fileURL: self.destinationURL) else { throw OperationError.invalidApp }
|
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.finish(.success(copiedApplication))
|
||||||
|
|
||||||
self.progress.completedUnitCount += 1
|
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 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)) }
|
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 {
|
installedApp.managedObjectContext?.perform {
|
||||||
guard let bundle = Bundle(url: installedApp.fileURL),
|
guard let bundle = Bundle(url: installedApp.fileURL),
|
||||||
let processName = bundle.executableURL?.lastPathComponent
|
let processName = bundle.executableURL?.lastPathComponent
|
||||||
@@ -56,7 +58,7 @@ class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
|||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error): self.finish(.failure(error))
|
||||||
case .success(let connection):
|
case .success(let connection):
|
||||||
print("Sending enable JIT request...")
|
Logger.altjit.debug("Sending enable JIT request...")
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
@@ -69,22 +71,31 @@ class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
|||||||
let result = Future<Result<Void, Error>, Never> { promise in
|
let result = Future<Result<Void, Error>, Never> { promise in
|
||||||
let request = EnableUnsignedCodeExecutionRequest(udid: udid, processName: processName)
|
let request = EnableUnsignedCodeExecutionRequest(udid: udid, processName: processName)
|
||||||
connection.send(request) { result in
|
connection.send(request) { result in
|
||||||
print("Sent enable JIT request!")
|
Logger.altjit.debug("Sent enable JIT request!")
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): promise(.success(.failure(error)))
|
case .failure(let error): promise(.success(.failure(error)))
|
||||||
case .success:
|
case .success:
|
||||||
print("Waiting for enable JIT response...")
|
Logger.altjit.debug("Waiting for enable JIT response...")
|
||||||
connection.receiveResponse() { result in
|
connection.receiveResponse() { result in
|
||||||
print("Received enable JIT response:", result)
|
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): promise(.success(.failure(error)))
|
case .failure(let error):
|
||||||
case .success(.error(let response)): promise(.success(.failure(response.error)))
|
Logger.altjit.error("Failed to receive enable JIT response. \(error.localizedDescription, privacy: .public)")
|
||||||
case .success(.enableUnsignedCodeExecution): promise(.success(.success(())))
|
promise(.success(.failure(error)))
|
||||||
case .success: promise(.success(.failure(ALTServerError(.unknownResponse))))
|
|
||||||
|
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)) }
|
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
|
ServerManager.shared.connect(to: server) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.finish(.failure(error))
|
self.finish(.failure(error))
|
||||||
case .success(let connection):
|
case .success(let connection):
|
||||||
print("Sending anisette data request...")
|
Logger.sideload.debug("Sending anisette data request...")
|
||||||
|
|
||||||
let request = AnisetteDataRequest()
|
let request = AnisetteDataRequest()
|
||||||
connection.send(request) { (result) in
|
connection.send(request) { (result) in
|
||||||
print("Sent anisette data request!")
|
|
||||||
|
|
||||||
switch result
|
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:
|
case .success:
|
||||||
print("Waiting for anisette data...")
|
Logger.sideload.debug("Waiting for anisette data...")
|
||||||
connection.receiveResponse() { (result) in
|
connection.receiveResponse() { (result) in
|
||||||
print("Receiving anisette data:", result.error?.localizedDescription ?? "success")
|
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error):
|
||||||
case .success(.error(let response)): self.finish(.failure(response.error))
|
Logger.sideload.error("Failed to receive anisette data response. \(error.localizedDescription, privacy: .public)")
|
||||||
case .success(.anisetteData(let response)): self.finish(.success(response.anisetteData))
|
self.finish(.failure(error))
|
||||||
case .success: self.finish(.failure(ALTServerError(.unknownRequest)))
|
|
||||||
|
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))) }
|
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.progress.totalUnitCount = Int64(1 + app.appExtensions.count)
|
||||||
|
|
||||||
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
||||||
@@ -245,6 +247,8 @@ extension FetchProvisioningProfilesOperation
|
|||||||
|
|
||||||
if let appID = appIDs.first(where: { $0.bundleIdentifier.lowercased() == bundleIdentifier.lowercased() })
|
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))
|
completionHandler(.success(appID))
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -275,6 +279,9 @@ extension FetchProvisioningProfilesOperation
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
let appID = try Result(appID, error).get()
|
let appID = try Result(appID, error).get()
|
||||||
|
|
||||||
|
Logger.sideload.notice("Registered new App ID \(appID.bundleIdentifier, privacy: .public)")
|
||||||
|
|
||||||
completionHandler(.success(appID))
|
completionHandler(.success(appID))
|
||||||
}
|
}
|
||||||
catch ALTAppleAPIError.maximumAppIDLimitReached
|
catch ALTAppleAPIError.maximumAppIDLimitReached
|
||||||
@@ -358,8 +365,15 @@ extension FetchProvisioningProfilesOperation
|
|||||||
let appID = appID.copy() as! ALTAppID
|
let appID = appID.copy() as! ALTAppID
|
||||||
appID.features = features
|
appID.features = features
|
||||||
|
|
||||||
ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in
|
ALTAppleAPI.shared.update(appID, team: team, session: session) { (updatedAppID, error) in
|
||||||
completionHandler(Result(appID, error))
|
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
|
else
|
||||||
@@ -377,6 +391,7 @@ extension FetchProvisioningProfilesOperation
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard var applicationGroups = entitlements[.appGroups] as? [String], !applicationGroups.isEmpty else {
|
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,
|
// Assigning an App ID to an empty app group array fails,
|
||||||
// so just do nothing if there are no app groups.
|
// so just do nothing if there are no app groups.
|
||||||
return completionHandler(.success(appID))
|
return completionHandler(.success(appID))
|
||||||
@@ -414,7 +429,10 @@ extension FetchProvisioningProfilesOperation
|
|||||||
ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in
|
ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in
|
||||||
switch Result(groups, error)
|
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):
|
case .success(let fetchedGroups):
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
|
|
||||||
@@ -439,8 +457,13 @@ extension FetchProvisioningProfilesOperation
|
|||||||
ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in
|
ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in
|
||||||
switch Result(group, error)
|
switch Result(group, error)
|
||||||
{
|
{
|
||||||
case .success(let group): groups.append(group)
|
case .success(let group):
|
||||||
case .failure(let error): errors.append(error)
|
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()
|
dispatchGroup.leave()
|
||||||
@@ -456,8 +479,17 @@ extension FetchProvisioningProfilesOperation
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in
|
ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in
|
||||||
let result = Result(success, error)
|
let groupIDs = groups.map { $0.groupIdentifier }
|
||||||
finish(result.map { _ in appID })
|
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))
|
completionHandler(.success(profile))
|
||||||
|
|
||||||
case .success:
|
case .success:
|
||||||
|
Logger.sideload.notice("Generating new free provisioning profile for App ID \(appID.bundleIdentifier, privacy: .public).")
|
||||||
|
|
||||||
// Fetch new provisioning profile
|
// Fetch new provisioning profile
|
||||||
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
|
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
|
||||||
completionHandler(Result(profile, error))
|
completionHandler(Result(profile, error))
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ class FindServerOperation: ResultOperation<Server>
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.sideload.notice("Discovering AltServers...")
|
||||||
|
|
||||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||||
let observer = Unmanaged.passUnretained(self).toOpaque()
|
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||||
|
|
||||||
@@ -63,22 +65,30 @@ class FindServerOperation: ResultOperation<Server>
|
|||||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
||||||
if let machServiceName = self.localServerMachServiceName
|
if let machServiceName = self.localServerMachServiceName
|
||||||
{
|
{
|
||||||
|
Logger.sideload.notice("Found AltDaemon!")
|
||||||
|
|
||||||
// Prefer background daemon, if it exists and is running.
|
// Prefer background daemon, if it exists and is running.
|
||||||
let server = Server(connectionType: .local, machServiceName: machServiceName)
|
let server = Server(connectionType: .local, machServiceName: machServiceName)
|
||||||
self.finish(.success(server))
|
self.finish(.success(server))
|
||||||
}
|
}
|
||||||
else if self.isWiredServerConnectionAvailable
|
else if self.isWiredServerConnectionAvailable
|
||||||
{
|
{
|
||||||
|
Logger.sideload.notice("Found AltServer connected via USB!")
|
||||||
|
|
||||||
let server = Server(connectionType: .wired)
|
let server = Server(connectionType: .wired)
|
||||||
self.finish(.success(server))
|
self.finish(.success(server))
|
||||||
}
|
}
|
||||||
else if let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred })
|
else if let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred })
|
||||||
{
|
{
|
||||||
|
Logger.sideload.notice("Found preferred AltServer! \(server.localizedName ?? "nil", privacy: .public)")
|
||||||
|
|
||||||
// Preferred server.
|
// Preferred server.
|
||||||
self.finish(.success(server))
|
self.finish(.success(server))
|
||||||
}
|
}
|
||||||
else if let server = ServerManager.shared.discoveredServers.first
|
else if let server = ServerManager.shared.discoveredServers.first
|
||||||
{
|
{
|
||||||
|
Logger.sideload.notice("Found AltServer! \(server.localizedName ?? "nil", privacy: .public)")
|
||||||
|
|
||||||
// Any available server.
|
// Any available server.
|
||||||
self.finish(.success(server))
|
self.finish(.success(server))
|
||||||
}
|
}
|
||||||
@@ -113,7 +123,7 @@ fileprivate extension FindServerOperation
|
|||||||
connection.connect { (result) in
|
connection.connect { (result) in
|
||||||
switch result
|
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
|
case .success: self.localServerMachServiceName = machServiceName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
let connection = self.context.installationConnection
|
let connection = self.context.installationConnection
|
||||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
|
|
||||||
|
Logger.sideload.notice("Installing resigned app \(resignedApp.bundleIdentifier, privacy: .public)...")
|
||||||
|
|
||||||
@Managed var appVersion = self.context.appVersion
|
@Managed var appVersion = self.context.appVersion
|
||||||
let storeBuildVersion = $appVersion.buildVersion
|
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
|
connection.send(request) { (result) in
|
||||||
switch result
|
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:
|
case .success:
|
||||||
|
Logger.sideload.notice("Sent begin installation request for resigned app \(resignedBundleID, privacy: .public).")
|
||||||
|
|
||||||
self.receive(from: connection) { (result) in
|
self.receive(from: connection) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success:
|
case .success:
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
|
Logger.sideload.notice("Successfully installed resigned app \(resignedBundleID, privacy: .public)!")
|
||||||
|
|
||||||
installedApp.refreshedDate = Date()
|
installedApp.refreshedDate = Date()
|
||||||
self.finish(.success(installedApp))
|
self.finish(.success(installedApp))
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
|
Logger.sideload.notice("Failed to install resigned app \(resignedBundleID, privacy: .public). \(error.localizedDescription, privacy: .public)")
|
||||||
self.finish(.failure(error))
|
self.finish(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +203,7 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
}
|
}
|
||||||
catch
|
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
|
do
|
||||||
{
|
{
|
||||||
let response = try result.get()
|
let response = try result.get()
|
||||||
print(response)
|
|
||||||
|
|
||||||
switch response
|
switch response
|
||||||
{
|
{
|
||||||
case .installationProgress(let 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
|
if response.progress == 1.0
|
||||||
{
|
{
|
||||||
self.progress.completedUnitCount = self.progress.totalUnitCount
|
self.progress.completedUnitCount = self.progress.totalUnitCount
|
||||||
@@ -249,7 +261,7 @@ private extension InstallAppOperation
|
|||||||
}
|
}
|
||||||
catch
|
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
|
return archiveURL
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -228,7 +228,7 @@ private extension PatchAppOperation
|
|||||||
return .ok
|
return .ok
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Extracted Spotlight from OTA archive.")
|
Logger.fugu14.notice("Extracted Spotlight from OTA archive.")
|
||||||
return spotlightFileURL
|
return spotlightFileURL
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -250,7 +250,7 @@ private extension PatchAppOperation
|
|||||||
let appBinaryURL = temporaryAppURL.appendingPathComponent(appName, isDirectory: false)
|
let appBinaryURL = temporaryAppURL.appendingPathComponent(appName, isDirectory: false)
|
||||||
try self.appPatcher.patchAppBinary(at: appBinaryURL, withBinaryAt: patchFileURL)
|
try self.appPatcher.patchAppBinary(at: appBinaryURL, withBinaryAt: patchFileURL)
|
||||||
|
|
||||||
print("Patched \(app.name).")
|
Logger.fugu14.notice("Patched \(app.name, privacy: .public)!")
|
||||||
return temporaryAppURL
|
return temporaryAppURL
|
||||||
}
|
}
|
||||||
.mapError { ($0 as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not patch %@ placeholder.", comment: ""), app.name)) }
|
.mapError { ($0 as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not patch %@ placeholder.", comment: ""), app.name)) }
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class PatchViewController: UIViewController
|
|||||||
}
|
}
|
||||||
catch
|
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()
|
self.update()
|
||||||
@@ -196,7 +196,7 @@ private extension PatchViewController
|
|||||||
}
|
}
|
||||||
catch
|
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
|
if let observation = self.didEnterBackgroundObservation
|
||||||
@@ -312,7 +312,7 @@ private extension PatchViewController
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
print("Error unzipping app bundle:", error)
|
Logger.fugu14.error("Error unzipping app bundle: \(error.localizedDescription, privacy: .public)")
|
||||||
unzippingError = error
|
unzippingError = error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,13 +44,15 @@ class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
guard let app = self.context.app else { throw OperationError.appNotFound(name: nil) }
|
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 }
|
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
|
ServerManager.shared.connect(to: server) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error): self.finish(.failure(error))
|
||||||
case .success(let connection):
|
case .success(let connection):
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
print("Sending refresh app request...")
|
Logger.sideload.debug("Sending refresh app request...")
|
||||||
|
|
||||||
var activeProfiles: Set<String>?
|
var activeProfiles: Set<String>?
|
||||||
if UserDefaults.standard.activeAppsLimit != nil
|
if UserDefaults.standard.activeAppsLimit != nil
|
||||||
@@ -65,20 +67,24 @@ class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
|
|
||||||
let request = InstallProvisioningProfilesRequest(udid: udid, provisioningProfiles: Set(profiles.values), activeProfiles: activeProfiles)
|
let request = InstallProvisioningProfilesRequest(udid: udid, provisioningProfiles: Set(profiles.values), activeProfiles: activeProfiles)
|
||||||
connection.send(request) { (result) in
|
connection.send(request) { (result) in
|
||||||
print("Sent refresh app request!")
|
Logger.sideload.debug("Sent refresh app request!")
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error): self.finish(.failure(error))
|
||||||
case .success:
|
case .success:
|
||||||
print("Waiting for refresh app response...")
|
Logger.sideload.debug("Waiting for refresh app response...")
|
||||||
|
|
||||||
connection.receiveResponse() { (result) in
|
connection.receiveResponse() { (result) in
|
||||||
print("Receiving refresh app response:", result)
|
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error):
|
||||||
case .success(.error(let response)): self.finish(.failure(response.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):
|
case .success(.installProvisioningProfiles):
|
||||||
self.managedObjectContext.perform {
|
self.managedObjectContext.perform {
|
||||||
@@ -100,10 +106,13 @@ class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
installedExtension.update(provisioningProfile: provisioningProfile)
|
installedExtension.update(provisioningProfile: provisioningProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.sideload.notice("Refreshed provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)")
|
||||||
self.finish(.success(installedApp))
|
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 Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
@objc(RemoveAppBackupOperation)
|
@objc(RemoveAppBackupOperation)
|
||||||
class RemoveAppBackupOperation: ResultOperation<Void>
|
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)) }
|
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 {
|
installedApp.managedObjectContext?.perform {
|
||||||
guard let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return self.finish(.failure(OperationError.missingAppGroup)) }
|
guard let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return self.finish(.failure(OperationError.missingAppGroup)) }
|
||||||
|
|
||||||
@@ -61,14 +66,14 @@ class RemoveAppBackupOperation: ResultOperation<Void>
|
|||||||
|
|
||||||
#else
|
#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))
|
self.finish(.failure(error))
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
catch
|
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))
|
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 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)) }
|
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 {
|
installedApp.managedObjectContext?.perform {
|
||||||
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
|
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
|
||||||
|
|
||||||
@@ -43,19 +45,24 @@ class RemoveAppOperation: ResultOperation<InstalledApp>
|
|||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error): self.finish(.failure(error))
|
||||||
case .success(let connection):
|
case .success(let connection):
|
||||||
print("Sending remove app request...")
|
Logger.sideload.debug("Sending remove app request...")
|
||||||
|
|
||||||
let request = RemoveAppRequest(udid: udid, bundleIdentifier: resignedBundleIdentifier)
|
let request = RemoveAppRequest(udid: udid, bundleIdentifier: resignedBundleIdentifier)
|
||||||
connection.send(request) { (result) in
|
connection.send(request) { (result) in
|
||||||
print("Sent remove app request!")
|
|
||||||
|
|
||||||
switch result
|
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:
|
case .success:
|
||||||
print("Waiting for remove app response...")
|
Logger.sideload.debug("Waiting for remove app response...")
|
||||||
connection.receiveResponse() { (result) in
|
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
|
switch result
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ class ResignAppOperation: ResultOperation<ALTApplication>
|
|||||||
let certificate = self.context.certificate
|
let certificate = self.context.certificate
|
||||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
|
|
||||||
|
Logger.sideload.notice("Resigning app \(self.context.bundleIdentifier, privacy: .public)...")
|
||||||
|
|
||||||
// Prepare app bundle
|
// Prepare app bundle
|
||||||
let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2)
|
let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2)
|
||||||
self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3)
|
self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3)
|
||||||
@@ -50,8 +52,6 @@ class ResignAppOperation: ResultOperation<ALTApplication>
|
|||||||
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in
|
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in
|
||||||
guard let appBundleURL = self.process(result) else { return }
|
guard let appBundleURL = self.process(result) else { return }
|
||||||
|
|
||||||
print("Resigning App:", self.context.bundleIdentifier)
|
|
||||||
|
|
||||||
// Resign app bundle
|
// Resign app bundle
|
||||||
let resignProgress = self.resignAppBundle(at: appBundleURL, team: team, certificate: certificate, profiles: Array(profiles.values)) { (result) in
|
let resignProgress = self.resignAppBundle(at: appBundleURL, team: team, certificate: certificate, profiles: Array(profiles.values)) { (result) in
|
||||||
guard let resignedURL = self.process(result) else { return }
|
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.
|
// Use appBundleURL since we need an app bundle, not .ipa.
|
||||||
guard let resignedApplication = ALTApplication(fileURL: appBundleURL) else { throw OperationError.invalidApp }
|
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))
|
self.finish(.success(resignedApplication))
|
||||||
}
|
}
|
||||||
catch
|
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)) }
|
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.
|
// 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 app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL)
|
||||||
let fileURL = InstalledApp.refreshedIPAURL(for: app)
|
let fileURL = InstalledApp.refreshedIPAURL(for: app)
|
||||||
@@ -99,17 +101,17 @@ private extension SendAppOperation
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
print("Sending app data (\(appData.count) bytes)...")
|
Logger.sideload.debug("Sending app data (\(appData.count) bytes)...")
|
||||||
|
|
||||||
connection.send(appData, prependSize: false) { (result) in
|
connection.send(appData, prependSize: false) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error):
|
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))
|
completionHandler(.failure(error))
|
||||||
|
|
||||||
case .success:
|
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(()))
|
completionHandler(.success(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,10 +87,11 @@ class UpdatePatronsOperation: ResultOperation<Void>
|
|||||||
|
|
||||||
self.finish(.success(()))
|
self.finish(.success(()))
|
||||||
|
|
||||||
print("Updated Friend Zone Patrons!")
|
Logger.main.notice("Updated Friend Zone Patrons! Refresh ID: \(response.refreshID, privacy: .public)")
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
Logger.main.error("Failed to update Friend Zone Patrons. \(error.localizedDescription, privacy: .public)")
|
||||||
self.finish(.failure(error))
|
self.finish(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ class VerifyAppOperation: ResultOperation<Void>
|
|||||||
|
|
||||||
guard let app = self.context.app else { throw OperationError.invalidParameters }
|
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 {
|
guard app.bundleIdentifier == self.context.bundleIdentifier else {
|
||||||
throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app)
|
throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app)
|
||||||
}
|
}
|
||||||
@@ -149,9 +151,9 @@ private extension VerifyAppOperation
|
|||||||
let data = try Data(contentsOf: ipaURL)
|
let data = try Data(contentsOf: ipaURL)
|
||||||
let sha256Hash = SHA256.hash(data: data)
|
let sha256Hash = SHA256.hash(data: data)
|
||||||
let hashString = sha256Hash.compactMap { String(format: "%02x", $0) }.joined()
|
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) }
|
guard hashString == expectedHash else { throw VerificationError.mismatchedHash(hashString, expectedHash: expectedHash, app: app) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ struct Server: Equatable
|
|||||||
|
|
||||||
extension Server
|
extension Server
|
||||||
{
|
{
|
||||||
|
var localizedName: String? {
|
||||||
|
return self.service?.name ?? self.identifier
|
||||||
|
}
|
||||||
|
|
||||||
// Defined in extension so we can still use the automatically synthesized initializer.
|
// Defined in extension so we can still use the automatically synthesized initializer.
|
||||||
init?(service: NetService, txtData: Data)
|
init?(service: NetService, txtData: Data)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ extension ServerManager
|
|||||||
case .wired:
|
case .wired:
|
||||||
guard let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore else { return finish(.failure(ALTServerError(.connectionFailed))) }
|
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()
|
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ extension ServerManager
|
|||||||
case .wireless:
|
case .wireless:
|
||||||
guard let service = server.service else { return finish(.failure(ALTServerError(.connectionFailed))) }
|
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)
|
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(_:))
|
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)
|
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
|
connection.stateUpdateHandler = { [unowned connection] (state) in
|
||||||
switch state
|
switch state
|
||||||
{
|
{
|
||||||
case .failed(let error):
|
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))
|
completion(.failure(OperationError.connectionFailed))
|
||||||
|
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
completion(.failure(OperationError.cancelled))
|
completion(.failure(OperationError.cancelled))
|
||||||
|
|
||||||
case .ready:
|
case .ready:
|
||||||
|
Logger.sideload.notice("Connected to \(serverName, privacy: .public)!")
|
||||||
let connection = NetworkConnection(connection)
|
let connection = NetworkConnection(connection)
|
||||||
completion(.success(connection))
|
completion(.success(connection))
|
||||||
|
|
||||||
@@ -201,10 +216,12 @@ private extension ServerManager
|
|||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error):
|
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))
|
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)
|
func netServiceBrowserWillSearch(_ browser: NetServiceBrowser)
|
||||||
{
|
{
|
||||||
print("Discovering servers...")
|
Logger.main.notice("Discovering AltServers...")
|
||||||
}
|
}
|
||||||
|
|
||||||
func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser)
|
func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser)
|
||||||
{
|
{
|
||||||
print("Stopped discovering servers.")
|
Logger.main.notice("Stopped discovering AltServers.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func netServiceBrowser(_ browser: NetServiceBrowser, didNotSearch errorDict: [String : NSNumber])
|
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)
|
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool)
|
||||||
@@ -263,12 +280,12 @@ extension ServerManager: NetServiceDelegate
|
|||||||
|
|
||||||
func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber])
|
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)
|
func netService(_ sender: NetService, didUpdateTXTRecord data: Data)
|
||||||
{
|
{
|
||||||
let txtDict = NetService.dictionary(fromTXTRecord: 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
|
return dateFormatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@IBOutlet private var exportLogButton: UIBarButtonItem!
|
||||||
|
@IBOutlet private var clearLogButton: UIBarButtonItem!
|
||||||
|
|
||||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||||
return .lightContent
|
return .lightContent
|
||||||
}
|
}
|
||||||
@@ -43,6 +46,14 @@ class ErrorLogViewController: UITableViewController
|
|||||||
|
|
||||||
self.tableView.dataSource = self.dataSource
|
self.tableView.dataSource = self.dataSource
|
||||||
self.tableView.prefetchDataSource = 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?)
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
@@ -260,6 +271,77 @@ private extension ErrorLogViewController
|
|||||||
{
|
{
|
||||||
self.performSegue(withIdentifier: "showErrorDetails", sender: loggedError)
|
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
|
extension ErrorLogViewController
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<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="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.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>
|
</connections>
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="Error Log" largeTitleDisplayMode="never" id="a1p-3W-bSi">
|
<navigationItem key="navigationItem" title="Error Log" largeTitleDisplayMode="never" id="a1p-3W-bSi">
|
||||||
<barButtonItem key="rightBarButtonItem" systemItem="trash" id="BnQ-Eh-1gC">
|
<rightBarButtonItems>
|
||||||
<connections>
|
<barButtonItem systemItem="trash" id="BnQ-Eh-1gC">
|
||||||
<action selector="clearLoggedErrors:" destination="g8a-Rf-zWa" id="faq-89-H5j"/>
|
<connections>
|
||||||
</connections>
|
<action selector="clearLoggedErrors:" destination="g8a-Rf-zWa" id="faq-89-H5j"/>
|
||||||
</barButtonItem>
|
</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>
|
</navigationItem>
|
||||||
<connections>
|
<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"/>
|
<segue destination="7gm-d1-zWK" kind="presentation" identifier="showErrorDetails" id="9vz-y6-evp"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</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