diff --git a/Cargo.lock b/Cargo.lock index 82990793..8fd80845 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,6 +558,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -987,6 +1000,12 @@ dependencies = [ "log", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1731,6 +1750,19 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "indicatif" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "inout" version = "0.1.3" @@ -1741,6 +1773,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -2181,6 +2222,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.32.2" @@ -2461,6 +2508,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3107,6 +3160,7 @@ dependencies = [ "dot-writer", "fehler", "humantime", + "indicatif", "itertools", "libc", "predicates", diff --git a/Cargo.toml b/Cargo.toml index 8f919827..8e492cc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ anyhow = "1.0.18" chrono = "0.4.10" clap = { version = "4", features = ["derive", "env", "string", "wrap_help"] } humantime = "2" +indicatif = "0.17" itertools = ">=0.10, <0.13" sequoia-cert-store = "0.4.1" sequoia-wot = "0.9" diff --git a/src/commands/network.rs b/src/commands/network.rs index 095b0e02..1afc2c75 100644 --- a/src/commands/network.rs +++ b/src/commands/network.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use std::time::{Duration, SystemTime}; use anyhow::Context; +use indicatif::ProgressBar; use tokio::task::JoinSet; use sequoia_openpgp as openpgp; @@ -499,6 +500,15 @@ struct Response { } impl Response { + /// Creates a progress bar. + fn progress_bar(config: &Config) -> ProgressBar { + if config.verbose { + ProgressBar::hidden() + } else { + ProgressBar::new(0) + } + } + /// Collects the responses, and displays failures. /// /// If `silent_errors` is given, then failure messages are @@ -507,12 +517,14 @@ impl Response { async fn collect(config: &mut Config<'_>, mut responses: JoinSet, certify: bool, - silent_errors: bool) + silent_errors: bool, + pb: &mut ProgressBar) -> Result> { let mut certs = Vec::new(); let mut errors = Vec::new(); while let Some(response) = responses.join_next().await { + pb.inc(1); let response = response?; match response.results { Ok(returned_certs) => for cert in returned_certs { @@ -540,7 +552,7 @@ impl Response { if ! silent_errors || config.verbose || certs.is_empty() { for (method, query, e) in errors { - wprintln!("{}: {}: {}", method, query, e); + pb.suspend(|| wprintln!("{}: {}: {}", method, query, e)); } } @@ -588,6 +600,8 @@ pub fn dispatch_fetch(mut config: Config, c: cli::network::fetch::Command) let mut seen_ids = HashSet::new(); let mut queries = Query::parse(&c.query)?; let mut results = Vec::new(); + let mut pb = Response::progress_bar(&config); + let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { for _ in 0..FETCH_MAX_QUERY_ITERATIONS { @@ -611,6 +625,7 @@ pub fn dispatch_fetch(mut config: Config, c: cli::network::fetch::Command) for ks in servers.iter().cloned() { let query = query.clone(); + pb.inc_length(1); requests.spawn(async move { let results = match query.clone() { Query::Handle(h) => ks.get(h).await, @@ -628,6 +643,7 @@ pub fn dispatch_fetch(mut config: Config, c: cli::network::fetch::Command) if let Some(address) = query.as_address() { let a = address.to_string(); let http_client = http_client.clone(); + pb.inc_length(1); requests.spawn(async move { let results = wkd::get(&http_client, &a).await; @@ -639,6 +655,7 @@ pub fn dispatch_fetch(mut config: Config, c: cli::network::fetch::Command) }); let a = address.to_string(); + pb.inc_length(1); requests.spawn(async move { let results = dane::get(&a).await; Response { @@ -655,7 +672,7 @@ pub fn dispatch_fetch(mut config: Config, c: cli::network::fetch::Command) } let mut certs = Response::collect( - &mut config, requests, c.output.is_none(), default_servers).await?; + &mut config, requests, c.output.is_none(), default_servers, &mut pb).await?; // Expand certs to discover new identifiers to query. for cert in &certs { @@ -673,6 +690,7 @@ pub fn dispatch_fetch(mut config: Config, c: cli::network::fetch::Command) Result::Ok(()) })?; + drop(pb); Response::import_or_emit(config, c.output, c.binary, results)?; Ok(()) @@ -707,12 +725,14 @@ pub fn dispatch_keyserver(mut config: Config, use crate::cli::network::keyserver::Subcommands::*; match c.subcommand { Fetch(c) => rt.block_on(async { + let mut pb = Response::progress_bar(&config); let queries = Query::parse(&c.query)?; let mut requests = tokio::task::JoinSet::new(); queries.into_iter().for_each(|query| { for ks in servers.iter().cloned() { let query = query.clone(); + pb.inc_length(1); requests.spawn(async move { let results = match query.clone() { Query::Handle(h) => ks.get(h).await, @@ -729,7 +749,8 @@ pub fn dispatch_keyserver(mut config: Config, }); let certs = Response::collect( - &mut config, requests, c.output.is_none(), default_servers).await?; + &mut config, requests, c.output.is_none(), default_servers, &mut pb).await?; + drop(pb); Response::import_or_emit(config, c.output, c.binary, certs)?; Result::Ok(()) })?, @@ -819,10 +840,12 @@ pub fn dispatch_wkd(mut config: Config, c: cli::network::wkd::Command) output.write(config.output_format, &mut std::io::stdout())?; }, Fetch(c) => rt.block_on(async { + let mut pb = Response::progress_bar(&config); let http_client = http_client()?; let queries = Query::parse_addresses(&c.addresses)?; let mut requests = tokio::task::JoinSet::new(); queries.into_iter().for_each(|query| { + pb.inc_length(1); let http_client = http_client.clone(); requests.spawn(async move { let results = wkd::get( @@ -838,7 +861,8 @@ pub fn dispatch_wkd(mut config: Config, c: cli::network::wkd::Command) }); let certs = Response::collect( - &mut config, requests, c.output.is_none(), false).await?; + &mut config, requests, c.output.is_none(), false, &mut pb).await?; + drop(pb); Response::import_or_emit(config, c.output, c.binary, certs)?; Result::Ok(()) })?, @@ -912,9 +936,11 @@ pub fn dispatch_dane(mut config: Config, c: cli::network::dane::Command) } }, Fetch(c) => rt.block_on(async { + let mut pb = Response::progress_bar(&config); let queries = Query::parse_addresses(&c.addresses)?; let mut requests = tokio::task::JoinSet::new(); queries.into_iter().for_each(|query| { + pb.inc_length(1); requests.spawn(async move { let results = dane::get( query.as_address().expect("parsed only addresses")) @@ -928,7 +954,8 @@ pub fn dispatch_dane(mut config: Config, c: cli::network::dane::Command) }); let certs = Response::collect( - &mut config, requests, c.output.is_none(), false).await?; + &mut config, requests, c.output.is_none(), false, &mut pb).await?; + drop(pb); Response::import_or_emit(config, c.output, c.binary, certs)?; Result::Ok(()) })?,