Browse Source

Add progress bar to install command (#1028)

remotes/origin/use-bnz
eblocha 9 months ago committed by GitHub
parent
commit
66efc5b90c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/show-download-progress.md
  2. 74
      Cargo.lock
  3. 2
      Cargo.toml
  4. 3
      docs/commands.md
  5. 13
      src/commands/install.rs
  6. 21
      src/downloader.rs
  7. 1
      src/main.rs
  8. 147
      src/progress.rs

5
.changeset/show-download-progress.md

@ -0,0 +1,5 @@
---
"fnm": minor
---
Show a progress bar when downloading and extracting node

74
Cargo.lock generated

@ -357,6 +357,19 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "console"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys 0.45.0",
]
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.1.5"
@ -540,6 +553,12 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.32"
@ -638,6 +657,8 @@ dependencies = [
"embed-resource", "embed-resource",
"encoding_rs_io", "encoding_rs_io",
"env_logger", "env_logger",
"http",
"indicatif",
"indoc", "indoc",
"junction", "junction",
"log", "log",
@ -912,6 +933,19 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "indicatif"
version = "0.17.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730"
dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic",
"unicode-width",
]
[[package]] [[package]]
name = "indoc" name = "indoc"
version = "2.0.2" version = "2.0.2"
@ -1164,6 +1198,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "object" name = "object"
version = "0.30.4" version = "0.30.4"
@ -1248,6 +1288,12 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "portable-atomic"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e"
[[package]] [[package]]
name = "pretty_assertions" name = "pretty_assertions"
version = "1.4.0" version = "1.4.0"
@ -2112,7 +2158,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.48.1",
] ]
[[package]] [[package]]
@ -2130,13 +2176,37 @@ dependencies = [
"windows_x86_64_msvc 0.42.2", "windows_x86_64_msvc 0.42.2",
] ]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.48.1",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
] ]
[[package]] [[package]]

2
Cargo.toml

@ -30,11 +30,13 @@ sysinfo = "0.29.3"
thiserror = "1.0.44" thiserror = "1.0.44"
clap_complete = "4.3.1" clap_complete = "4.3.1"
anyhow = "1.0.71" anyhow = "1.0.71"
indicatif = "0.17.6"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
duct = "0.13.6" duct = "0.13.6"
test-log = "0.2.12" test-log = "0.2.12"
http = "0.2.9"
[build-dependencies] [build-dependencies]
embed-resource = "1.8.0" embed-resource = "1.8.0"

3
docs/commands.md

@ -216,6 +216,9 @@ Options:
--latest --latest
Install latest version Install latest version
--no-progress
Do not display a progress bar
--log-level <LOG_LEVEL> --log-level <LOG_LEVEL>
The log level of fnm commands The log level of fnm commands

13
src/commands/install.rs

