cached_path/
progress_bar.rs

1use std::io::{self, Write};
2use std::time::Instant;
3
4/// Progress bar types.
5///
6/// This can be set with
7/// [`CacheBuilder::progress_bar()`](struct.CacheBuilder.html#method.progress_bar).
8#[derive(Debug, Clone, Default)]
9pub enum ProgressBar {
10    /// Gives pretty, verbose progress bars.
11    #[default]
12    Full,
13    /// Gives progress bars with minimal output.
14    ///
15    /// This is a good option to use if your output is being captured to a file but you
16    /// still want to see progress updates.
17    Light,
18}
19
20impl ProgressBar {
21    pub(crate) fn wrap_download<W: Write>(
22        &self,
23        resource: &str,
24        content_length: Option<u64>,
25        writer: W,
26    ) -> DownloadWrapper<W> {
27        let bar: Box<dyn DownloadBar> = match self {
28            ProgressBar::Full => Box::new(FullDownloadBar::new(content_length)),
29            ProgressBar::Light => Box::new(LightDownloadBar::new(resource, content_length)),
30        };
31        DownloadWrapper::new(bar, writer)
32    }
33}
34
35pub(crate) struct DownloadWrapper<W: Write> {
36    bar: Box<dyn DownloadBar>,
37    writer: W,
38}
39
40impl<W> DownloadWrapper<W>
41where
42    W: Write,
43{
44    fn new(bar: Box<dyn DownloadBar>, writer: W) -> Self {
45        Self { bar, writer }
46    }
47
48    pub(crate) fn finish(&self) {
49        self.bar.finish();
50    }
51}
52
53impl<W: Write> Write for DownloadWrapper<W> {
54    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
55        self.writer.write(buf)
56    }
57
58    fn flush(&mut self) -> io::Result<()> {
59        self.writer.flush()
60    }
61
62    fn write_vectored(&mut self, bufs: &[io::IoSlice]) -> io::Result<usize> {
63        self.writer.write_vectored(bufs)
64    }
65
66    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
67        self.writer.write_all(buf).map(|()| {
68            self.bar.tick(buf.len());
69        })
70    }
71}
72
73trait DownloadBar {
74    fn tick(&mut self, chunk_size: usize);
75
76    fn finish(&self);
77}
78
79pub(crate) struct FullDownloadBar {
80    bar: indicatif::ProgressBar,
81}
82
83impl FullDownloadBar {
84    pub(crate) fn new(content_length: Option<u64>) -> Self {
85        let bar = match content_length {
86            Some(length) => {
87                let bar = indicatif::ProgressBar::new(length);
88                bar.set_style(
89                    indicatif::ProgressStyle::default_bar()
90                    .progress_chars("=>-")
91                    .template(
92                        "{msg:.bold.cyan/blue} [{bar:20.cyan/blue}][{percent}%] {bytes}/{total_bytes:.bold} |{bytes_per_sec}|",
93                    )
94                );
95                bar
96            }
97            None => {
98                let bar = indicatif::ProgressBar::new_spinner();
99                bar.set_style(
100                    indicatif::ProgressStyle::default_bar()
101                        .tick_strings(&[
102                            "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖",
103                            "⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐",
104                            "⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒",
105                            "⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋",
106                            "⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈",
107                            "⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉",
108                            "⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚",
109                            "⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂",
110                            "⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒",
111                            "⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒⠲⠴",
112                            "⠒⠐⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄",
113                            "⠐⠒⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤",
114                            "⠓⠋⠉⠈⠈⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠",
115                        ])
116                        .template(
117                            "{msg:.bold.cyan/blue} [{spinner:.cyan/blue}] {bytes:.bold} |{bytes_per_sec}|",
118                        ),
119                );
120                bar
121            }
122        };
123        bar.set_message("Downloading");
124        // Update every 1 MBs.
125        // NOTE: If we don't set this, the updates happen WAY too frequently and it makes downloads
126        // take about twice as long.
127        bar.set_draw_delta(1_000_000);
128        Self { bar }
129    }
130}
131
132impl DownloadBar for FullDownloadBar {
133    fn tick(&mut self, chunk_size: usize) {
134        self.bar.inc(chunk_size as u64);
135    }
136
137    fn finish(&self) {
138        self.bar.set_message("Downloaded");
139        self.bar.set_style(
140            indicatif::ProgressStyle::default_bar()
141                .template("{msg:.green.bold} {total_bytes:.bold} in {elapsed}"),
142        );
143        self.bar.finish_at_current_pos();
144    }
145}
146
147pub(crate) struct LightDownloadBar {
148    start_time: Instant,
149    bytes: usize,
150    bytes_since_last_update: usize,
151}
152
153impl LightDownloadBar {
154    pub(crate) fn new(resource: &str, content_length: Option<u64>) -> Self {
155        if let Some(size) = content_length {
156            eprint!(
157                "Downloading {} [{}]...",
158                resource,
159                indicatif::HumanBytes(size)
160            );
161        } else {
162            eprint!("Downloading {}...", resource);
163        }
164        io::stderr().flush().ok();
165        Self {
166            start_time: Instant::now(),
167            bytes: 0,
168            bytes_since_last_update: 0,
169        }
170    }
171}
172
173impl DownloadBar for LightDownloadBar {
174    fn tick(&mut self, chunk_size: usize) {
175        self.bytes_since_last_update += chunk_size;
176        // Update every 100 MBs.
177        if self.bytes_since_last_update > 100_000_000 {
178            eprint!(".");
179            io::stderr().flush().ok();
180            self.bytes_since_last_update = 0;
181        }
182        self.bytes += chunk_size;
183    }
184
185    fn finish(&self) {
186        let duration = Instant::now().duration_since(self.start_time);
187        eprintln!(
188            " ✓ Done! Finished in {}",
189            indicatif::HumanDuration(duration)
190        );
191        io::stderr().flush().ok();
192    }
193}