use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use crate::sync::format_bytes;
pub struct ProgressReporter {
multi: MultiProgress,
}
pub struct FileProgressBar {
bar: ProgressBar,
}
impl ProgressReporter {
pub fn new() -> Self {
Self {
multi: MultiProgress::new(),
}
}
pub fn add_file_bar(&self, filename: &str, total_bytes: u64) -> FileProgressBar {
let pb = self.multi.add(ProgressBar::new(total_bytes));
let style = ProgressStyle::with_template(
"{spinner:.green} {msg:40!} [{bar:30.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec})",
)
.unwrap_or_else(|_| ProgressStyle::default_bar());
pb.set_style(style);
let msg: String = if filename.chars().count() > 40 {
filename.chars().take(40).collect()
} else {
filename.to_string()
};
pb.set_message(msg);
FileProgressBar { bar: pb }
}
}
impl Default for ProgressReporter {
fn default() -> Self {
Self::new()
}
}
impl FileProgressBar {
pub fn update(&self, bytes_transferred: u64) {
self.bar.set_position(bytes_transferred);
}
pub fn finish(&self, filename: &str, total_bytes: u64) {
let size_str = format_bytes(total_bytes);
self.bar
.finish_with_message(format!("✓ {:<40} ({})", filename, size_str));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_progress_bar_finish_does_not_panic() {
let reporter = ProgressReporter::new();
let bar = reporter.add_file_bar("test-file.wav", 1_048_576);
bar.update(512_000);
bar.update(1_048_576);
bar.finish("test-file.wav", 1_048_576);
}
#[test]
fn add_file_bar_truncates_long_filename() {
let reporter = ProgressReporter::new();
let long_name = "a".repeat(80);
let bar = reporter.add_file_bar(&long_name, 1024);
bar.finish(&long_name, 1024);
}
#[test]
fn add_file_bar_truncates_multibyte_utf8_filename() {
let reporter = ProgressReporter::new();
let long_name = "日".repeat(45);
assert!(long_name.len() > 40, "sanity: byte length exceeds 40");
assert!(
long_name.chars().count() > 40,
"sanity: char count exceeds 40"
);
let bar = reporter.add_file_bar(&long_name, 2048);
bar.update(2048);
bar.finish(&long_name, 2048);
}
}