Add a machine abstraction to LumaCore, with QEMU as its first backend: templates mirroring frida-core's test guests, a QMP client, and a display that maps QEMU's surface over D-Bus rather than copying frames. Leave readiness to the user: drive the guest, mark it ready, and come back to that snapshot later. Keep the machine in the project, so booting it again resumes where it was marked. Hand the stub to addBareboneDevice, so it joins the picker beside the other devices. Show the display while booting, and afterwards in a Machines panel, which the side panel now models as an enum to share its slot.
69 lines
2.2 KiB
Swift
69 lines
2.2 KiB
Swift
import Foundation
|
|
|
|
@MainActor
|
|
public final class EngineRegistry {
|
|
public static let shared = EngineRegistry()
|
|
|
|
public init() {}
|
|
|
|
private var engines: [URL: Engine] = [:]
|
|
private var startTasks: [URL: Task<Void, Never>] = [:]
|
|
|
|
public func engine(
|
|
for workingProjectURL: URL,
|
|
dataDirectory: URL,
|
|
gitHubAuth: GitHubAuth? = nil
|
|
) throws -> Engine {
|
|
let key = workingProjectURL.standardizedFileURL
|
|
if let existing = engines[key] {
|
|
return existing
|
|
}
|
|
let fm = FileManager.default
|
|
let dbURL = key.appendingPathComponent("db.sqlite")
|
|
let tracesURL = key.appendingPathComponent("traces", isDirectory: true)
|
|
let eventsURL = key.appendingPathComponent("events.log")
|
|
try fm.createDirectory(at: key, withIntermediateDirectories: true)
|
|
try fm.createDirectory(at: tracesURL, withIntermediateDirectories: true)
|
|
let store = try ProjectStore(path: dbURL.path)
|
|
let blobs = try BlobStore(directory: tracesURL)
|
|
let eventStore = EventStore(fileURL: eventsURL)
|
|
let engine = Engine(
|
|
store: store,
|
|
blobs: blobs,
|
|
eventStore: eventStore,
|
|
dataDirectory: dataDirectory,
|
|
gitHubAuth: gitHubAuth
|
|
)
|
|
engines[key] = engine
|
|
return engine
|
|
}
|
|
|
|
public func startIfNeeded(for workingProjectURL: URL) async {
|
|
let key = workingProjectURL.standardizedFileURL
|
|
if let existing = startTasks[key] {
|
|
await existing.value
|
|
return
|
|
}
|
|
guard let engine = engines[key] else { return }
|
|
let task = Task { @MainActor in
|
|
await engine.start()
|
|
}
|
|
startTasks[key] = task
|
|
await task.value
|
|
}
|
|
|
|
public func shutdownAll() async {
|
|
for url in Array(engines.keys) {
|
|
await release(workingProjectURL: url)
|
|
}
|
|
}
|
|
|
|
public func release(workingProjectURL: URL) async {
|
|
let key = workingProjectURL.standardizedFileURL
|
|
let pending = startTasks.removeValue(forKey: key)
|
|
let engine = engines.removeValue(forKey: key)
|
|
await pending?.value
|
|
await engine?.shutdown()
|
|
}
|
|
}
|