feat(qbzd): live event stream — GET /api/events (SSE) + qbzd watch
Add a Server-Sent Events feed so a client (a plasmoid, a bar applet, a script) reacts to playback/queue/library changes without polling — the last CONSOLE surface. Server (api/sse.rs): `GET /api/events` streams one SSE frame per CoreEvent (`event: <type>` + the tagged `data:` JSON). Because the control-plane serve loop is single-threaded, an open stream would starve it, so the route is moved onto its OWN thread (Request is Send) after the same origin/token gate every route runs; the bus is drained with `blocking_recv()` there. Bulky/internal events (search payloads, loading/download/navigation hints, diagnostics) are filtered out; everything else — playback, queue, volume, auth, favorites, playlists, errors, device changes — is emitted. ApiState gains the bus sender. Client (`qbzd watch`): a bespoke reqwest client with NO read timeout (only the connect is bounded); default output is newline-delimited JSON (one data payload per line, `qbzd watch | jq`), `--raw` passes the SSE frames through verbatim. P1_ROUTES → 26 with the route-table test updated (caller: `qbzd watch`). 283 tests green. cc
This commit is contained in:
7 files changed
+300
-3
No files matched your search
@@ -153,6 +153,7 @@ pub async fn run(roots: ProfileRoots, cfg: QbzdConfig, warns: Vec<String>) -> Re
|
||||
crate::api::ApiState {
|
||||
runtime: booted.runtime.clone(),
|
||||
shared: booted.shared.clone(),
|
||||
bus: booted.bus.clone(),
|
||||
roots: roots.clone(),
|
||||
token: cfg.server.token.filter(|t| !t.trim().is_empty()),
|
||||
bind: bind_addr.to_string(),
|
||||
|
||||
Reference in new issue
Block a user