From e75373084ab1d7570135b18555d204f9ca44b791 Mon Sep 17 00:00:00 2001 From: Casey Fleser Date: Mon, 11 Jul 2022 21:13:18 -0500 Subject: [PATCH] Added ability to boot / shutdown devices --- SimDirs/Model/SimCtl.swift | 27 ++++++++++++++++++ SimDirs/Model/SimDevice.swift | 29 +++++++++++++++++++- SimDirs/Views/Model Views/DeviceHeader.swift | 14 ++++++++-- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/SimDirs/Model/SimCtl.swift b/SimDirs/Model/SimCtl.swift index dad0647..9a5bd2f 100644 --- a/SimDirs/Model/SimCtl.swift +++ b/SimDirs/Model/SimCtl.swift @@ -43,7 +43,34 @@ struct SimCtl { return try JSONDecoder().decode([String : [String : [SimDevice]]].self, from: json)["devices"] ?? [:] } + func readDevice(_ device: SimDevice) throws -> SimDevice? { + let json : Data = try run(args: ["list", "-j", "devices", device.udid]) + let decoded = try JSONDecoder().decode([String : [String : [SimDevice]]].self, from: json)["devices"] ?? [:] + var result : SimDevice? = nil + + for devices in decoded.values { + if let match = devices.first(where: { $0.udid == device.udid }) { + result = match + break + } + } + + return result + } + func readAllDevices() throws -> [SimDevice] { return try readAllRuntimeDevices().flatMap { $1 } } + + func bootDevice(_ device: SimDevice, boot: Bool) async throws { + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { let _ : Data = try run(args: [boot ? "boot" : "shutdown", device.udid]) } + group.addTask { try await Task.sleep(nanoseconds: 1_000_000_000) } + try await group.next() // delay or run complate + } + + if let refreshedDev = try readDevice(device) { + await MainActor.run { () -> Void in device.updateDevice(from: refreshedDev) } + } + } } diff --git a/SimDirs/Model/SimDevice.swift b/SimDirs/Model/SimDevice.swift index 2c18c46..e984e33 100644 --- a/SimDirs/Model/SimDevice.swift +++ b/SimDirs/Model/SimDevice.swift @@ -25,12 +25,24 @@ class SimDevice: ObservableObject, Decodable { case booted = "Booted" case shuttingDown = "Shutting Down" case shutdown = "Shutdown" + + var showBooted : Bool { + switch self { + case .booted, .booting: return true + default: return false + } + } } @Published var name : String @Published var state : State @Published var isAvailable : Bool @Published var availabilityError : String? + var isTransitioning : Bool { state == .booting || state == .shuttingDown } + var isBooted : Bool { + get { state.showBooted == true } + set { bootDevice(newValue) } + } let udid : String let dataPath : String @@ -98,7 +110,7 @@ class SimDevice: ObservableObject, Decodable { return !(name == other.name && state == other.state && isAvailable == other.isAvailable && availabilityError == other.availabilityError) } - func updateDevice(from other: SimDevice) -> Bool { + @discardableResult func updateDevice(from other: SimDevice) -> Bool { guard hasChanged(from: other) else { return false } name = other.name @@ -108,6 +120,21 @@ class SimDevice: ObservableObject, Decodable { return true } + + func bootDevice(_ boot: Bool) { + if boot && state == .shutdown || !boot && state == .booted { + let simctl = SimCtl() + + state = boot ? .booting : .shuttingDown + Task { + do { + try await simctl.bootDevice(self, boot: boot) + } catch { + print("Failed to \(boot ? "boot" : "shutdown") device: \(error)") + } + } + } + } } extension SimDevice: SourceItemData { diff --git a/SimDirs/Views/Model Views/DeviceHeader.swift b/SimDirs/Views/Model Views/DeviceHeader.swift index 65e40e0..1bdf3e7 100644 --- a/SimDirs/Views/Model Views/DeviceHeader.swift +++ b/SimDirs/Views/Model Views/DeviceHeader.swift @@ -12,11 +12,21 @@ extension SimDevice { } struct DeviceHeader: View { - @ObservedObject var device : SimDevice + @ObservedObject var device : SimDevice var body: some View { VStack(alignment: .leading, spacing: 3.0) { - Text("State: \(device.state.rawValue)") + HStack(spacing: 8.0) { + Toggle("Booted", isOn: $device.isBooted) + .toggleStyle(.switch) + .disabled(device.isTransitioning) + + if device.isTransitioning { + ProgressView() + .controlSize(.small) + Text(device.state.rawValue) + } + } Text("UDID: \(device.udid)") } .font(.subheadline)