use std::collections::HashSet;
use anyhow::Context as _;
use argonauts_core::RunId;
use argonauts_core::app::AppEvent;
use argonauts_core::events::failure::run_failure_line;
use argonauts_core::events::run::RunOutcome;
use argonauts_core::events::{RunEvent, RunEventKind, RunLifecycle};
use tokio::task::JoinHandle;
use super::{RunNews, RunsState};
use crate::controller::Ctx;
use crate::controller::attach::{self, Attached, Stage};
pub(super) struct Alongside {
run: RunId,
events: Vec<RunEvent>,
finished: bool,
stream: JoinHandle<()>,
}
impl Drop for Alongside {
fn drop(&mut self) {
self.stream.abort();
}
}
#[derive(Default)]
pub(super) struct Streaming {
runs: Vec<Alongside>,
ended_here: HashSet<RunId>,
}
pub(super) fn keeps_working(old: &Attached) -> bool {
old.replay_done && !old.ended && old.stage == Stage::Going
}
pub(super) async fn follow(
ctx: &Ctx,
runs: &mut RunsState,
old: &mut Attached,
) -> anyhow::Result<()> {
let next = old.events.last().map_or(0, |event| event.seq + 1);
let stream = ctx
.services
.conductor
.subscribe(&old.run, next)
.await
.with_context(|| format!("following {} off screen", old.run))?;
let live = *stream.live.borrow();
runs.streaming.runs.push(Alongside {
run: old.run.clone(),
events: std::mem::take(&mut old.events),
finished: false,
stream: attach::forward(old.run.clone(), stream, live, ctx.internal.clone()),
});
Ok(())
}
pub(super) fn on_screen(runs: &mut RunsState, run: &RunId) {
runs.streaming.runs.retain(|side| &side.run != run);
runs.streaming.ended_here.remove(run);
}
pub(super) fn ended_here(runs: &RunsState, run: &RunId) -> bool {
runs.streaming.ended_here.contains(run)
|| runs
.streaming
.runs
.iter()
.any(|side| &side.run == run && side.finished)
}
pub(super) async fn on_event(ctx: &mut Ctx, runs: &mut RunsState, run: &RunId, event: RunEvent) {
let Some(side) = runs.streaming.runs.iter_mut().find(|side| &side.run == run) else {
return;
};
if side.events.last().is_some_and(|last| event.seq <= last.seq) {
return;
}
ctx.send(AppEvent::Run {
event: Box::new(crate::views::required::for_screen(&event, &ctx.env)),
});
let outcome = match &event.kind {
RunEventKind::Run(RunLifecycle::Finished { outcome, detail }) => {
let line = run_failure_line(detail.as_deref());
runs.news.push((
run.clone(),
RunNews::Ended {
outcome: *outcome,
line,
},
));
Some(*outcome)
}
_ => None,
};
side.events.push(event);
let Some(outcome) = outcome else {
return;
};
side.finished = true;
let events = std::mem::take(&mut side.events);
super::send_recap(ctx, run, &events, outcome).await;
super::notify_finished(ctx, runs, run, outcome, None);
super::refresh_background(ctx);
}
pub(super) fn on_ended(runs: &mut RunsState, run: &RunId) {
let streaming = &mut runs.streaming;
let Some(at) = streaming.runs.iter().position(|side| &side.run == run) else {
return;
};
let side = streaming.runs.remove(at);
if side.finished {
streaming.ended_here.insert(side.run.clone());
} else {
let line = "its run host exited before waiting".to_owned();
let outcome = RunOutcome::Failed;
runs.news
.push((side.run.clone(), RunNews::Ended { outcome, line }));
}
}
#[cfg(test)]
pub(super) fn streams(runs: &RunsState, run: &RunId) -> bool {
runs.streaming.runs.iter().any(|side| &side.run == run)
}