Surface the existing pin (keep-from-cull) and per-file delete actions as visible inline buttons on each offline cache row instead of context-menu-only: a star toggles protection from auto-cull (and restore-if-missing), a trash culls that file early. Aligns wording/icons to the star metaphor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
No EOL
4.1 KiB
Swift
99 lines
No EOL
4.1 KiB
Swift
import Foundation
|
|
|
|
/// Ensure local VLC is running with its HTTP/Lua interface — the app can't poll or
|
|
/// control VLC without it. Mirrors governor's `launchVlcHttp` / `vlcHttpConfig`.
|
|
/// All blocking process/defaults work runs off the main actor.
|
|
public enum VLCLauncher {
|
|
public struct HttpConfig: Sendable, Equatable {
|
|
public let host: String
|
|
public let port: Int
|
|
public let password: String
|
|
}
|
|
|
|
private static let vlcBin = "/Applications/VLC.app/Contents/MacOS/VLC"
|
|
|
|
public static func httpConfig(host: String = "127.0.0.1", port: Int = 8080) -> HttpConfig? {
|
|
let pw = VLCConfig.password()
|
|
guard !pw.isEmpty else { return nil }
|
|
return HttpConfig(host: host, port: port, password: pw)
|
|
}
|
|
|
|
public static func isHttpReachable(_ cfg: HttpConfig) async -> Bool {
|
|
var comps = URLComponents()
|
|
comps.scheme = "http"
|
|
comps.host = cfg.host
|
|
comps.port = cfg.port
|
|
comps.path = "/requests/status.json"
|
|
guard let url = comps.url else { return false }
|
|
var req = URLRequest(url: url)
|
|
req.timeoutInterval = 3
|
|
req.setValue("Basic \(Data(":\(cfg.password)".utf8).base64EncodedString())",
|
|
forHTTPHeaderField: "Authorization")
|
|
guard let (_, resp) = try? await URLSession.shared.data(for: req),
|
|
(resp as? HTTPURLResponse)?.statusCode == 200 else { return false }
|
|
return true
|
|
}
|
|
|
|
/// Launch VLC (or relaunch when HTTP is down) and wait for the interface.
|
|
@discardableResult
|
|
public static func ensureRunning(_ cfg: HttpConfig) async -> Bool {
|
|
if await isHttpReachable(cfg) { return true }
|
|
await launchOffMain(cfg)
|
|
for _ in 0..<24 {
|
|
try? await Task.sleep(for: .milliseconds(500))
|
|
if await isHttpReachable(cfg) {
|
|
// Give the freshly (re)launched VLC a moment to fully initialize the
|
|
// HTTP interface and be ready to accept play commands (in_play etc).
|
|
try? await Task.sleep(for: .milliseconds(300))
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// Quit/relaunch + defaults — never call from @MainActor (blocks the UI).
|
|
private static func launchOffMain(_ cfg: HttpConfig) async {
|
|
await Task.detached(priority: .utility) {
|
|
if isProcessRunning() { quitSync() }
|
|
persistDefaults(cfg)
|
|
spawnWithHttp(cfg)
|
|
}.value
|
|
}
|
|
|
|
private static func isProcessRunning() -> Bool {
|
|
ProcessRunner.run("/usr/bin/pgrep", ["-x", "VLC"]).ok
|
|
}
|
|
|
|
private static func quitSync() {
|
|
_ = ProcessRunner.runShell(
|
|
"osascript -e 'tell application \"VLC\" to quit' 2>/dev/null || true", timeout: 6)
|
|
for _ in 0..<10 where isProcessRunning() {
|
|
Thread.sleep(forTimeInterval: 0.2)
|
|
}
|
|
}
|
|
|
|
private static func persistDefaults(_ cfg: HttpConfig) {
|
|
_ = ProcessRunner.run("/usr/bin/defaults", ["write", "org.videolan.vlc", "extraintf", "-string", "http"])
|
|
_ = ProcessRunner.run("/usr/bin/defaults", ["write", "org.videolan.vlc", "http-host", "-string", cfg.host])
|
|
_ = ProcessRunner.run("/usr/bin/defaults", ["write", "org.videolan.vlc", "http-port", "-int", String(cfg.port)])
|
|
_ = ProcessRunner.run("/usr/bin/defaults", ["write", "org.videolan.vlc", "http-password", "-string", cfg.password])
|
|
}
|
|
|
|
private static func spawnWithHttp(_ cfg: HttpConfig) {
|
|
guard FileManager.default.fileExists(atPath: vlcBin) else {
|
|
Log.error("VLC not installed at \(vlcBin)")
|
|
return
|
|
}
|
|
let args = [
|
|
"-a", "VLC", "--args",
|
|
"--extraintf", "http",
|
|
"--http-host", cfg.host,
|
|
"--http-port", String(cfg.port),
|
|
"--http-password", cfg.password,
|
|
"--no-video-title-show",
|
|
]
|
|
let r = ProcessRunner.run("/usr/bin/open", args)
|
|
if r.ok { Log.info("launched VLC with HTTP on \(cfg.host):\(cfg.port)") }
|
|
else { Log.error("failed to launch VLC: \(r.stderr.trimmingCharacters(in: .whitespacesAndNewlines))") }
|
|
}
|
|
} |