keys.scala, etc.: Make merging public keys have a progress bar.
[tripe-android] / progress.scala
index ddd2b6a..5f81615 100644 (file)
  * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-package uk.org.distorted.tripe; package object progress;
+package uk.org.distorted.tripe; package object progress {
 
 /*----- Imports -----------------------------------------------------------*/
 
-import Math.ceil;
-import System.currentTimeMillis;
-import System.{err => stderr};         // FIXME: split out terminal progress
+import scala.collection.mutable.{Publisher, Subscriber};
 
-/*----- Main code ---------------------------------------------------------*/
+import java.lang.System.currentTimeMillis;
 
-def formatTime(t: Int): String =
-  if (t < -1) "???"
-  else {
-    val (s, t1) = (t%60, t/60);
-    val (m, h) = (t1%60, t1/60);
-    if (h > 0) f"$h%d:$m%02d:$s%02d"
-    else f"$m%02d:$s%02d"
+/*----- Progress displays -------------------------------------------------*/
+
+trait Model {
+  protected val t0 = currentTimeMillis;
+
+  def what: String;
+  def max: Long;
+
+  def eta(cur: Long): Double = {
+    /* Report the estimated time remaining in seconds, or -1 if no idea.
+     *
+     * The model here is very stupid.  Weird jobs should override this and
+     * do something more sensible.
+     */
+
+    val max = this.max;
+    val delta = currentTimeMillis - t0
+    if (max < 0 || cur <= 0) -1 else delta*(max - cur)/cur.toDouble
   }
 
-private val UDATA = Seq("kB", "MB", "GB", "PB", "EB");
-def formatBytes(n: Long): String = {
-  val (x, u) = (n.toDouble, "B ") /: UDATA {
-    case ((x, u), name) if x >= 1024.0 => (x/1024.0, name)
-    case (xu, _) => xu
+  protected def fmt1(n: Long): String = n.toString;
+
+  def format(cur: Long): String = {
+    val max = this.max;
+    val fc = fmt1(cur);
+    if (max >= 0) { val fm = fmt1(max); s"%${fm.length}s/%s".format(fc, fm) }
+    else if (cur > 0) fc
+    else ""
   }
-  f"$x%6.1f$u%s"
 }
 
-trait Eyecandy {
-  def set(line: String);
-  def clear();
-  def commit();
-  def commit(line: String) { commit(); set(line); commit(); }
+class SimpleModel(val what: String, val max: Long) extends Model;
 
-  def begin(job: Job);
+class DetailedModel(what: String, max: Long) extends SimpleModel(what, max) {
+  var detail: String = null;
+  override def format(cur: Long): String = {
+    val sb = new StringBuilder;
+    sb ++= super.format(cur);
+    if (detail != null) { sb += ' '; sb ++= detail; }
+    sb.result
+  }
 }
 
+private val UDATA = Seq("kB", "MB", "GB", "TB", "PB", "EB");
+
+trait DataModel extends Model {
+  override def fmt1(n: Long): String = {
+    val (x, u) = ((n.toDouble, "B ") /: UDATA) { (xu, n) => (xu, n) match {
+      case ((x, u), name) if x >= 1024.0 => (x/1024.0, name)
+      case (xu, _) => xu
+    } }
+    f"$x%6.1f$u%s"
+  }
+}
 
-trait Job with Publisher[ {
-  def what: String;                    // imperative for what we're doing
-  def cur: Long;                       // current position in work
-  def max: Long;                       // maximum work to do
-  def format: String;                  // describe progress in useful terms
+trait BaseReporter {
+  def done();
+  def failed(e: Exception);
+}
 
-  private[this] val t0 = currentTimeMillis;
+trait JobReporter extends BaseReporter {
+  def step(cur: Long);
+  def change(model: Model, cur: Long);
+}
 
-  def eta: Int =
-    /* Report the estimated time remaining in seconds, or -1 if no idea.
-     *
-     * The model here is very stupid.  Weird jobs should override this and do
-     * something more sensible.
-     */
+trait OperationReporter extends BaseReporter {
+  def step(detail: String);
+}
 
-    if (max < 0 || cur <= 0) -1
-    else ceil((currentTimeMillis - t0)/1000.0 *
-             (max - cur)/cur.toDouble).toInt;
+def withReporter[T, P <: BaseReporter](rep: P, body: P => T): T = {
+  val ret = try { body(rep) }
+  catch { case e: Exception => rep.failed(e); throw e; }
+  rep.done();
+  ret
 }
 
-object TerminalEyecandy extends Eyecandy {
-  private var last = "";
-  var eyecandyp = 
-  
+trait Eyecandy {
+  def note(msg: String);
+  def clear();
+  def commit();
+  def record(msg: String) { note(msg); commit(); }
+  def done();
+  def cancelled() { failed("cancelled"); }
+  def failed(msg: String);
+
+  def beginJob(model: Model): JobReporter
+    // = new JobReporter(model);
+
+  def beginOperation(what: String): OperationReporter
+    // = new OperationReporter(what);
+
+  def job[T](model: Model)(body: JobReporter => T): T =
+    withReporter(beginJob(model), body);
+
+  def operation[T](what: String)(body: OperationReporter => T): T =
+    withReporter(beginOperation(what), body);
 }
 
 /*----- That's all, folks -------------------------------------------------*/
+
+}