remote: implement ReorderVirtualMachines and GetPackageSize

This commit is contained in:
osy 2024-02-15 23:50:00 -08:00
parent db038df8e2
commit 452f8c2088
5 changed files with 124 additions and 14 deletions

View File

@ -29,9 +29,10 @@ struct VMDetailsView: View {
#else #else
private let regularScreenSizeClass: Bool = true private let regularScreenSizeClass: Bool = true
#endif #endif
@State private var size: Int64 = 0
private var sizeLabel: String { private var sizeLabel: String {
let size = data.computeSize(for: vm)
return ByteCountFormatter.string(fromByteCount: size, countStyle: .binary) return ByteCountFormatter.string(fromByteCount: size, countStyle: .binary)
} }
@ -109,6 +110,11 @@ struct VMDetailsView: View {
} }
#endif #endif
} }
.onAppear {
Task {
size = await data.computeSize(for: vm)
}
}
} }
} }
} }

View File

@ -549,7 +549,7 @@ struct AlertMessage: Identifiable {
/// Calculate total size of VM and data /// Calculate total size of VM and data
/// - Parameter vm: VM to calculate size /// - Parameter vm: VM to calculate size
/// - Returns: Size in bytes /// - Returns: Size in bytes
func computeSize(for vm: VMData) -> Int64 { func computeSize(for vm: VMData) async -> Int64 {
let path = vm.pathUrl let path = vm.pathUrl
guard let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: [.totalFileAllocatedSizeKey]) else { guard let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: [.totalFileAllocatedSizeKey]) else {
logger.error("failed to create enumerator for \(path)") logger.error("failed to create enumerator for \(path)")
@ -633,7 +633,7 @@ struct AlertMessage: Identifiable {
listSelect(vm: vm) 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) { 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)) let status = copyfile(srcURL.path, dstURL.path, nil, copyfile_flags_t(COPYFILE_ALL | COPYFILE_RECURSIVE | COPYFILE_CLONE | COPYFILE_DATA_SPARSE))
if status < 0 { if status < 0 {
@ -999,6 +999,7 @@ enum UTMDataError: Error {
case jitStreamerDecodeFailed case jitStreamerDecodeFailed
case jitStreamerAttachFailed case jitStreamerAttachFailed
case jitStreamerUrlInvalid(String) case jitStreamerUrlInvalid(String)
case notImplemented
} }
extension UTMDataError: LocalizedError { extension UTMDataError: LocalizedError {
@ -1028,6 +1029,8 @@ extension UTMDataError: LocalizedError {
return NSLocalizedString("Failed to attach to JitStreamer.", comment: "UTMData") return NSLocalizedString("Failed to attach to JitStreamer.", comment: "UTMData")
case .jitStreamerUrlInvalid(let urlString): case .jitStreamerUrlInvalid(let urlString):
return String.localizedStringWithFormat(NSLocalizedString("Invalid JitStreamer attach URL:\n%@", comment: "UTMData"), 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 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: UTMConfiguration>(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 #endif

View File

@ -333,10 +333,18 @@ extension UTMRemoteClient {
try await _listVirtualMachines(parameters: .init()).items 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 { func getQEMUConfiguration(for id: UUID) async throws -> UTMQemuConfiguration {
try await _getQEMUConfiguration(parameters: .init(id: id)).configuration 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 { func getPackageDataFile(for id: UUID, name: String) async throws -> URL {
let fm = FileManager.default let fm = FileManager.default
let cacheUrl = try fm.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) 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) 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 { private func _getQEMUConfiguration(parameters: M.GetQEMUConfiguration.Request) async throws -> M.GetQEMUConfiguration.Reply {
try await M.GetQEMUConfiguration.send(parameters, to: peer) 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 { private func _getPackageDataFile(parameters: M.GetPackageDataFile.Request) async throws -> M.GetPackageDataFile.Reply {
try await M.GetPackageDataFile.send(parameters, to: peer) try await M.GetPackageDataFile.send(parameters, to: peer)
} }

View File

@ -21,8 +21,9 @@ enum UTMRemoteMessageServer: UInt8, MessageID {
static let version = 1 static let version = 1
case serverHandshake case serverHandshake
case listVirtualMachines case listVirtualMachines
case reorderVirtualMachines
case getQEMUConfiguration case getQEMUConfiguration
case updateQEMUConfiguration case getPackageSize
case getPackageDataFile case getPackageDataFile
case startVirtualMachine case startVirtualMachine
case stopVirtualMachine 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 { struct GetQEMUConfiguration: Message {
static let id = UTMRemoteMessageServer.getQEMUConfiguration static let id = UTMRemoteMessageServer.getQEMUConfiguration
@ -95,16 +107,16 @@ extension UTMRemoteMessageServer {
} }
} }
struct UpdateQEMUConfiguration: Message { struct GetPackageSize: Message {
static let id = UTMRemoteMessageServer.updateQEMUConfiguration static let id = UTMRemoteMessageServer.getPackageSize
struct Request: Serializable, Codable { struct Request: Serializable, Codable {
let id: UUID let id: UUID
let configuration: UTMQemuConfiguration
let files: [String: Data]
} }
struct Reply: Serializable, Codable {} struct Reply: Serializable, Codable {
let size: Int64
}
} }
struct GetPackageDataFile: Message { struct GetPackageDataFile: Message {

View File

@ -608,10 +608,12 @@ extension UTMRemoteServer {
return try await _handshake(parameters: .decode(data)).encode() return try await _handshake(parameters: .decode(data)).encode()
case .listVirtualMachines: case .listVirtualMachines:
return try await _listVirtualMachines(parameters: .decode(data)).encode() return try await _listVirtualMachines(parameters: .decode(data)).encode()
case .reorderVirtualMachines:
return try await _reorderVirtualMachines(parameters: .decode(data)).encode()
case .getQEMUConfiguration: case .getQEMUConfiguration:
return try await _getQEMUConfiguration(parameters: .decode(data)).encode() return try await _getQEMUConfiguration(parameters: .decode(data)).encode()
case .updateQEMUConfiguration: case .getPackageSize:
return try await _updateQEMUConfiguration(parameters: .decode(data)).encode() return try await _getPackageSize(parameters: .decode(data)).encode()
case .getPackageDataFile: case .getPackageDataFile:
return try await _getPackageDataFile(parameters: .decode(data)).encode() return try await _getPackageDataFile(parameters: .decode(data)).encode()
case .startVirtualMachine: case .startVirtualMachine:
@ -679,6 +681,20 @@ extension UTMRemoteServer {
return .init(items: items) 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 { private func _getQEMUConfiguration(parameters: M.GetQEMUConfiguration.Request) async throws -> M.GetQEMUConfiguration.Reply {
let vm = try await findVM(withId: parameters.id) let vm = try await findVM(withId: parameters.id)
if let config = await vm.config as? UTMQemuConfiguration { 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 { private func _getPackageSize(parameters: M.GetPackageSize.Request) async throws -> M.GetPackageSize.Reply {
return .init() 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 { private func _getPackageDataFile(parameters: M.GetPackageDataFile.Request) async throws -> M.GetPackageDataFile.Reply {