use wsb::prelude::*;
use crate::web::ops::OpsEvent;
use crate::web::state::WebState;
use axum::{
extract::State,
response::sse::{Event, KeepAlive, Sse},
};
use futures::stream::Stream;
use std::convert::Infallible;
use tokio::sync::broadcast::error::RecvError;
use super::workspace::WorkspaceChangedEvent;
#[derive(Serialize)]
struct LogHistory<'a> {
lines: &'a [wsb::logging::LogLine],
}
pub async fn events_stream(
State(state): State<WebState>,
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
let mut git_rx = state.git_status_tx.subscribe();
let mut file_rx = state.file_changed_tx.subscribe();
let mut ws_changed_rx = state.workspace_changed_tx.subscribe();
let (log_tx, log_ring) = wsb::logging::log_stream();
let mut log_rx = log_tx.subscribe();
let mut github_rx = state.github.as_ref().ok().map(|github| github.changed_tx.subscribe());
let registry = state.ops.clone();
let mut ops_rx = registry.subscribe();
let ops_snapshot = state.ops.snapshot().await;
let log_history = log_ring.snapshot().await;
let stream = async_stream::stream! {
yield Ok(Event::default().event("connected").data("{}"));
let initial_ops = OpsEvent::Snapshot { ops: ops_snapshot };
if let Ok(json) = serde_json::to_string(&initial_ops) {
yield Ok(Event::default().event("ops").data(json));
}
let history = LogHistory { lines: &log_history };
if let Ok(json) = serde_json::to_string(&history) {
yield Ok(Event::default().event("history").data(json));
}
loop {
tokio::select! {
git_result = git_rx.recv() => {
match git_result {
Ok(repo) => {
let changed = WorkspaceChangedEvent::repo_status(&repo.path);
if let Ok(json) = serde_json::to_string(&changed) {
yield Ok(Event::default().event("changed").data(json));
}
}
Err(RecvError::Lagged(_)) => {
let changed = WorkspaceChangedEvent::everything();
if let Ok(json) = serde_json::to_string(&changed) {
yield Ok(Event::default().event("changed").data(json));
}
}
Err(RecvError::Closed) => break,
}
}
ws_result = ws_changed_rx.recv() => {
match ws_result {
Ok(changed) => {
if let Ok(json) = serde_json::to_string(&changed) {
yield Ok(Event::default().event("changed").data(json));
}
}
Err(RecvError::Lagged(_)) => {
let changed = WorkspaceChangedEvent::everything();
if let Ok(json) = serde_json::to_string(&changed) {
yield Ok(Event::default().event("changed").data(json));
}
}
Err(RecvError::Closed) => break,
}
}
github_result = async {
match github_rx.as_mut() {
Some(rx) => rx.recv().await,
None => std::future::pending().await,
}
} => {
match github_result {
Ok(changed) => {
if let Ok(json) = serde_json::to_string(&changed) {
yield Ok(Event::default().event("github").data(json));
}
}
Err(RecvError::Lagged(_)) => {
let changed = crate::web::github::GitHubChanged { scopes: Vec::new() };
if let Ok(json) = serde_json::to_string(&changed) {
yield Ok(Event::default().event("github").data(json));
}
}
Err(RecvError::Closed) => break,
}
}
file_result = file_rx.recv() => {
match file_result {
Ok(event) => {
if let Ok(json) = serde_json::to_string(&event) {
yield Ok(Event::default().event("file_changed").data(json));
}
}
Err(RecvError::Lagged(_)) => {
let event = crate::web::state::FileChangedEvent {
path: String::new(),
mtime_secs: None,
};
if let Ok(json) = serde_json::to_string(&event) {
yield Ok(Event::default().event("file_changed").data(json));
}
}
Err(RecvError::Closed) => break,
}
}
ops_result = ops_rx.recv() => {
match ops_result {
Ok(event) => {
if let Ok(json) = serde_json::to_string(&event) {
yield Ok(Event::default().event("ops").data(json));
}
}
Err(RecvError::Lagged(n)) => {
tracing::warn!("events SSE client lagged by {n} ops events, re-sending snapshot");
let recovery = OpsEvent::Snapshot { ops: registry.snapshot().await };
if let Ok(json) = serde_json::to_string(&recovery) {
yield Ok(Event::default().event("ops").data(json));
}
}
Err(RecvError::Closed) => break,
}
}
log_result = log_rx.recv() => {
match log_result {
Ok(line) => {
match serde_json::to_string(&line) {
Ok(json) => yield Ok(Event::default().event("log").data(json)),
Err(e) => wsb::errln!("[wsb] log SSE serialize error: {e}"),
}
}
Err(RecvError::Lagged(_)) => {}
Err(RecvError::Closed) => break,
}
}
}
}
};
Sse::new(stream).keep_alive(KeepAlive::default())
}