diff --git a/Cargo.lock b/Cargo.lock index 5021ae5..e588264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "console" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "unicode-width", + "winapi", +] + [[package]] name = "crc32fast" version = "1.2.1" @@ -180,6 +195,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "filetime" version = "0.2.14" @@ -270,6 +291,18 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507cf157a0dab3c837bef6e2086466255d9de4a6b1af69e62b62c54cd52f6062" +dependencies = [ + "console", + "lazy_static", + "number_prefix", + "regex", +] + [[package]] name = "indoc" version = "1.0.3" @@ -341,6 +374,12 @@ dependencies = [ "adler32", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "once_cell" version = "1.5.2" @@ -374,12 +413,6 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -422,46 +455,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core", -] - [[package]] name = "redox_syscall" version = "0.2.4" @@ -482,14 +475,20 @@ dependencies = [ ] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "ce5f1ceb7f74abbce32601642fcf8e8508a8a8991e0621c7d750295b9095702b" dependencies = [ - "winapi", + "regex-syntax", ] +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "ring" version = "0.16.20" @@ -592,12 +591,12 @@ dependencies = [ "clap_generate", "colored", "dirs-next", + "indicatif", "indoc", "semver", "serde", "serde_json", "tar", - "tempfile", "ureq", "url", "xz2", @@ -639,26 +638,22 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.2.0" +name = "termcolor" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ - "cfg-if 1.0.0", - "libc", - "rand", - "redox_syscall", - "remove_dir_all", - "winapi", + "winapi-util", ] [[package]] -name = "termcolor" -version = "1.1.2" +name = "terminal_size" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" dependencies = [ - "winapi-util", + "libc", + "winapi", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0d31de3..dcd4f02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ clap = "3.0.0-beta.2" clap_generate = "3.0.0-beta.2" colored = "2.0.0" dirs-next = "2.0.0" +indicatif = "0.16.0" indoc = "1.0.3" semver = "0.11.0" serde = { version = "1.0.123", features = ["derive"] } serde_json = "1.0.62" tar = "0.4.32" -tempfile = "3.2.0" ureq = { version = "2.0.1", features = ["json"] } url = "2.2.0" xz2 = "0.1.6" diff --git a/src/archive.rs b/src/archive.rs index b0138b7..ebe374b 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,18 +1,19 @@ -use std::path::Path; -use ureq::Response; +use std::{io::Cursor, path::Path}; pub struct Archive { - res: Response, + reader: Cursor>, } impl Archive { - pub fn new(res: Response) -> Self { - Archive { res } + pub fn new(buf: Vec) -> Self { + Self { + reader: Cursor::new(buf), + } } #[cfg(unix)] pub fn extract_into>(self, path: P) -> anyhow::Result<()> { - let xz_stream = xz2::read::XzDecoder::new(self.res.into_reader()); + let xz_stream = xz2::read::XzDecoder::new(self.reader); let mut archive = tar::Archive::new(xz_stream); archive.unpack(path).map_err(|e| anyhow::Error::new(e)) } @@ -21,10 +22,7 @@ impl Archive { pub fn extract_into>(self, path: P) -> anyhow::Result<()> { use std::{fs, io}; - let mut reader = self.res.into_reader(); - let mut temp_zip = tempfile::tempfile()?; - std::io::copy(&mut reader, &mut temp_zip)?; - let mut archive = zip::read::ZipArchive::new(&mut temp_zip)?; + let mut archive = zip::read::ZipArchive::new(self.reader)?; for i in 0..archive.len() { let mut file = archive.by_index(i)?; diff --git a/src/downloader.rs b/src/downloader.rs index dd7d1d9..07fb59b 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -1,9 +1,10 @@ -use crate::archive::Archive; use crate::config::Config; use crate::fetcher::Release; use crate::symlink::symlink_to; use crate::url; +use crate::{archive::Archive, progress_bar::Bar}; use colored::*; +use indicatif::HumanBytes; use std::path::PathBuf; use ureq; @@ -22,15 +23,24 @@ pub fn download(r: &Release, config: &Config) -> anyhow::Result { let res = ureq::get(&dist.url).call()?; let len = res .header("Content-Length") - .and_then(|x| x.parse::().ok()) - .ok_or(anyhow::Error::msg("Unable to get content length."))?; + .and_then(|x| x.parse::().ok()); - println!("Installing : {}", &r.version.to_string().bold()); - println!("Downloading : {}", &dist.url.bold()); - println!("Size : {}", &len.to_string().bold()); - println!("---"); + let size = match len { + Some(l) => HumanBytes(l).to_string(), + None => "unknown".into(), + }; - Archive::new(res).extract_into(&release_dir)?; + println!("Installing : {}", r.version.to_string().bold()); + println!("Downloading : {}", dist.url.bold()); + println!("Size : {}", size.bold()); + + println!(); + + let buf = Bar::new(len).read_start(res.into_reader())?; + + println!(); + + Archive::new(buf).extract_into(&release_dir)?; std::fs::rename(&release_dir.join(dist.name), &dest)?; diff --git a/src/main.rs b/src/main.rs index dfd7ccf..1dd086a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod cmd; mod config; mod downloader; mod fetcher; +mod progress_bar; mod shell; mod symlink; mod sysinfo; diff --git a/src/progress_bar.rs b/src/progress_bar.rs new file mode 100644 index 0000000..9a7fa50 --- /dev/null +++ b/src/progress_bar.rs @@ -0,0 +1,69 @@ +// https://mattgathu.github.io/2017/08/29/writing-cli-app-rust.html +use indicatif::{ProgressBar, ProgressStyle}; +use std::io::Read; + +const TEMPLATE: &'static str = + "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})"; + +pub struct Bar { + len: Option, + bar: ProgressBar, + chunk_size: usize, +} + +impl Bar { + pub fn new(len: Option) -> Self { + let (bar, chunk_size) = match len { + Some(x) => { + let bar = ProgressBar::new(x); + + bar.set_style( + ProgressStyle::default_bar() + .template(TEMPLATE) + .progress_chars("#>-"), + ); + + (bar, x as usize / 99) + } + None => { + let bar = ProgressBar::new_spinner(); + + bar.set_style(ProgressStyle::default_spinner()); + + (bar, 1024usize) + } + }; + + Self { + len, + bar, + chunk_size, + } + } + + pub fn read_start(&self, mut reader: impl Read) -> anyhow::Result> { + let mut buf: Vec = match self.len { + Some(x) => Vec::with_capacity(x as usize), + None => Vec::new(), + }; + + loop { + let mut buffer = vec![0; self.chunk_size]; + let bcount = reader.read(&mut buffer[..])?; + + buffer.truncate(bcount); + + if !buffer.is_empty() { + buf.extend(buffer.iter()); + + self.bar.inc(bcount as u64); + } else { + break; + } + } + + self.bar.finish(); + + Ok(buf) + } +}