use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use indicatif::{ProgressBar, ProgressStyle};
pub trait EventSink<E>: Send + Sync {
fn on_event(&self, event: &E);
}
#[derive(Clone, Debug, Default)]
pub struct ProgressUpdate {
pub position: u64,
pub length: Option<u64>,
pub message: Option<String>,
pub finished: bool,
}
pub struct FanOut<E> {
sinks: Vec<Arc<dyn EventSink<E>>>,
}
impl<E> FanOut<E> {
pub fn new() -> Self {
Self { sinks: Vec::new() }
}
pub fn push(&mut self, sink: Arc<dyn EventSink<E>>) {
self.sinks.push(sink);
}
pub fn builder() -> FanOutBuilder<E> {
FanOutBuilder { sinks: Vec::new() }
}
pub fn len(&self) -> usize {
self.sinks.len()
}
pub fn is_empty(&self) -> bool {
self.sinks.is_empty()
}
}
impl<E> Default for FanOut<E> {
fn default() -> Self {
Self::new()
}
}
impl<E: Send + Sync> EventSink<E> for FanOut<E> {
fn on_event(&self, event: &E) {
for sink in &self.sinks {
sink.on_event(event);
}
}
}
pub struct FanOutBuilder<E> {
sinks: Vec<Arc<dyn EventSink<E>>>,
}
impl<E> FanOutBuilder<E> {
pub fn with(mut self, sink: Arc<dyn EventSink<E>>) -> Self {
self.sinks.push(sink);
self
}
pub fn build(self) -> FanOut<E> {
FanOut { sinks: self.sinks }
}
}
type RenderFn<E> = Box<dyn Fn(&E) -> Option<ProgressUpdate> + Send + Sync + 'static>;
pub struct IndicatifSink<E> {
progress_bar: Option<ProgressBar>,
render: RenderFn<E>,
last_line_at: Mutex<Instant>,
}
impl<E> IndicatifSink<E> {
pub fn new<F>(total: u64, interactive: bool, render: F) -> Self
where
F: Fn(&E) -> Option<ProgressUpdate> + Send + Sync + 'static,
{
let progress_bar = if interactive {
let pb = ProgressBar::new(total);
pb.set_style(
ProgressStyle::default_bar()
.template(
"{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} | {msg} | ETA {eta_precise}",
)
.expect("invalid indicatif progress template")
.progress_chars("#>-"),
);
Some(pb)
} else {
None
};
Self {
progress_bar,
render: Box::new(render),
last_line_at: Mutex::new(Instant::now() - Duration::from_secs(5)),
}
}
pub fn println(&self, line: &str) {
if let Some(progress_bar) = &self.progress_bar {
progress_bar.println(line);
} else {
eprintln!("{line}");
}
}
pub fn is_interactive(&self) -> bool {
self.progress_bar.is_some()
}
}
impl<E: Send + Sync> EventSink<E> for IndicatifSink<E> {
fn on_event(&self, event: &E) {
let Some(update) = (self.render)(event) else {
return;
};
if let Some(progress_bar) = &self.progress_bar {
if let Some(length) = update.length {
progress_bar.set_length(length);
}
progress_bar.set_position(update.position);
if let Some(message) = update.message.clone() {
progress_bar.set_message(message);
}
if update.finished {
let final_msg = update.message.unwrap_or_else(|| "complete".to_string());
progress_bar.finish_with_message(final_msg);
}
return;
}
let now = Instant::now();
let mut guard = self.last_line_at.lock().unwrap_or_else(|e| e.into_inner());
if !update.finished && now.duration_since(*guard) < Duration::from_secs(1) {
return;
}
*guard = now;
drop(guard);
let msg = update.message.unwrap_or_default();
match update.length {
Some(length) => {
eprintln!("[aicx] {}/{} {}", update.position, length, msg);
}
None => {
eprintln!("[aicx] {} {}", update.position, msg);
}
}
}
}
pub struct TracingSink;
impl TracingSink {
pub fn new() -> Self {
Self
}
}
impl Default for TracingSink {
fn default() -> Self {
Self::new()
}
}
impl<E: std::fmt::Debug + Send + Sync> EventSink<E> for TracingSink {
fn on_event(&self, event: &E) {
tracing::info!(?event, "aicx event");
}
}