mirror of
https://github.com/apple/container.git
synced 2026-07-12 12:37:02 +00:00
bfc5ca9222
Closes #63.
164 lines
6.6 KiB
Swift
164 lines
6.6 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025 Apple Inc. and the container project authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import ContainerXPC
|
|
import ContainerizationExtras
|
|
import Foundation
|
|
import TerminalProgress
|
|
|
|
/// A client that can be used to receive progress updates from a service.
|
|
public actor ProgressUpdateClient {
|
|
private var endpointConnection: xpc_connection_t?
|
|
private var endpoint: xpc_endpoint_t?
|
|
|
|
/// Creates a new client for receiving progress updates from a service.
|
|
/// - Parameters:
|
|
/// - progressUpdate: The handler to invoke when progress updates are received.
|
|
/// - request: The XPC message to send the endpoint to connect to.
|
|
public init(for progressUpdate: @escaping ProgressUpdateHandler, request: XPCMessage) async {
|
|
createEndpoint(for: progressUpdate)
|
|
setEndpoint(to: request)
|
|
}
|
|
|
|
/// Performs a connection setup for receiving progress updates.
|
|
/// - Parameter progressUpdate: The handler to invoke when progress updates are received.
|
|
private func createEndpoint(for progressUpdate: @escaping ProgressUpdateHandler) {
|
|
let endpointConnection = xpc_connection_create(nil, nil)
|
|
// Access to `reversedConnection` is protected by a lock
|
|
nonisolated(unsafe) var reversedConnection: xpc_connection_t?
|
|
let reversedConnectionLock = NSLock()
|
|
xpc_connection_set_event_handler(endpointConnection) { connectionMessage in
|
|
reversedConnectionLock.withLock {
|
|
switch xpc_get_type(connectionMessage) {
|
|
case XPC_TYPE_CONNECTION:
|
|
reversedConnection = connectionMessage
|
|
xpc_connection_set_event_handler(connectionMessage) { updateMessage in
|
|
Self.handleProgressUpdate(updateMessage, progressUpdate: progressUpdate)
|
|
}
|
|
xpc_connection_activate(connectionMessage)
|
|
case XPC_TYPE_ERROR:
|
|
if let reversedConnectionUnwrapped = reversedConnection {
|
|
xpc_connection_cancel(reversedConnectionUnwrapped)
|
|
reversedConnection = nil
|
|
}
|
|
default:
|
|
fatalError("unhandled xpc object type: \(xpc_get_type(connectionMessage))")
|
|
}
|
|
}
|
|
}
|
|
xpc_connection_activate(endpointConnection)
|
|
|
|
self.endpointConnection = endpointConnection
|
|
self.endpoint = xpc_endpoint_create(endpointConnection)
|
|
}
|
|
|
|
/// Performs a setup of the progress update endpoint.
|
|
/// - Parameter request: The XPC message containing the endpoint to use.
|
|
private func setEndpoint(to request: XPCMessage) {
|
|
guard let endpoint else {
|
|
return
|
|
}
|
|
request.set(key: .progressUpdateEndpoint, value: endpoint)
|
|
}
|
|
|
|
/// Performs cleanup of the created connection.
|
|
public func finish() {
|
|
if let endpointConnection {
|
|
xpc_connection_cancel(endpointConnection)
|
|
self.endpointConnection = nil
|
|
}
|
|
}
|
|
|
|
private static func handleProgressUpdate(_ message: xpc_object_t, progressUpdate: @escaping ProgressUpdateHandler) {
|
|
switch xpc_get_type(message) {
|
|
case XPC_TYPE_DICTIONARY:
|
|
let message = XPCMessage(object: message)
|
|
handleProgressUpdate(message, progressUpdate: progressUpdate)
|
|
case XPC_TYPE_ERROR:
|
|
break
|
|
default:
|
|
fatalError("unhandled xpc object type: \(xpc_get_type(message))")
|
|
break
|
|
}
|
|
}
|
|
|
|
private static func handleProgressUpdate(_ message: XPCMessage, progressUpdate: @escaping ProgressUpdateHandler) {
|
|
var events = [ProgressUpdateEvent]()
|
|
|
|
if let description = message.string(key: .progressUpdateSetDescription) {
|
|
events.append(.setDescription(description))
|
|
}
|
|
if let subDescription = message.string(key: .progressUpdateSetSubDescription) {
|
|
events.append(.setSubDescription(subDescription))
|
|
}
|
|
if let itemsName = message.string(key: .progressUpdateSetItemsName) {
|
|
events.append(.setItemsName(itemsName))
|
|
}
|
|
var tasks = message.int(key: .progressUpdateAddTasks)
|
|
if tasks != 0 {
|
|
events.append(.addTasks(tasks))
|
|
}
|
|
tasks = message.int(key: .progressUpdateSetTasks)
|
|
if tasks != 0 {
|
|
events.append(.setTasks(tasks))
|
|
}
|
|
var totalTasks = message.int(key: .progressUpdateAddTotalTasks)
|
|
if totalTasks != 0 {
|
|
events.append(.addTotalTasks(totalTasks))
|
|
}
|
|
totalTasks = message.int(key: .progressUpdateSetTotalTasks)
|
|
if totalTasks != 0 {
|
|
events.append(.setTotalTasks(totalTasks))
|
|
}
|
|
var items = message.int(key: .progressUpdateAddItems)
|
|
if items != 0 {
|
|
events.append(.addItems(items))
|
|
}
|
|
items = message.int(key: .progressUpdateSetItems)
|
|
if items != 0 {
|
|
events.append(.setItems(items))
|
|
}
|
|
var totalItems = message.int(key: .progressUpdateAddTotalItems)
|
|
if totalItems != 0 {
|
|
events.append(.addTotalItems(totalItems))
|
|
}
|
|
totalItems = message.int(key: .progressUpdateSetTotalItems)
|
|
if totalItems != 0 {
|
|
events.append(.setTotalItems(totalItems))
|
|
}
|
|
var size = message.int64(key: .progressUpdateAddSize)
|
|
if size != 0 {
|
|
events.append(.addSize(size))
|
|
}
|
|
size = message.int64(key: .progressUpdateSetSize)
|
|
if size != 0 {
|
|
events.append(.setSize(size))
|
|
}
|
|
var totalSize = message.int64(key: .progressUpdateAddTotalSize)
|
|
if totalSize != 0 {
|
|
events.append(.addTotalSize(totalSize))
|
|
}
|
|
totalSize = message.int64(key: .progressUpdateSetTotalSize)
|
|
if totalSize != 0 {
|
|
events.append(.setTotalSize(totalSize))
|
|
}
|
|
|
|
Task {
|
|
await progressUpdate(events)
|
|
}
|
|
}
|
|
}
|