use crate::core::events::{EventEmitter, TransferEvent};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::sync::{Arc, Mutex};
use std::time::Duration;
pub struct CliEventEmitter {
mp: Arc<MultiProgress>,
pb: Mutex<Option<ProgressBar>>,
prefix: String,
}
impl CliEventEmitter {
pub fn new(prefix: &str) -> Self {
Self {
mp: Arc::new(MultiProgress::new()),
pb: Mutex::new(None),
prefix: prefix.to_string(),
}
}
fn make_progress_style() -> ProgressStyle {
#[allow(clippy::literal_string_with_formatting_args)]
let template = "{prefix}{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} {binary_bytes_per_sec}";
ProgressStyle::with_template(template).map_or_else(
|_| ProgressStyle::default_bar(),
|style| style.progress_chars("#>-"),
)
}
}
impl EventEmitter for CliEventEmitter {
fn emit(&self, event: &TransferEvent) {
match event {
TransferEvent::Started { .. } => {
let mut guard = self.pb.lock().unwrap_or_else(|error| error.into_inner());
if guard.is_none() {
let pb = self.mp.add(ProgressBar::new(0));
pb.set_style(Self::make_progress_style());
pb.enable_steady_tick(Duration::from_millis(250));
pb.set_prefix(format!("{} ", self.prefix));
*guard = Some(pb);
}
}
TransferEvent::Progress {
processed,
total,
speed,
..
} => {
let mut guard = self.pb.lock().unwrap_or_else(|error| error.into_inner());
if guard.is_none() {
let pb = self.mp.add(ProgressBar::new(*total));
pb.set_style(Self::make_progress_style());
pb.enable_steady_tick(Duration::from_millis(250));
pb.set_prefix(format!("{} ", self.prefix));
pb.set_length(*total);
pb.set_position(*processed);
*guard = Some(pb);
return;
}
if let Some(pb) = guard.as_ref() {
pb.set_length(*total);
pb.set_position(*processed);
pb.set_message(human_bytes_per_sec(*speed));
}
}
TransferEvent::Completed { .. } => {
let value = self
.pb
.lock()
.unwrap_or_else(|error| error.into_inner())
.take();
if let Some(pb) = value {
pb.finish_and_clear();
}
}
TransferEvent::Failed { message, .. } => {
let value = self
.pb
.lock()
.unwrap_or_else(|error| error.into_inner())
.take();
if let Some(pb) = value {
pb.abandon();
}
eprintln!("Transfer failed: {message}");
}
TransferEvent::FileNames { .. } => {
}
}
}
}
fn human_bytes_per_sec(speed: f64) -> String {
if speed <= 0.0 {
return "0 B/s".to_string();
}
let units = ["B/s", "KB/s", "MB/s", "GB/s"];
let mut val = speed;
let mut idx = 0;
while val >= 1024.0 && idx + 1 < units.len() {
val /= 1024.0;
idx += 1;
}
format!("{:.1} {}", val, units[idx])
}
#[cfg(test)]
mod tests {
use super::human_bytes_per_sec;
#[test]
fn human_bytes_per_sec_formats_zero() {
assert_eq!(human_bytes_per_sec(0.0), "0 B/s");
}
#[test]
fn human_bytes_per_sec_formats_kilobytes_once() {
assert_eq!(human_bytes_per_sec(2048.0), "2.0 KB/s");
}
}