use std::io::{self, Write};
use std::time::Instant;
#[derive(Debug, Clone, Default)]
pub enum ProgressBar {
#[default]
Full,
Light,
}
impl ProgressBar {
pub(crate) fn wrap_download<W: Write>(
&self,
resource: &str,
content_length: Option<u64>,
writer: W,
) -> DownloadWrapper<W> {
let bar: Box<dyn DownloadBar> = match self {
ProgressBar::Full => Box::new(FullDownloadBar::new(content_length)),
ProgressBar::Light => Box::new(LightDownloadBar::new(resource, content_length)),
};
DownloadWrapper::new(bar, writer)
}
}
pub(crate) struct DownloadWrapper<W: Write> {
bar: Box<dyn DownloadBar>,
writer: W,
}
impl<W> DownloadWrapper<W>
where
W: Write,
{
fn new(bar: Box<dyn DownloadBar>, writer: W) -> Self {
Self { bar, writer }
}
pub(crate) fn finish(&self) {
self.bar.finish();
}
}
impl<W: Write> Write for DownloadWrapper<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.writer.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice]) -> io::Result<usize> {
self.writer.write_vectored(bufs)
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.writer.write_all(buf).map(|()| {
self.bar.tick(buf.len());
})
}
}
trait DownloadBar {
fn tick(&mut self, chunk_size: usize);
fn finish(&self);
}
pub(crate) struct FullDownloadBar {
bar: indicatif::ProgressBar,
}
impl FullDownloadBar {
pub(crate) fn new(content_length: Option<u64>) -> Self {
let bar = match content_length {
Some(length) => {
let bar = indicatif::ProgressBar::new(length);
bar.set_style(
indicatif::ProgressStyle::default_bar()
.progress_chars("=>-")
.template(
"{msg:.bold.cyan/blue} [{bar:20.cyan/blue}][{percent}%] {bytes}/{total_bytes:.bold} |{bytes_per_sec}|",
)
.expect("Failed to set progress bar template")
);
bar
}
None => {
let bar = indicatif::ProgressBar::new_spinner();
bar.set_style(
indicatif::ProgressStyle::default_bar()
.tick_strings(&[
"⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖",
"⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐",
"⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒",
"⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋",
"⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈",
"⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉",
"⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚",
"⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂",
"⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒",
"⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒⠲⠴",
"⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄",
"⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤",
"⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠",
])
.template(
"{msg:.bold.cyan/blue} [{spinner:.cyan/blue}] {bytes:.bold} |{bytes_per_sec}|",
)
.expect("Failed to set spinner progress bar template"),
);
bar
}
};
bar.set_message("Downloading");
bar.enable_steady_tick(std::time::Duration::from_millis(100));
Self { bar }
}
}
impl DownloadBar for FullDownloadBar {
fn tick(&mut self, chunk_size: usize) {
self.bar.inc(chunk_size as u64);
}
fn finish(&self) {
self.bar.set_message("Downloaded");
self.bar.set_style(
indicatif::ProgressStyle::default_bar()
.template("{msg:.green.bold} {total_bytes:.bold} in {elapsed}")
.expect("Failed to set finish progress bar template"),
);
self.bar.finish();
}
}
pub(crate) struct LightDownloadBar {
start_time: Instant,
bytes: usize,
bytes_since_last_update: usize,
}
impl LightDownloadBar {
pub(crate) fn new(resource: &str, content_length: Option<u64>) -> Self {
if let Some(size) = content_length {
eprint!(
"Downloading {resource} [{}]...",
indicatif::HumanBytes(size)
);
} else {
eprint!("Downloading {resource}...");
}
io::stderr().flush().ok();
Self {
start_time: Instant::now(),
bytes: 0,
bytes_since_last_update: 0,
}
}
}
impl DownloadBar for LightDownloadBar {
fn tick(&mut self, chunk_size: usize) {
self.bytes_since_last_update += chunk_size;
if self.bytes_since_last_update > 100_000_000 {
eprint!(".");
io::stderr().flush().ok();
self.bytes_since_last_update = 0;
}
self.bytes += chunk_size;
}
fn finish(&self) {
let duration = Instant::now().duration_since(self.start_time);
eprintln!(
" ✓ Done! Finished in {}",
indicatif::HumanDuration(duration)
);
io::stderr().flush().ok();
}
}