use std::collections::BTreeMap;
use std::sync::{Mutex, MutexGuard};
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
use wyrd_spec::registry::RelativeArtifactPath;
pub(crate) struct DownloadProgressDisplay {
progress: MultiProgress,
bars: Mutex<BTreeMap<RelativeArtifactPath, ProgressBar>>,
}
impl DownloadProgressDisplay {
#[cfg(feature = "internal")]
#[must_use]
pub(crate) fn stdout() -> Self {
Self::new(ProgressDrawTarget::stdout())
}
#[must_use]
pub(crate) fn hidden() -> Self {
Self::new(ProgressDrawTarget::hidden())
}
pub(crate) fn start(&self, path: &RelativeArtifactPath, total_bytes: Option<u64>) {
let bar = self.progress.add(match total_bytes {
Some(total_bytes) => ProgressBar::new(total_bytes),
None => ProgressBar::new_spinner(),
});
bar.set_style(
ProgressStyle::with_template("{msg} {wide_bar} {bytes}/{total_bytes}")
.expect("progress template is a static invariant"),
);
bar.set_message(path.as_str().to_owned());
self.bars_lock().insert(path.clone(), bar);
}
pub(crate) fn set_position(
&self,
path: &RelativeArtifactPath,
downloaded_bytes: u64,
total_bytes: Option<u64>,
) {
if let Some(bar) = self.bars_lock().get(path) {
if let Some(total_bytes) = total_bytes {
bar.set_length(total_bytes);
}
bar.set_position(downloaded_bytes);
}
}
pub(crate) fn finish(&self, path: &RelativeArtifactPath, succeeded: bool) {
if let Some(bar) = self.bars_lock().remove(path) {
if !succeeded {
bar.set_message(format!("{} failed", path.as_str()));
}
bar.finish_and_clear();
self.progress.remove(&bar);
}
}
pub(crate) fn clear(&self) {
let bars = std::mem::take(&mut *self.bars_lock());
for (_, bar) in bars {
bar.finish_and_clear();
self.progress.remove(&bar);
}
let _ = self.progress.clear();
}
fn bars_lock(&self) -> MutexGuard<'_, BTreeMap<RelativeArtifactPath, ProgressBar>> {
self.bars
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn new(draw_target: ProgressDrawTarget) -> Self {
Self {
progress: MultiProgress::with_draw_target(draw_target),
bars: Mutex::new(BTreeMap::new()),
}
}
}
#[cfg(test)]
mod tests {
use indicatif::ProgressDrawTarget;
use wyrd_spec::registry::RelativeArtifactPath;
use super::DownloadProgressDisplay;
#[test]
fn tracks_multiple_artifacts_and_clears_after_success() {
let display = DownloadProgressDisplay::new(ProgressDrawTarget::hidden());
let first = RelativeArtifactPath::new("first.bin").expect("test path is valid");
let second = RelativeArtifactPath::new("nested/second.bin").expect("test path is valid");
display.start(&first, Some(3));
display.start(&second, Some(5));
display.set_position(&first, 3, Some(3));
display.set_position(&second, 5, Some(5));
assert_eq!(display.bars_lock()[&first].position(), 3);
assert_eq!(display.bars_lock()[&second].position(), 5);
display.finish(&first, true);
display.finish(&second, true);
display.clear();
assert!(display.bars_lock().is_empty());
}
}