Phase 0/1: SwiftUI app (XcodeGen), PlumTVCore framework, player MVP. - PlayerTarget protocol + VLCTarget (HTTP) and BlackTVTarget (ssh black-tv) - config-driven hosts (~/.config/plumtv/hosts.json), seeded plum-vlc + black - reliability: SSH ControlMaster (5s->~1s/poll), endpoint pinning (LAN->overlay), single-flight polling, keep-last-known on transient failure, debounced volume - Hosts pane shows live connection state; Player has target picker + transport - unit tests for status decoding Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 lines
1.2 KiB
Swift
33 lines
1.2 KiB
Swift
import Foundation
|
|
|
|
public struct ProcessResult: Sendable {
|
|
public let status: Int32
|
|
public let stdout: String
|
|
public let stderr: String
|
|
public var ok: Bool { status == 0 }
|
|
}
|
|
|
|
/// Minimal blocking command runner. Outputs here are tiny (a line of JSON), so
|
|
/// the read-to-EOF pattern is safe. Always call off the main thread.
|
|
public enum ProcessRunner {
|
|
public static func run(_ launchPath: String, _ args: [String]) -> ProcessResult {
|
|
let p = Process()
|
|
p.executableURL = URL(fileURLWithPath: launchPath)
|
|
p.arguments = args
|
|
let out = Pipe()
|
|
let err = Pipe()
|
|
p.standardOutput = out
|
|
p.standardError = err
|
|
do {
|
|
try p.run()
|
|
} catch {
|
|
return ProcessResult(status: -1, stdout: "", stderr: "spawn failed: \(error.localizedDescription)")
|
|
}
|
|
let o = out.fileHandleForReading.readDataToEndOfFile()
|
|
let e = err.fileHandleForReading.readDataToEndOfFile()
|
|
p.waitUntilExit()
|
|
return ProcessResult(status: p.terminationStatus,
|
|
stdout: String(decoding: o, as: UTF8.self),
|
|
stderr: String(decoding: e, as: UTF8.self))
|
|
}
|
|
}
|