@ -3,6 +3,7 @@ use crate::alias::create_alias;
use crate::arch::get_safe_arch; use crate::arch::get_safe_arch;
use crate::config::FnmConfig; use crate::config::FnmConfig;
use crate::downloader::{install_node_dist, Error as DownloaderError}; use crate::downloader::{install_node_dist, Error as DownloaderError};
use crate::log_level::LogLevel;
use crate::lts::LtsType; use crate::lts::LtsType;
use crate::outln; use crate::outln;
use crate::remote_node_index; use crate::remote_node_index;
@ -25,6 +26,10 @@ pub struct Install {
/// Install latest version /// Install latest version
#[clap(long, conflicts_with_all = &["version", "lts"])] #[clap(long, conflicts_with_all = &["version", "lts"])]
pub latest: bool, pub latest: bool,
/// Do not display a progress bar
#[clap(long)]
pub no_progress: bool,
} }
impl Install { impl Install {
@ -34,16 +39,19 @@ impl Install {
version: v, version: v,
lts: false, lts: false,
latest: false, latest: false,
no_progress: _,
} => Ok(v), } => Ok(v),
Self { Self {
version: None, version: None,
lts: true, lts: true,
latest: false, latest: false,
no_progress: _,
} => Ok(Some(UserVersion::Full(Version::Lts(LtsType::Latest)))), } => Ok(Some(UserVersion::Full(Version::Lts(LtsType::Latest)))),
Self { Self {
version: None, version: None,
lts: false, lts: false,
latest: true, latest: true,
no_progress: _,
} => Ok(Some(UserVersion::Full(Version::Latest))), } => Ok(Some(UserVersion::Full(Version::Latest))),
_ => Err(Error::TooManyVersionsProvided), _ => Err(Error::TooManyVersionsProvided),
} }
@ -56,6 +64,8 @@ impl Command for Install {
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let current_dir = std::env::current_dir().unwrap(); let current_dir = std::env::current_dir().unwrap();
let show_progress = !self.no_progress && config.log_level().is_writable(&LogLevel::Info);
let current_version = self let current_version = self
.version()? .version()?
.or_else(|| get_user_version_for_directory(current_dir, config)) .or_else(|| get_user_version_for_directory(current_dir, config))
@ -131,6 +141,7 @@ impl Command for Install {
&config.node_dist_mirror, &config.node_dist_mirror,
config.installations_dir(), config.installations_dir(),
safe_arch, safe_arch,
show_progress,
) { ) {
Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => { Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => {
outln!(config, Error, "{} {}", "warning:".bold().yellow(), err); outln!(config, Error, "{} {}", "warning:".bold().yellow(), err);
@ -225,6 +236,7 @@ mod tests {
version: UserVersion::from_str("12.0.0").ok(), version: UserVersion::from_str("12.0.0").ok(),
lts: false, lts: false,
latest: false, latest: false,
no_progress: true,
} }
.apply(&config) .apply(&config)
.expect("Can't install"); .expect("Can't install");
@ -250,6 +262,7 @@ mod tests {
version: None, version: None,
lts: false, lts: false,
latest: true, latest: true,
no_progress: true,
} }
.apply(&config) .apply(&config)
.expect("Can't install"); .expect("Can't install");

21
src/downloader.rs

@ -2,8 +2,11 @@ use crate::arch::Arch;
use crate::archive; use crate::archive;
use crate::archive::{Error as ExtractError, Extract}; use crate::archive::{Error as ExtractError, Extract};
use crate::directory_portal::DirectoryPortal; use crate::directory_portal::DirectoryPortal;
use crate::progress::ResponseProgress;
use crate::version::Version; use crate::version::Version;
use indicatif::ProgressDrawTarget;
use log::debug; use log::debug;
use std::io::Read;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use thiserror::Error; use thiserror::Error;
@ -63,10 +66,7 @@ fn download_url(base_url: &Url, version: &Version, arch: &Arch) -> Url {
.unwrap() .unwrap()
} }
pub fn extract_archive_into<P: AsRef<Path>>( fn extract_archive_into(path: impl AsRef<Path>, response: impl Read) -> Result<(), Error> {
path: P,
response: crate::http::Response,
) -> Result<(), Error> {
#[cfg(unix)] #[cfg(unix)]
let extractor = archive::TarXz::new(response); let extractor = archive::TarXz::new(response);
#[cfg(windows)] #[cfg(windows)]
@ -81,6 +81,7 @@ pub fn install_node_dist<P: AsRef<Path>>(
node_dist_mirror: &Url, node_dist_mirror: &Url,
installations_dir: P, installations_dir: P,
arch: &Arch, arch: &Arch,
show_progress: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str()); let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str());
@ -109,7 +110,14 @@ pub fn install_node_dist<P: AsRef<Path>>(
} }
debug!("Extracting response..."); debug!("Extracting response...");
extract_archive_into(&portal, response)?; if show_progress {
extract_archive_into(
&portal,
ResponseProgress::new(response, ProgressDrawTarget::stderr()),
)?;
} else {
extract_archive_into(&portal, response)?;
}
debug!("Extraction completed"); debug!("Extraction completed");
let installed_directory = std::fs::read_dir(&portal)? let installed_directory = std::fs::read_dir(&portal)?
@ -171,7 +179,8 @@ mod tests {
let version = Version::parse("12.0.0").unwrap(); let version = Version::parse("12.0.0").unwrap();
let arch = Arch::X64; let arch = Arch::X64;
let node_dist_mirror = Url::parse("https://nodejs.org/dist/").unwrap(); let node_dist_mirror = Url::parse("https://nodejs.org/dist/").unwrap();
install_node_dist(&version, &node_dist_mirror, path, &arch).expect("Can't install Node 12"); install_node_dist(&version, &node_dist_mirror, path, &arch, false)
.expect("Can't install Node 12");
let mut location_path = path.join(version.v_str()).join("installation"); let mut location_path = path.join(version.v_str()).join("installation");

1
src/main.rs

@ -22,6 +22,7 @@ mod installed_versions;
mod lts; mod lts;
mod package_json; mod package_json;
mod path_ext; mod path_ext;
mod progress;
mod remote_node_index; mod remote_node_index;
mod shell; mod shell;
mod system_info; mod system_info;

147
src/progress.rs

@ -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…
Cancel
Save