From: Ian Jackson Date: Thu, 13 May 2021 20:34:38 +0000 (+0100) Subject: progress: Print progress bars in otter(1) bundle upload X-Git-Tag: otter-0.6.0~308 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=758f9f7269e6603ef570d4032dfba2f667f945d2;p=otter.git progress: Print progress bars in otter(1) bundle upload Signed-off-by: Ian Jackson --- diff --git a/Cargo.lock b/Cargo.lock index a84ba241..e4eacaf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,6 +438,21 @@ dependencies = [ "bitflags", ] +[[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 0.3.9", +] + [[package]] name = "console_error_panic_hook" version = "0.1.6" @@ -844,6 +859,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[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.28" @@ -2226,6 +2247,7 @@ dependencies = [ "cast_trait_object", "chrono", "chrono-tz", + "console", "delegate", "derive-into-owned", "digest 0.9.0", @@ -2272,6 +2294,7 @@ dependencies = [ "typetag", "uds", "unicase 2.6.0", + "unicode-width", "url 2.2.1", "usvg", "vecdeque-stableix", @@ -3820,6 +3843,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index db2d7fb4..4a66e13a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ byteorder="1.3" cast_trait_object="0.1" chrono="0.4" chrono-tz="0.5" +console="0.14" delegate="0.5" derive-into-owned="0.1" digest="0.9" @@ -76,6 +77,7 @@ toml="0.5" typetag="0.1.6" uds="0.2" unicase="2" +unicode-width="0.1" url="2" vecdeque-stableix="1" zip="0.5" diff --git a/src/bin/otter.rs b/src/bin/otter.rs index 791dc873..b978437e 100644 --- a/src/bin/otter.rs +++ b/src/bin/otter.rs @@ -1349,7 +1349,11 @@ mod upload_bundle { game: instance_name.clone(), hash: bundles::Hash(hash.into()), kind, }; - chan.cmd_withbulk(&cmd, &mut f, &mut io::sink(), &mut |_|Ok((/*todo*/)))?; + let mut progress = termprogress::new(); + chan.cmd_withbulk(&cmd, &mut f, &mut io::sink(), &mut |pi|{ + progress.report(&pi); + Ok(()) + })?; } inventory::submit!{Subcommand( diff --git a/src/imports.rs b/src/imports.rs index 54ac0ba3..3baa6e54 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -36,5 +36,6 @@ pub use sha2; pub use slotmap; pub use toml; pub use uds; +pub use unicode_width; pub use vecdeque_stableix; pub use zip as zipfile; diff --git a/src/lib.rs b/src/lib.rs index 4da24030..6fa03187 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,7 @@ pub mod progress; pub mod shapelib; pub mod spec; pub mod sse; +pub mod termprogress; pub mod tz; pub mod updates; pub mod ui; diff --git a/src/prelude.rs b/src/prelude.rs index f68a9714..bffec21d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -158,6 +158,7 @@ pub use crate::spec::*; pub use crate::spec::piece_specs::{FaceColourSpecs, SimpleCommon}; pub use crate::sse; pub use crate::toml_de; +pub use crate::termprogress; pub use crate::tz::*; pub use crate::updates::*; pub use crate::utils::*; diff --git a/src/progress.rs b/src/progress.rs index 232b6ab3..3c0ae81f 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -6,8 +6,8 @@ use crate::prelude::*; #[derive(Debug,Clone,Serialize,Deserialize,IntoOwned)] pub struct ProgressInfo<'pi> { - phase: Count<'pi>, - item: Count<'pi>, + pub phase: Count<'pi>, + pub item: Count<'pi>, } #[derive(Debug,Clone,Serialize,Deserialize,IntoOwned)] diff --git a/src/termprogress.rs b/src/termprogress.rs new file mode 100644 index 00000000..72d75f57 --- /dev/null +++ b/src/termprogress.rs @@ -0,0 +1,115 @@ +// Copyright 2020-2021 Ian Jackson and contributors to Otter +// SPDX-License-Identifier: AGPL-3.0-or-later There is NO WARRANTY. + +use crate::prelude::*; + +use unicode_width::UnicodeWidthChar; + +type Col = usize; + +pub trait Reporter { + fn report(&mut self, pi: &ProgressInfo<'_>); +} + +pub struct NullReporter; + +#[allow(unused_variables)] +impl Reporter for NullReporter { + fn report(&mut self, pi: &ProgressInfo<'_>) { } +} + +pub fn new() -> Box { + let term = console::Term::buffered_stderr(); + if_chain!{ + if term.is_term(); + if let Some((_, width)) = term.size_checked(); + then { + Box::new(TermReporter { + term, + width: width.into(), + needs_clear: None, + }) + } else { + Box::new(NullReporter) + } + } +} + +pub struct TermReporter { + term: console::Term, + width: Col, + needs_clear: Option<()>, +} + +const LHS_TARGET: Col = 25; +const LHS_FRAC: f32 = (LHS_TARGET as f32)/80.0; + +impl Reporter for TermReporter { + fn report(&mut self, pi: &ProgressInfo<'_>) { + if let Some((_, width)) = self.term.size_checked() { + self.width = width.into() + } + + let mid = min(LHS_TARGET, ((self.width as f32) * LHS_FRAC) as Col); + let mut out = String::new(); + self.bar(&mut out, mid, &pi.phase); + self.bar(&mut out, self.width - mid, &pi.item); + self.clear_line(); + self.needs_clear = Some(()); + self.term.write_str(&out).unwrap_or(()); + self.term.flush().unwrap_or(()); + } +} + +impl TermReporter { + fn clear_line(&mut self) { + if let Some(()) = self.needs_clear.take() { + self.term.clear_line().unwrap_or(()); + } + } + + fn bar(&self, out: &mut String, fwidth: Col, info: &progress::Count) { + let desc = console::strip_ansi_codes(&info.desc); + let w_change = if info.n == 0 { 0 } else { + min( + ((info.i as f32) / (info.n as f32) * (fwidth as f32)) as Col, + LHS_TARGET + ) + }; + let mut desc = desc + .chars() + .chain(iter::repeat(' ')) + .peekable(); + let mut w_sofar = 0; + + let mut half = |stop_at|{ + let mut accumulate = String::new(); + loop { + let &c = desc.peek().unwrap(); + if_let!{ Some(w) = c.width(); else continue; } + let w_next = w_sofar + w; + if w_next > stop_at { break } + accumulate.push(c); + w_sofar = w_next; + let _ = desc.next().unwrap(); + } + accumulate + }; + + let lhs = half(w_change); + if lhs.len() > 0 { + let style = console::Style::new().for_stderr().reverse(); + *out += &style.apply_to(lhs).to_string(); + } + + *out += &half(fwidth); + out.extend( iter::repeat(' ').take( fwidth - w_sofar )); + } +} + +impl Drop for TermReporter { + fn drop(&mut self) { + self.clear_line(); + self.term.flush().unwrap_or(()); + } +}