#[derive(Debug, Clone)]
pub struct ProgressEvent {
pub filename: String,
pub bytes_downloaded: u64,
pub bytes_total: u64,
pub percent: f64,
pub files_remaining: usize,
}
#[must_use]
pub(crate) fn completed_event(filename: &str, size: u64, files_remaining: usize) -> ProgressEvent {
ProgressEvent {
filename: filename.to_owned(),
bytes_downloaded: size,
bytes_total: size,
percent: 100.0,
files_remaining,
}
}
#[must_use]
pub(crate) fn streaming_event(
filename: &str,
bytes_downloaded: u64,
bytes_total: u64,
files_remaining: usize,
) -> ProgressEvent {
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
let percent = if bytes_total > 0 {
(bytes_downloaded as f64 / bytes_total as f64) * 100.0
} else {
0.0
};
ProgressEvent {
filename: filename.to_owned(),
bytes_downloaded,
bytes_total,
percent,
files_remaining,
}
}
#[cfg(feature = "indicatif")]
pub struct IndicatifProgress {
multi: indicatif::MultiProgress,
overall: indicatif::ProgressBar,
file_bars: std::sync::Mutex<std::collections::HashMap<String, indicatif::ProgressBar>>,
completed_files: std::sync::Mutex<std::collections::HashSet<String>>,
finished: std::sync::atomic::AtomicBool,
}
#[cfg(feature = "indicatif")]
impl IndicatifProgress {
#[must_use]
pub fn new() -> Self {
let multi = indicatif::MultiProgress::new();
let overall = multi.add(indicatif::ProgressBar::new(0));
overall.set_style(
indicatif::ProgressStyle::default_bar()
.template("{msg} [{bar:40.cyan/blue}] {pos}/{len} files")
.ok()
.unwrap_or_else(indicatif::ProgressStyle::default_bar)
.progress_chars("=> "),
);
overall.set_message("Overall");
Self {
multi,
overall,
file_bars: std::sync::Mutex::new(std::collections::HashMap::new()),
completed_files: std::sync::Mutex::new(std::collections::HashSet::new()),
finished: std::sync::atomic::AtomicBool::new(false),
}
}
#[must_use]
pub fn multi(&self) -> &indicatif::MultiProgress {
&self.multi
}
pub fn set_total_files(&self, total: u64) {
self.overall.set_length(total);
}
pub fn handle(&self, event: &ProgressEvent) {
if event.percent >= 100.0 {
if let Ok(mut bars) = self.file_bars.lock() {
if let Some(bar) = bars.remove(&event.filename) {
bar.finish_and_clear();
}
}
let is_new = self
.completed_files
.lock()
.is_ok_and(|mut set| set.insert(event.filename.clone()));
if is_new {
let remaining = u64::try_from(event.files_remaining).unwrap_or(u64::MAX);
let total = self.overall.position() + 1 + remaining;
self.overall.set_length(total);
self.overall.inc(1);
}
} else if event.bytes_total > 0 {
if let Ok(mut bars) = self.file_bars.lock() {
let bar = bars.entry(event.filename.clone()).or_insert_with(|| {
let pb = self.multi.insert_before(
&self.overall,
indicatif::ProgressBar::new(event.bytes_total),
);
pb.set_style(
indicatif::ProgressStyle::default_bar()
.template(
"{msg} [{bar:40.green/dim}] {bytes}/{total_bytes} {bytes_per_sec} ({eta})",
)
.ok()
.unwrap_or_else(indicatif::ProgressStyle::default_bar)
.progress_chars("=> "),
);
pb.set_message(event.filename.clone());
pb
});
bar.set_position(event.bytes_downloaded);
}
}
}
pub fn finish(&self) {
if !self
.finished
.swap(true, std::sync::atomic::Ordering::Relaxed)
{
self.overall.finish();
}
}
}
#[cfg(feature = "indicatif")]
impl Drop for IndicatifProgress {
fn drop(&mut self) {
self.finish();
}
}
#[cfg(feature = "indicatif")]
impl Default for IndicatifProgress {
fn default() -> Self {
Self::new()
}
}