chiark / gitweb /
progress: Print progress bars in otter(1) bundle upload
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 13 May 2021 20:34:38 +0000 (21:34 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 14 May 2021 22:51:38 +0000 (23:51 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
Cargo.lock
Cargo.toml
src/bin/otter.rs
src/imports.rs
src/lib.rs
src/prelude.rs
src/progress.rs
src/termprogress.rs [new file with mode: 0644]

index a84ba241be3adfb94e5ad1639721b3fa06ef0075..e4eacaf2f519378527d2bfa5999f19af74a44349 100644 (file)
@@ -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"
index db2d7fb4da072b1e7b92a00ad8124b16b9b3f2d7..4a66e13afabc9e7003b5eda9fd864461475e4d0e 100644 (file)
@@ -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"
index 791dc8730f7e7ef6cc7f8687058d7cb07994f250..b978437e37996d56a6edf22cb07d473c3471560c 100644 (file)
@@ -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(
index 54ac0ba3053496d6fbefe5c82e30d363a8feed3e..3baa6e548c95cabf9d1cb92a09438b4530763c39 100644 (file)
@@ -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;
index 4da24030c1042d9c4d5a0c21e291eb6709cb6b05..6fa03187bd59ba08477e169b27151f4ea0c98598 100644 (file)
@@ -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;
index f68a9714d0413084eba488feb53c03acf973343e..bffec21d3acc893e184997ba678ad58601d20fad 100644 (file)
@@ -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::*;
index 232b6ab3260ec50d6f9ca203199046dbe85a3f09..3c0ae81f541ff104fdd8426d274dbb6f74d0e3aa 100644 (file)
@@ -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 (file)
index 0000000..72d75f5
--- /dev/null
@@ -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<dyn Reporter> {
+  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(());
+  }
+}