1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use std::io::{self, Write};
use std::time::Instant;

/// Progress bar types.
///
/// This can be set with
/// [`CacheBuilder::progress_bar()`](struct.CacheBuilder.html#method.progress_bar).
#[derive(Debug, Clone)]
pub enum ProgressBar {
    /// Progress bar with the highest verbosity.
    ///
    /// This should only be used if you're running `cached-path` from an interactive terminal.
    Full,
    /// Progress bar with that produces minimal output.
    ///
    /// This is a good option to use if your output is being captured to a file but you
    /// still want to see a progress bar for downloads.
    Light,
}

impl Default for ProgressBar {
    fn default() -> Self {
        ProgressBar::Light
    }
}

impl ProgressBar {
    pub(crate) fn get_full_progress_bar(
        resource: &str,
        content_length: Option<u64>,
    ) -> indicatif::ProgressBar {
        let progress_bar = match content_length {
            Some(length) => {
                let progress_bar = indicatif::ProgressBar::new(length);
                progress_bar.set_style(indicatif::ProgressStyle::default_spinner().template(
                    "{percent}% {wide_bar:.cyan/blue} {bytes_per_sec},<{eta} [{bytes}, {elapsed}]",
                ));
                progress_bar
            }
            None => {
                let progress_bar = indicatif::ProgressBar::new_spinner();
                progress_bar.set_style(
                    indicatif::ProgressStyle::default_spinner()
                        .template("{spinner} {bytes_per_sec} [{bytes}, {elapsed}] {msg}"),
                );
                progress_bar
            }
        };
        progress_bar.println(format!("Downloading {}", resource));
        // Update every 1 MBs.
        // NOTE: If we don't set this, the updates happen WAY too frequently and it makes downloads
        // take about twice as long.
        progress_bar.set_draw_delta(1_000_000);
        progress_bar
    }

    pub(crate) fn get_light_download_wrapper<W: Write>(
        resource: &str,
        content_length: Option<u64>,
        writer: W,
    ) -> LightDownloadWrapper<W> {
        LightDownloadWrapper::new(resource, content_length, writer)
    }
}

#[derive(Debug)]
pub(crate) struct LightDownloadWrapper<W: Write> {
    start_time: Instant,
    bytes: usize,
    bytes_since_last_update: usize,
    writer: W,
}

impl<W: Write> LightDownloadWrapper<W> {
    pub(crate) fn new(resource: &str, content_length: Option<u64>, writer: W) -> 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,
            writer,
        }
    }

    pub(crate) fn finish(&self) {
        let duration = Instant::now().duration_since(self.start_time);
        eprint!(
            " ✓ Done! Finished in {}\n",
            indicatif::HumanDuration(duration)
        );
        io::stderr().flush().ok();
    }
}

impl<W: Write> Write for LightDownloadWrapper<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(|()| {
            let chunk_size = buf.len();
            self.bytes_since_last_update += chunk_size;
            // Update every 100 MBs.
            if self.bytes_since_last_update > 100_000_000 {
                eprint!(".");
                io::stderr().flush().ok();
                self.bytes_since_last_update = 0;
            }
            self.bytes += chunk_size;
        })
    }
}