X-Git-Url: https://git.distorted.org.uk/~mdw/tripe-android/blobdiff_plain/c8292b34485a2e00e676023d4164dd5841e4659f..HEAD:/progress.scala?ds=sidebyside diff --git a/progress.scala b/progress.scala index ddd2b6a..5f81615 100644 --- a/progress.scala +++ b/progress.scala @@ -23,68 +23,112 @@ * along with TrIPE. If not, see . */ -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 -------------------------------------------------*/ + +}