67 lines
2.0 KiB
Swift
67 lines
2.0 KiB
Swift
import Foundation
|
|
|
|
struct LaunchControlCommandResult: Sendable {
|
|
let exitCode: Int32
|
|
let standardOutput: String
|
|
let standardError: String
|
|
|
|
var combinedOutput: String {
|
|
[standardOutput, standardError]
|
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
.filter { !$0.isEmpty }
|
|
.joined(separator: "\n")
|
|
}
|
|
}
|
|
|
|
protocol LaunchControlCommandRunning: Sendable {
|
|
func runLaunchctl(arguments: [String]) throws -> LaunchControlCommandResult
|
|
}
|
|
|
|
struct ProcessLaunchControlCommandRunner: LaunchControlCommandRunning {
|
|
private let timeout: TimeInterval
|
|
|
|
init(timeout: TimeInterval = 2) {
|
|
self.timeout = timeout
|
|
}
|
|
|
|
func runLaunchctl(arguments: [String]) throws -> LaunchControlCommandResult {
|
|
let process = Process()
|
|
process.executableURL = URL(fileURLWithPath: "/bin/launchctl")
|
|
process.arguments = arguments
|
|
|
|
let outputPipe = Pipe()
|
|
let errorPipe = Pipe()
|
|
let completion = DispatchSemaphore(value: 0)
|
|
process.standardOutput = outputPipe
|
|
process.standardError = errorPipe
|
|
process.terminationHandler = { _ in
|
|
completion.signal()
|
|
}
|
|
|
|
try process.run()
|
|
let deadline = DispatchTime.now() + timeout
|
|
if completion.wait(timeout: deadline) == .timedOut {
|
|
process.terminate()
|
|
_ = completion.wait(timeout: .now() + 0.5)
|
|
if process.isRunning {
|
|
process.interrupt()
|
|
}
|
|
}
|
|
|
|
let output = String(
|
|
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
|
|
encoding: .utf8
|
|
) ?? ""
|
|
let error = String(
|
|
data: errorPipe.fileHandleForReading.readDataToEndOfFile(),
|
|
encoding: .utf8
|
|
) ?? ""
|
|
|
|
return LaunchControlCommandResult(
|
|
exitCode: process.terminationStatus,
|
|
standardOutput: output,
|
|
standardError: error
|
|
)
|
|
}
|
|
}
|