Files
xtool-mirror/Sources/XKit/Installation/AppInstaller.swift
2025-05-24 00:35:46 +05:30

129 lines
3.6 KiB
Swift

//
// AppInstaller.swift
// XKit
//
// Created by Kabir Oberai on 13/11/19.
// Copyright © 2019 Kabir Oberai. All rights reserved.
//
import Foundation
import SwiftyMobileDevice
public actor AppInstaller {
public enum Error: LocalizedError {
case userCancelled
case invalidURL
public var errorDescription: String? {
switch self {
case .userCancelled:
return NSLocalizedString(
"app_installer.error.user_cancelled",
value: "The user cancelled the operation",
comment: ""
)
case .invalidURL:
return NSLocalizedString(
"app_installer.error.invalid_url",
value: "Invalid URL",
comment: ""
)
}
}
}
public enum Stage: Sendable {
case connecting(Double)
case uploading(Double)
case installing(String, Double?)
public var displayName: String {
switch self {
case .connecting:
return NSLocalizedString(
"app_installer.stage.connecting",
value: "Connecting",
comment: ""
)
case .uploading:
// installing is a better name although this is
// technically uploading
return NSLocalizedString(
"app_installer.stage.installing",
value: "Installing",
comment: ""
)
case .installing:
return NSLocalizedString(
"app_installer.stage.verifying",
value: "Verifying",
comment: ""
)
}
}
public var displayProgress: Double {
switch self {
case .connecting(let progress):
return progress
case .uploading(let progress):
return progress
case .installing(_, let progress):
return progress ?? 0
}
}
}
public let ipa: URL
public let udid: String
public let connectionPreferences: Connection.Preferences
public init(
ipa: URL,
udid: String,
connectionPreferences: Connection.Preferences
) {
self.ipa = ipa
self.udid = udid
self.connectionPreferences = connectionPreferences
}
// synchronous
public func install(progress: @escaping @Sendable (Stage) -> Void) async throws {
let connection = try await Connection.connection(
forUDID: udid,
preferences: connectionPreferences
) {
progress(.connecting($0 * 4/6))
}
try Task.checkCancellation()
let uploader = try await IPAUploader(connection: connection)
progress(.connecting(5/6))
try Task.checkCancellation()
// we need to start the installer quickly because sometimes it fails if we do it after
// uploading the ipa to the device
let installer = try await IPAInstaller(connection: connection)
progress(.connecting(6/6))
try Task.checkCancellation()
let uploaded = try await uploader.upload(app: ipa) { currentProgress in
progress(.uploading(currentProgress))
}
defer { uploaded.delete() }
try Task.checkCancellation()
try await installer.install(uploaded: uploaded) { currentProgress in
progress(.installing(currentProgress.details, currentProgress.progress))
}
_ = connection
}
}