![55192399+eblocha@users.noreply.github.com](/assets/img/avatar_default.png)
![GitHub](/assets/img/avatar_default.png)
8 changed files with 258 additions and 8 deletions
@ -0,0 +1,5 @@ |
|||||||
|
--- |
||||||
|
"fnm": minor |
||||||
|
--- |
||||||
|
|
||||||
|
Show a progress bar when downloading and extracting node |
@ -0,0 +1,147 @@ |
|||||||
|
use std::io::Read; |
||||||
|
|
||||||
|
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; |
||||||
|
use reqwest::blocking::Response; |
||||||
|
|
||||||
|
pub struct ResponseProgress { |
||||||
|
progress: Option<ProgressBar>, |
||||||
|
response: Response, |
||||||
|
} |
||||||
|
|
||||||
|
fn make_progress_bar(size: u64, target: ProgressDrawTarget) -> ProgressBar { |
||||||
|
let bar = ProgressBar::with_draw_target(Some(size), target); |
||||||
|
|
||||||
|
bar.set_style( |
||||||
|
ProgressStyle::with_template( |
||||||
|
"[{elapsed_precise}] [{bar:40}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})", |
||||||
|
) |
||||||
|
.unwrap() |
||||||
|
.progress_chars("#>-"), |
||||||
|
); |
||||||
|
|
||||||
|
bar |
||||||
|
} |
||||||
|
|
||||||
|
impl ResponseProgress { |
||||||
|
pub fn new(response: Response, target: ProgressDrawTarget) -> Self { |
||||||
|
Self { |
||||||
|
progress: response |
||||||
|
.content_length() |
||||||
|
.map(|len| make_progress_bar(len, target)), |
||||||
|
response, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn finish(&self) { |
||||||
|
if let Some(ref bar) = self.progress { |
||||||
|
bar.finish(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Read for ResponseProgress { |
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { |
||||||
|
let size = self.response.read(buf)?; |
||||||
|
|
||||||
|
if let Some(ref bar) = self.progress { |
||||||
|
bar.inc(size as u64); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(size) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Drop for ResponseProgress { |
||||||
|
fn drop(&mut self) { |
||||||
|
self.finish(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use indicatif::{ProgressDrawTarget, TermLike}; |
||||||
|
use reqwest::blocking::Response; |
||||||
|
use std::{ |
||||||
|
io::Read, |
||||||
|
sync::{Arc, Mutex}, |
||||||
|
}; |
||||||
|
|
||||||
|
use super::ResponseProgress; |
||||||
|
|
||||||
|
const CONTENT_LENGTH: usize = 100; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
struct MockedTerm { |
||||||
|
pub buf: Arc<Mutex<String>>, |
||||||
|
} |
||||||
|
|
||||||
|
impl TermLike for MockedTerm { |
||||||
|
fn width(&self) -> u16 { |
||||||
|
80 |
||||||
|
} |
||||||
|
|
||||||
|
fn move_cursor_up(&self, _n: usize) -> std::io::Result<()> { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn move_cursor_down(&self, _n: usize) -> std::io::Result<()> { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn move_cursor_right(&self, _n: usize) -> std::io::Result<()> { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn move_cursor_left(&self, _n: usize) -> std::io::Result<()> { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn write_line(&self, s: &str) -> std::io::Result<()> { |
||||||
|
self.buf.lock().unwrap().push_str(s); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn write_str(&self, s: &str) -> std::io::Result<()> { |
||||||
|
self.buf.lock().unwrap().push_str(s); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn clear_line(&self) -> std::io::Result<()> { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn flush(&self) -> std::io::Result<()> { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_reads_data_and_shows_progress() { |
||||||
|
let response: Response = http::Response::builder() |
||||||
|
.header("Content-Length", CONTENT_LENGTH) |
||||||
|
.body("a".repeat(CONTENT_LENGTH)) |
||||||
|
.unwrap() |
||||||
|
.into(); |
||||||
|
|
||||||
|
let mut buf = [0; CONTENT_LENGTH]; |
||||||
|
|
||||||
|
let out_buf = Arc::new(Mutex::new(String::new())); |
||||||
|
|
||||||
|
let mut progress = ResponseProgress::new( |
||||||
|
response, |
||||||
|
ProgressDrawTarget::term_like(Box::new(MockedTerm { |
||||||
|
buf: out_buf.clone(), |
||||||
|
})), |
||||||
|
); |
||||||
|
let size = progress.read(&mut buf[..]).unwrap(); |
||||||
|
|
||||||
|
drop(progress); |
||||||
|
|
||||||
|
assert_eq!(size, CONTENT_LENGTH); |
||||||
|
assert_eq!(buf, "a".repeat(CONTENT_LENGTH).as_bytes()); |
||||||
|
assert!(out_buf |
||||||
|
.lock() |
||||||
|
.unwrap() |
||||||
|
.contains(&format!("[{}]", &"#".repeat(40)))); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue