use std::{
io::Write,
sync::mpsc::{self, Receiver, RecvTimeoutError, Sender},
time::Duration,
};
use crossterm::{
cursor, queue,
style::Print,
terminal::{Clear, ClearType},
};
use xacli_core::Context;
use crate::Result;
pub struct ProgressBar {
total: u64,
message: String,
rx: Receiver<ProgressEvent>,
}
pub struct ProgressHandle {
tx: Sender<ProgressEvent>,
}
pub enum ProgressEvent {
Increment(u64),
SetProgress(u64),
SetMessage(String),
Finish,
}
impl ProgressBar {
pub fn new(total: u64, message: impl Into<String>) -> (Self, ProgressHandle) {
let (tx, rx) = mpsc::channel();
let bar = Self {
total,
message: message.into(),
rx,
};
let handle = ProgressHandle { tx };
(bar, handle)
}
pub fn run(mut self, ctx: &mut dyn Context) -> Result<()> {
let mut current = 0u64;
let stdout = &mut ctx.stdout();
self.render(stdout, current)?;
loop {
match self.rx.recv_timeout(Duration::from_millis(100)) {
Ok(ProgressEvent::Increment(n)) => {
current = (current + n).min(self.total);
self.render(stdout, current)?;
}
Ok(ProgressEvent::SetProgress(n)) => {
current = n.min(self.total);
self.render(stdout, current)?;
}
Ok(ProgressEvent::SetMessage(msg)) => {
self.message = msg;
self.render(stdout, current)?;
}
Ok(ProgressEvent::Finish) => {
current = self.total;
self.render(stdout, current)?;
println!();
break;
}
Err(RecvTimeoutError::Timeout) => {
self.render(stdout, current)?;
}
Err(RecvTimeoutError::Disconnected) => {
println!();
break;
}
}
}
Ok(())
}
fn render(&self, stdout: &mut impl Write, current: u64) -> Result<()> {
let percent = if self.total > 0 {
(current as f64 / self.total as f64) * 100.0
} else {
0.0
};
let bar_width = 40;
let filled = if self.total > 0 {
((current as f64 / self.total as f64) * bar_width as f64) as usize
} else {
0
};
let bar = format!("[{}{}]", "=".repeat(filled), " ".repeat(bar_width - filled));
queue!(
stdout,
cursor::MoveToColumn(0),
Clear(ClearType::CurrentLine),
Print(&self.message),
Print(" "),
Print(&bar),
Print(format!(" {}/{} ({:.1}%)", current, self.total, percent)),
)?;
stdout.flush()?;
Ok(())
}
}
impl ProgressHandle {
pub fn inc(&self, n: u64) {
let _ = self.tx.send(ProgressEvent::Increment(n));
}
pub fn set(&self, n: u64) {
let _ = self.tx.send(ProgressEvent::SetProgress(n));
}
pub fn set_message(&self, msg: impl Into<String>) {
let _ = self.tx.send(ProgressEvent::SetMessage(msg.into()));
}
pub fn finish(&self) {
let _ = self.tx.send(ProgressEvent::Finish);
}
}