templates and availability spawned `emulator -list-avds` on every render, so opening the boot sheet -- and clicking a template, which re-queries them all -- stuttered for seconds. Cache the listing and warm it off the main thread when the picker or sheet appears. Collapse the two per-architecture Android entries into one; the chosen AVD now decides the architecture, retiring the message that wrongly claimed Frida could only instrument the other one.
114 lines
3.4 KiB
Swift
114 lines
3.4 KiB
Swift
import Foundation
|
|
|
|
@MainActor
|
|
public protocol VirtualMachineBackend: AnyObject {
|
|
var id: String { get }
|
|
var name: String { get }
|
|
var templates: [VirtualMachineTemplate] { get }
|
|
|
|
func availability(for template: VirtualMachineTemplate) -> VirtualMachineAvailability
|
|
func launch(_ request: VirtualMachineLaunchRequest) async throws -> any VirtualMachine
|
|
|
|
func prewarm() async
|
|
|
|
func resolvedParameters(
|
|
for template: VirtualMachineTemplate,
|
|
parameters: [String: VirtualMachineParameterValue]
|
|
) -> [String: VirtualMachineParameterValue]
|
|
}
|
|
|
|
extension VirtualMachineBackend {
|
|
public func prewarm() async {}
|
|
|
|
public func resolvedParameters(
|
|
for template: VirtualMachineTemplate,
|
|
parameters: [String: VirtualMachineParameterValue]
|
|
) -> [String: VirtualMachineParameterValue] {
|
|
parameters
|
|
}
|
|
}
|
|
|
|
public enum VirtualMachineAvailability: Sendable, Equatable {
|
|
case available
|
|
case unavailable(reason: String)
|
|
|
|
public var isAvailable: Bool {
|
|
self == .available
|
|
}
|
|
|
|
public var reason: String? {
|
|
guard case .unavailable(let reason) = self else { return nil }
|
|
return reason
|
|
}
|
|
}
|
|
|
|
public struct VirtualMachineLaunchRequest: Sendable {
|
|
public let id: UUID
|
|
public let template: VirtualMachineTemplate
|
|
public let name: String
|
|
public let parameters: [String: VirtualMachineParameterValue]
|
|
public let agentPath: URL?
|
|
public let storageDirectory: URL
|
|
public let resumesFromReadySnapshot: Bool
|
|
|
|
public init(
|
|
id: UUID,
|
|
template: VirtualMachineTemplate,
|
|
name: String,
|
|
parameters: [String: VirtualMachineParameterValue],
|
|
agentPath: URL?,
|
|
storageDirectory: URL,
|
|
resumesFromReadySnapshot: Bool
|
|
) {
|
|
self.id = id
|
|
self.template = template
|
|
self.name = name
|
|
self.parameters = parameters
|
|
self.agentPath = agentPath
|
|
self.storageDirectory = storageDirectory
|
|
self.resumesFromReadySnapshot = resumesFromReadySnapshot
|
|
}
|
|
|
|
public func text(_ id: String) -> String? {
|
|
parameters[id]?.text
|
|
}
|
|
|
|
public func number(_ id: String) -> Int? {
|
|
parameters[id]?.number
|
|
}
|
|
|
|
public func toggle(_ id: String) -> Bool? {
|
|
parameters[id]?.toggle
|
|
}
|
|
}
|
|
|
|
public enum VirtualMachineError: Swift.Error, LocalizedError {
|
|
case launchFailed(reason: String)
|
|
case snapshotFailed(reason: String)
|
|
case displayUnavailable(reason: String)
|
|
|
|
/// An error that already says it could not start the machine is passed on
|
|
/// as it is, rather than being wrapped in the same words twice.
|
|
static func launchFailure(_ reason: String, from error: Swift.Error) -> VirtualMachineError {
|
|
(error as? VirtualMachineError) ?? .launchFailed(reason: reason)
|
|
}
|
|
|
|
public var reason: String {
|
|
switch self {
|
|
case .launchFailed(let reason), .snapshotFailed(let reason), .displayUnavailable(let reason):
|
|
return reason
|
|
}
|
|
}
|
|
|
|
public var errorDescription: String? {
|
|
switch self {
|
|
case .launchFailed(let reason):
|
|
return "Unable to start the machine: \(reason)"
|
|
case .snapshotFailed(let reason):
|
|
return "Unable to snapshot the machine: \(reason)"
|
|
case .displayUnavailable(let reason):
|
|
return "Unable to show the machine's display: \(reason)"
|
|
}
|
|
}
|
|
}
|