Skip to content

Commit

Permalink
download progress bars
Browse files Browse the repository at this point in the history
Fixes #1585
  • Loading branch information
jdx committed Feb 2, 2024
1 parent d3748ef commit c4ed3ca
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 33 deletions.
28 changes: 26 additions & 2 deletions src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::fs::File;
use std::hash::{Hash, Hasher};
use std::io::{Read, Write};
use std::path::Path;

use eyre::Result;
use rayon::prelude::*;
use sha2::{Digest, Sha256};

use crate::file::display_path;
use crate::ui::progress_report::SingleReport;

pub fn hash_to_str<T: Hash>(t: &T) -> String {
let mut s = DefaultHasher::new();
Expand All @@ -18,15 +20,37 @@ pub fn hash_to_str<T: Hash>(t: &T) -> String {
}

pub fn file_hash_sha256(path: &Path) -> Result<String> {
file_hash_sha256_prog(path, None)
}

pub fn file_hash_sha256_prog(path: &Path, pr: Option<&dyn SingleReport>) -> Result<String> {
let mut file = File::open(path)?;
if let Some(pr) = pr {
pr.set_length(file.metadata()?.len());
}
let mut hasher = Sha256::new();
let mut buf = [0; 32 * 1024];
loop {
let n = file.read(&mut buf)?;
if n == 0 {
break;
}
hasher.write_all(&buf[..n])?;
if let Some(pr) = pr {
pr.inc(n as u64);
}
}
std::io::copy(&mut file, &mut hasher)?;
let hash = hasher.finalize();
Ok(format!("{hash:x}"))
}

pub fn ensure_checksum_sha256(path: &Path, checksum: &str) -> Result<()> {
let actual = file_hash_sha256(path)?;
pub fn ensure_checksum_sha256(
path: &Path,
checksum: &str,
pr: Option<&dyn SingleReport>,
) -> Result<()> {
let actual = file_hash_sha256_prog(path, pr)?;
ensure!(
actual == checksum,
"Checksum mismatch for file {}:\nExpected: {checksum}\nActual: {actual}",
Expand Down
27 changes: 25 additions & 2 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use std::time::Duration;

Expand All @@ -10,6 +11,7 @@ use reqwest::IntoUrl;
use crate::cli::version;
use crate::env::MISE_FETCH_REMOTE_VERSIONS_TIMEOUT;
use crate::file::display_path;
use crate::ui::progress_report::SingleReport;
use crate::{env, file};

#[cfg(not(test))]
Expand Down Expand Up @@ -77,14 +79,35 @@ impl Client {
Ok(json)
}

pub fn download_file<U: IntoUrl>(&self, url: U, path: &Path) -> Result<()> {
pub fn download_file<U: IntoUrl>(
&self,
url: U,
path: &Path,
pr: Option<&dyn SingleReport>,
) -> Result<()> {
let url = url.into_url()?;
debug!("GET Downloading {} to {}", &url, display_path(path));
let mut resp = self.get(url)?;
if let Some(length) = resp.content_length() {
if let Some(pr) = pr {
pr.set_length(length);
}
}

file::create_dir_all(path.parent().unwrap())?;

let mut buf = [0; 32 * 1024];
let mut file = File::create(path)?;
resp.copy_to(&mut file)?;
loop {
let n = resp.read(&mut buf)?;
if n == 0 {
break;
}
file.write_all(&buf[..n])?;
if let Some(pr) = pr {
pr.inc(n as u64);
}
}
Ok(())
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/core/bun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl BunPlugin {
let tarball_path = tv.download_path().join(filename);

pr.set_message(format!("downloading {}", &url));
HTTP.download_file(&url, &tarball_path)?;
HTTP.download_file(&url, &tarball_path, Some(pr))?;

Ok(tarball_path)
}
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/core/deno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl DenoPlugin {
let tarball_path = tv.download_path().join(filename);

pr.set_message(format!("downloading {}", &url));
HTTP.download_file(&url, &tarball_path)?;
HTTP.download_file(&url, &tarball_path, Some(pr))?;

// TODO: hash::ensure_checksum_sha256(&tarball_path, &m.sha256)?;

Expand Down
1 change: 1 addition & 0 deletions src/plugins/core/erlang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl ErlangPlugin {
HTTP_FETCH.download_file(
format!("https://raw.githubusercontent.com/kerl/kerl/{KERL_VERSION}/kerl"),
&self.kerl_path(),
None,
)?;
file::make_executable(&self.kerl_path())?;
Ok(())
Expand Down
31 changes: 16 additions & 15 deletions src/plugins/core/go.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use std::thread;

use eyre::Result;
use itertools::Itertools;
Expand Down Expand Up @@ -102,21 +103,21 @@ impl GoPlugin {
let tarball_url = format!("{}/{}", &*env::MISE_GO_DOWNLOAD_MIRROR, &filename);
let tarball_path = tv.download_path().join(filename);

pr.set_message(format!("downloading {}", &tarball_url));
HTTP.download_file(&tarball_url, &tarball_path)?;

self.verify_tarball_checksum(&tarball_url, &tarball_path)?;

Ok(tarball_path)
}

fn verify_tarball_checksum(&self, tarball_url: &str, tarball_path: &Path) -> Result<()> {
if !*env::MISE_GO_SKIP_CHECKSUM {
let checksum_url = format!("{}.sha256", tarball_url);
let checksum = HTTP.get_text(checksum_url)?;
hash::ensure_checksum_sha256(tarball_path, &checksum)?;
}
Ok(())
thread::scope(|s| {
let checksum_handle = s.spawn(|| {
let checksum_url = format!("{}.sha256", &tarball_url);
HTTP.get_text(checksum_url)
});
pr.set_message(format!("downloading {}", &tarball_url));
HTTP.download_file(&tarball_url, &tarball_path, Some(pr))?;

if !*env::MISE_GO_SKIP_CHECKSUM {
pr.set_message("verifying checksum".into());
let checksum = checksum_handle.join().unwrap()?;
hash::ensure_checksum_sha256(&tarball_path, &checksum, Some(pr))?;
}
Ok(tarball_path)
})
}

fn install(&self, tv: &ToolVersion, pr: &dyn SingleReport, tarball_path: &Path) -> Result<()> {
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/core/java.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ impl JavaPlugin {
let tarball_path = tv.download_path().join(filename);

pr.set_message(format!("downloading {}", &m.url));
HTTP.download_file(&m.url, &tarball_path)?;
HTTP.download_file(&m.url, &tarball_path, Some(pr))?;

hash::ensure_checksum_sha256(&tarball_path, &m.sha256)?;
hash::ensure_checksum_sha256(&tarball_path, &m.sha256, Some(pr))?;

Ok(tarball_path)
}
Expand Down
8 changes: 4 additions & 4 deletions src/plugins/core/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ impl NodePlugin {
pr.set_message(format!("using previously downloaded {tarball_name}"));
} else {
pr.set_message(format!("downloading {tarball_name}"));
HTTP.download_file(url.clone(), local)?;
HTTP.download_file(url.clone(), local, Some(pr))?;
}
if *env::MISE_NODE_VERIFY {
pr.set_message(format!("verifying {tarball_name}"));
self.verify(local, version)?;
self.verify(local, version, pr)?;
}
Ok(())
}
Expand All @@ -143,13 +143,13 @@ impl NodePlugin {
self.sh(ctx, opts)?.arg(&opts.make_install_cmd).execute()
}

fn verify(&self, tarball: &Path, version: &str) -> Result<()> {
fn verify(&self, tarball: &Path, version: &str, pr: &dyn SingleReport) -> Result<()> {
let tarball_name = tarball.file_name().unwrap().to_string_lossy().to_string();
// TODO: verify gpg signature
let shasums = HTTP.get_text(self.shasums_url(version)?)?;
let shasums = hash::parse_shasums(&shasums);
let shasum = shasums.get(&tarball_name).unwrap();
hash::ensure_checksum_sha256(tarball, shasum)
hash::ensure_checksum_sha256(tarball, shasum, Some(pr))
}

fn node_path(&self, tv: &ToolVersion) -> PathBuf {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/core/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ impl PythonPlugin {
let tarball_path = download.join(filename);

ctx.pr.set_message(format!("downloading {}", &url));
HTTP.download_file(&url, &tarball_path)?;
HTTP.download_file(&url, &tarball_path, Some(ctx.pr.as_ref()))?;

ctx.pr
.set_message(format!("installing {}", tarball_path.display()));
Expand Down
35 changes: 30 additions & 5 deletions src/ui/progress_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,32 @@ use indicatif::{ProgressBar, ProgressStyle};
use once_cell::sync::Lazy;

use crate::ui::style;
use crate::{forge, ui};
use crate::{env, forge, ui};

pub trait SingleReport: Send + Sync {
fn println(&self, _message: String) {}
fn set_message(&self, _message: String) {}
fn inc(&self, _delta: u64) {}
fn set_length(&self, _length: u64) {}
fn finish(&self) {}
fn finish_with_message(&self, _message: String) {}
}

static SPIN_TEMPLATE: Lazy<ProgressStyle> = Lazy::new(|| {
let tmpl = "{prefix} {wide_msg} {spinner:.blue} {elapsed:3.dim.italic}";
ProgressStyle::with_template(tmpl).unwrap()
});

static PROG_TEMPLATE: Lazy<ProgressStyle> = Lazy::new(|| {
ProgressStyle::with_template("{prefix} {wide_msg} {spinner:.blue} {elapsed:3.dim.italic}")
.unwrap()
let tmpl = match *env::TERM_WIDTH {
0..=89 => "{prefix} {wide_msg} {bar:5.cyan/blue} {percent:2}%",
90..=99 => "{prefix} {wide_msg} {bar:10.cyan/blue} {spinner:.blue} {percent:2}%",
100..=120 => "{prefix} {wide_msg} {bytes}/{total_bytes:10} {bar:20.cyan/blue} {spinner:.blue} {elapsed:3.dim.italic}",
_ => {
"{prefix} {wide_msg} {bytes}/{total_bytes} ({eta}) {bar:20.cyan/blue} {spinner:.blue} {elapsed:3.dim.italic}"
}
};
ProgressStyle::with_template(tmpl).unwrap()
});

static SUCCESS_TEMPLATE: Lazy<ProgressStyle> = Lazy::new(|| {
Expand All @@ -34,7 +48,7 @@ pub struct ProgressReport {
static LONGEST_PLUGIN_NAME: Lazy<usize> = Lazy::new(|| {
forge::list()
.into_iter()
.map(|p| p.id().len() + 10)
.map(|p| p.id().len())
.max()
.unwrap_or_default()
.max(15)
Expand All @@ -60,7 +74,7 @@ impl ProgressReport {
let _ctrlc = ui::ctrlc::handle_ctrlc().unwrap_or_default();
let pad = *LONGEST_PLUGIN_NAME;
let pb = ProgressBar::new(100)
.with_style(PROG_TEMPLATE.clone())
.with_style(SPIN_TEMPLATE.clone())
.with_prefix(normal_prefix(pad, &prefix));
pb.enable_steady_tick(Duration::from_millis(250));
ProgressReport {
Expand All @@ -81,6 +95,17 @@ impl SingleReport for ProgressReport {
fn set_message(&self, message: String) {
self.pb.set_message(message.replace('\r', ""));
}
fn inc(&self, delta: u64) {
self.pb.inc(delta);
if Some(self.pb.position()) == self.pb.length() {
self.pb.set_style(SPIN_TEMPLATE.clone());
}
}
fn set_length(&self, length: u64) {
self.pb.set_position(0);
self.pb.set_style(PROG_TEMPLATE.clone());
self.pb.set_length(length);
}
fn finish(&self) {
self.pb.set_style(SUCCESS_TEMPLATE.clone());
self.pb
Expand Down

0 comments on commit c4ed3ca

Please sign in to comment.