use std::future::Future;
use tokio::sync::mpsc::UnboundedSender;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputStream {
Stdout,
Stderr,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UpgradeEvent {
TargetStarted { target: String },
TargetFinished { target: String, success: bool },
CommandStarted { program: String, args: Vec<String> },
Output { stream: OutputStream, text: String },
CommandFinished { success: bool, code: i32 },
Message { text: String },
HandoffStarted { label: String },
HandoffFinished { success: bool },
Finished { success: bool },
}
#[derive(Clone, Default)]
pub struct UpgradeSink {
sender: Option<UnboundedSender<UpgradeEvent>>,
}
impl UpgradeSink {
pub fn new(sender: UnboundedSender<UpgradeEvent>) -> Self {
Self {
sender: Some(sender),
}
}
fn emit(&self, event: UpgradeEvent) -> bool {
self.sender
.as_ref()
.is_some_and(|sender| sender.send(event).is_ok())
}
}
tokio::task_local! {
static ACTIVE_UPGRADE_SINK: UpgradeSink;
}
pub async fn with_upgrade_sink<F, T>(sender: Option<UnboundedSender<UpgradeEvent>>, future: F) -> T
where
F: Future<Output = T>,
{
let sink = sender.map(UpgradeSink::new).unwrap_or_default();
ACTIVE_UPGRADE_SINK.scope(sink, future).await
}
tokio::task_local! {
static TUI_ACTIVE: ();
}
pub async fn with_tui_active<F, T>(future: F) -> T
where
F: Future<Output = T>,
{
TUI_ACTIVE.scope((), future).await
}
pub fn tui_active() -> bool {
TUI_ACTIVE.try_with(|_| ()).is_ok()
}
pub fn upgrade_events_enabled() -> bool {
ACTIVE_UPGRADE_SINK
.try_with(|sink| sink.sender.is_some())
.unwrap_or(false)
}
pub fn emit_upgrade_event(event: UpgradeEvent) -> bool {
ACTIVE_UPGRADE_SINK
.try_with(|sink| sink.emit(event))
.unwrap_or(false)
}
pub fn log_upgrade_message(text: impl Into<String>) {
let text = text.into();
if !emit_upgrade_event(UpgradeEvent::Message { text: text.clone() }) {
println!("{text}");
}
}