* 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);
-}
+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, R <: BaseReporter]
+ (rep: R, body: R => 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 -------------------------------------------------*/
+
+}