From 452f8c2088cf511f3604f5eddae6a1be9bed0fc5 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Thu, 15 Feb 2024 23:50:00 -0800 Subject: [PATCH] remote: implement ReorderVirtualMachines and GetPackageSize --- Platform/Shared/VMDetailsView.swift | 10 ++++- Platform/UTMData.swift | 61 ++++++++++++++++++++++++++++- Remote/UTMRemoteClient.swift | 17 ++++++++ Remote/UTMRemoteMessage.swift | 24 +++++++++--- Remote/UTMRemoteServer.swift | 26 ++++++++++-- 5 files changed, 124 insertions(+), 14 deletions(-) diff --git a/Platform/Shared/VMDetailsView.swift b/Platform/Shared/VMDetailsView.swift index d23cf775..4431a40d 100644 --- a/Platform/Shared/VMDetailsView.swift +++ b/Platform/Shared/VMDetailsView.swift @@ -29,9 +29,10 @@ struct VMDetailsView: View { #else private let regularScreenSizeClass: Bool = true #endif - + + @State private var size: Int64 = 0 + private var sizeLabel: String { - let size = data.computeSize(for: vm) return ByteCountFormatter.string(fromByteCount: size, countStyle: .binary) } @@ -109,6 +110,11 @@ struct VMDetailsView: View { } #endif } + .onAppear { + Task { + size = await data.computeSize(for: vm) + } + } } } } diff --git a/Platform/UTMData.swift b/Platform/UTMData.swift index f899c64e..afbc8f1a 100644 --- a/Platform/UTMData.swift +++ b/Platform/UTMData.swift @@ -549,7 +549,7 @@ struct AlertMessage: Identifiable { /// Calculate total size of VM and data /// - Parameter vm: VM to calculate size /// - Returns: Size in bytes - func computeSize(for vm: VMData) -> Int64 { + func computeSize(for vm: VMData) async -> Int64 { let path = vm.pathUrl guard let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: [.totalFileAllocatedSizeKey]) else { logger.error("failed to create enumerator for \(path)") @@ -633,7 +633,7 @@ struct AlertMessage: Identifiable { listSelect(vm: vm) } - func copyItemWithCopyfile(at srcURL: URL, to dstURL: URL) async throws { + private func copyItemWithCopyfile(at srcURL: URL, to dstURL: URL) async throws { try await Task.detached(priority: .userInitiated) { let status = copyfile(srcURL.path, dstURL.path, nil, copyfile_flags_t(COPYFILE_ALL | COPYFILE_RECURSIVE | COPYFILE_CLONE | COPYFILE_DATA_SPARSE)) if status < 0 { @@ -999,6 +999,7 @@ enum UTMDataError: Error { case jitStreamerDecodeFailed case jitStreamerAttachFailed case jitStreamerUrlInvalid(String) + case notImplemented } extension UTMDataError: LocalizedError { @@ -1028,6 +1029,8 @@ extension UTMDataError: LocalizedError { return NSLocalizedString("Failed to attach to JitStreamer.", comment: "UTMData") case .jitStreamerUrlInvalid(let urlString): return String.localizedStringWithFormat(NSLocalizedString("Invalid JitStreamer attach URL:\n%@", comment: "UTMData"), urlString) + case .notImplemented: + return NSLocalizedString("This functionality is not yet implemented.", comment: "UTMData") } } } @@ -1122,5 +1125,59 @@ class UTMRemoteData: UTMData { session.fatalError = message } } + + override func listMove(fromOffsets: IndexSet, toOffset: Int) { + let ids = fromOffsets.map({ virtualMachines[$0].id }) + Task { + try await remoteClient.server.reorderVirtualMachines(fromIds: ids, toOffset: toOffset) + } + super.listMove(fromOffsets: fromOffsets, toOffset: toOffset) + } + + override func save(vm: VMData) async throws { + throw UTMDataError.notImplemented + } + + override func discardChanges(for vm: VMData) throws { + throw UTMDataError.notImplemented + } + + override func create(config: Config) async throws -> VMData { + throw UTMDataError.notImplemented + } + + @discardableResult + override func delete(vm: VMData, alsoRegistry: Bool) async throws -> Int? { + throw UTMDataError.notImplemented + } + + @discardableResult + override func clone(vm: VMData) async throws -> VMData { + throw UTMDataError.notImplemented + } + + override func export(vm: VMData, to url: URL) async throws { + throw UTMDataError.notImplemented + } + + override func move(vm: VMData, to url: URL) async throws { + throw UTMDataError.notImplemented + } + + override func template(vm: VMData) async throws { + throw UTMDataError.notImplemented + } + + override func computeSize(for vm: VMData) async -> Int64 { + (try? await remoteClient.server.getPackageSize(for: vm.id)) ?? 0 + } + + override func importUTM(from url: URL, asShortcut: Bool) async throws { + throw UTMDataError.notImplemented + } + + override func mountSupportTools(for vm: any UTMVirtualMachine) async throws { + throw UTMDataError.notImplemented + } } #endif diff --git a/Remote/UTMRemoteClient.swift b/Remote/UTMRemoteClient.swift index 58104714..425b4bb6 100644 --- a/Remote/UTMRemoteClient.swift +++ b/Remote/UTMRemoteClient.swift @@ -333,10 +333,18 @@ extension UTMRemoteClient { try await _listVirtualMachines(parameters: .init()).items } + func reorderVirtualMachines(fromIds ids: [UUID], toOffset offset: Int) async throws { + try await _reorderVirtualMachines(parameters: .init(ids: ids, offset: offset)) + } + func getQEMUConfiguration(for id: UUID) async throws -> UTMQemuConfiguration { try await _getQEMUConfiguration(parameters: .init(id: id)).configuration } + func getPackageSize(for id: UUID) async throws -> Int64 { + try await _getPackageSize(parameters: .init(id: id)).size + } + func getPackageDataFile(for id: UUID, name: String) async throws -> URL { let fm = FileManager.default let cacheUrl = try fm.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) @@ -400,10 +408,19 @@ extension UTMRemoteClient { try await M.ListVirtualMachines.send(parameters, to: peer) } + @discardableResult + private func _reorderVirtualMachines(parameters: M.ReorderVirtualMachines.Request) async throws -> M.ReorderVirtualMachines.Reply { + try await M.ReorderVirtualMachines.send(parameters, to: peer) + } + private func _getQEMUConfiguration(parameters: M.GetQEMUConfiguration.Request) async throws -> M.GetQEMUConfiguration.Reply { try await M.GetQEMUConfiguration.send(parameters, to: peer) } + private func _getPackageSize(parameters: M.GetPackageSize.Request) async throws -> M.GetPackageSize.Reply { + try await M.GetPackageSize.send(parameters, to: peer) + } + private func _getPackageDataFile(parameters: M.GetPackageDataFile.Request) async throws -> M.GetPackageDataFile.Reply { try await M.GetPackageDataFile.send(parameters, to: peer) } diff --git a/Remote/UTMRemoteMessage.swift b/Remote/UTMRemoteMessage.swift index 38534ae8..2a13f9e1 100644 --- a/Remote/UTMRemoteMessage.swift +++ b/Remote/UTMRemoteMessage.swift @@ -21,8 +21,9 @@ enum UTMRemoteMessageServer: UInt8, MessageID { static let version = 1 case serverHandshake case listVirtualMachines + case reorderVirtualMachines case getQEMUConfiguration - case updateQEMUConfiguration + case getPackageSize case getPackageDataFile case startVirtualMachine case stopVirtualMachine @@ -83,6 +84,17 @@ extension UTMRemoteMessageServer { } } + struct ReorderVirtualMachines: Message { + static let id = UTMRemoteMessageServer.reorderVirtualMachines + + struct Request: Serializable, Codable { + let ids: [UUID] + let offset: Int + } + + struct Reply: Serializable, Codable {} + } + struct GetQEMUConfiguration: Message { static let id = UTMRemoteMessageServer.getQEMUConfiguration @@ -95,16 +107,16 @@ extension UTMRemoteMessageServer { } } - struct UpdateQEMUConfiguration: Message { - static let id = UTMRemoteMessageServer.updateQEMUConfiguration + struct GetPackageSize: Message { + static let id = UTMRemoteMessageServer.getPackageSize struct Request: Serializable, Codable { let id: UUID - let configuration: UTMQemuConfiguration - let files: [String: Data] } - struct Reply: Serializable, Codable {} + struct Reply: Serializable, Codable { + let size: Int64 + } } struct GetPackageDataFile: Message { diff --git a/Remote/UTMRemoteServer.swift b/Remote/UTMRemoteServer.swift index 26cfe6ce..1049cfe4 100644 --- a/Remote/UTMRemoteServer.swift +++ b/Remote/UTMRemoteServer.swift @@ -608,10 +608,12 @@ extension UTMRemoteServer { return try await _handshake(parameters: .decode(data)).encode() case .listVirtualMachines: return try await _listVirtualMachines(parameters: .decode(data)).encode() + case .reorderVirtualMachines: + return try await _reorderVirtualMachines(parameters: .decode(data)).encode() case .getQEMUConfiguration: return try await _getQEMUConfiguration(parameters: .decode(data)).encode() - case .updateQEMUConfiguration: - return try await _updateQEMUConfiguration(parameters: .decode(data)).encode() + case .getPackageSize: + return try await _getPackageSize(parameters: .decode(data)).encode() case .getPackageDataFile: return try await _getPackageDataFile(parameters: .decode(data)).encode() case .startVirtualMachine: @@ -679,6 +681,20 @@ extension UTMRemoteServer { return .init(items: items) } + private func _reorderVirtualMachines(parameters: M.ReorderVirtualMachines.Request) async throws -> M.ReorderVirtualMachines.Reply { + await Task { @MainActor in + let vms = data.virtualMachines + let source = parameters.ids.reduce(into: IndexSet(), { indexSet, id in + if let index = vms.firstIndex(where: { $0.id == id }) { + indexSet.insert(index) + } + }) + let destination = min(max(0, parameters.offset), vms.count) + data.listMove(fromOffsets: source, toOffset: destination) + return .init() + }.value + } + private func _getQEMUConfiguration(parameters: M.GetQEMUConfiguration.Request) async throws -> M.GetQEMUConfiguration.Reply { let vm = try await findVM(withId: parameters.id) if let config = await vm.config as? UTMQemuConfiguration { @@ -688,8 +704,10 @@ extension UTMRemoteServer { } } - private func _updateQEMUConfiguration(parameters: M.UpdateQEMUConfiguration.Request) async throws -> M.UpdateQEMUConfiguration.Reply { - return .init() + private func _getPackageSize(parameters: M.GetPackageSize.Request) async throws -> M.GetPackageSize.Reply { + let vm = try await findVM(withId: parameters.id) + let size = await data.computeSize(for: vm) + return .init(size: size) } private func _getPackageDataFile(parameters: M.GetPackageDataFile.Request) async throws -> M.GetPackageDataFile.Reply {