chiark / gitweb /
keys.scala, etc.: Make merging public keys have a progress bar.
[tripe-android] / util.scala
1 /* -*-scala-*-
2  *
3  * Miscellaneous utilities
4  *
5  * (c) 2018 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the Trivial IP Encryption (TrIPE) Android app.
11  *
12  * TrIPE is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free
14  * Software Foundation; either version 3 of the License, or (at your
15  * option) any later version.
16  *
17  * TrIPE is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
20  * for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
24  */
25
26 package uk.org.distorted; package object tripe {
27
28 /*----- Imports -----------------------------------------------------------*/
29
30 import scala.language.{existentials, implicitConversions};
31
32 import scala.collection.mutable.{HashSet, WeakHashMap};
33 import scala.concurrent.duration.{Deadline, Duration};
34 import scala.util.control.{Breaks, ControlThrowable};
35
36 import java.io.{BufferedReader, Closeable, File, InputStream, Reader};
37 import java.net.{HttpURLConnection, URL, URLConnection};
38 import java.nio.{ByteBuffer, CharBuffer};
39 import java.nio.channels.{SelectionKey, Selector};
40 import java.nio.channels.spi.{AbstractSelector, AbstractSelectableChannel};
41 import java.nio.charset.Charset;
42 import java.text.SimpleDateFormat;
43 import java.util.{Set => JSet};
44 import java.util.concurrent.locks.{Lock, ReentrantLock};
45
46 /*----- Miscellaneous useful things ---------------------------------------*/
47
48 val rng = new java.security.SecureRandom;
49
50 def unreachable(msg: String): Nothing = throw new AssertionError(msg);
51 def unreachable(): Nothing = unreachable("unreachable");
52 final val ok = ();
53 class Brand(val what: String) {
54   override def toString(): String = s"<${getClass.getName} $what>";
55 }
56
57 /*----- Various pieces of implicit magic ----------------------------------*/
58
59 class InvalidCStringException(msg: String) extends Exception(msg);
60
61 object Implicits {
62
63   /* --- Syntactic sugar for locks --- */
64
65   implicit class LockOps(lk: Lock) {
66     /* LK withLock { BODY }
67      * LK.withLock(INTERRUPT) { BODY }
68      * LK.withLock(DUR, [INTERRUPT]) { BODY } orElse { ALT }
69      * LK.withLock(DL, [INTERRUPT]) { BODY } orElse { ALT }
70      *
71      * Acquire a lock while executing a BODY.  If a duration or deadline is
72      * given then wait so long for the lock, and then give up and run ALT
73      * instead.
74      */
75
76     def withLock[T](dur: Duration, interrupt: Boolean)
77                    (body: => T): PendingLock[T] =
78       new PendingLock(lk, if (dur > Duration.Zero) dur else Duration.Zero,
79                       interrupt, body);
80     def withLock[T](dur: Duration)(body: => T): PendingLock[T] =
81       withLock(dur, true)(body);
82     def withLock[T](dl: Deadline, interrupt: Boolean)
83                    (body: => T): PendingLock[T] =
84       new PendingLock(lk, dl.timeLeft, interrupt, body);
85     def withLock[T](dl: Deadline)(body: => T): PendingLock[T] =
86       withLock(dl, true)(body);
87     def withLock[T](interrupt: Boolean)(body: => T): T = {
88       if (interrupt) lk.lockInterruptibly();
89       else lk.lock();
90       try { body; } finally lk.unlock();
91     }
92     def withLock[T](body: => T): T = withLock(true)(body);
93   }
94
95   class PendingLock[T] private[Implicits]
96           (val lk: Lock, val dur: Duration,
97            val interrupt: Boolean, body: => T) {
98     /* An auxiliary class for LockOps; provides the `orElse' qualifier. */
99
100     def orElse(alt: => T): T = {
101       val locked = (dur, interrupt) match {
102         case (Duration.Inf, true) => lk.lockInterruptibly(); true
103         case (Duration.Inf, false) => lk.lock(); true
104         case (Duration.Zero, false) => lk.tryLock()
105         case (_, true) => lk.tryLock(dur.length, dur.unit)
106         case _ => unreachable("timed wait is always interruptible");
107       }
108       if (!locked) alt;
109       else try { body; } finally lk.unlock();
110     }
111   }
112
113   /* Implicit conversions to `Boolean'.  I miss the way C integers and
114    * pointers convert to boolean, so let's do that here.
115    *
116    * Numeric zero, null, and empty containers are all false; other objects
117    * are true.
118    */
119   implicit def truish(n: Byte): Boolean = n != 0;
120   implicit def truish(n: Char): Boolean = n != 0;
121   implicit def truish(n: Short): Boolean = n != 0;
122   implicit def truish(n: Int): Boolean = n != 0;
123   implicit def truish(n: Long): Boolean = n != 0;
124   implicit def truish(n: Float): Boolean = n != 0;
125   implicit def truish(n: Double): Boolean = n != 0;
126   implicit def truish(x: AnyRef): Boolean = x != null;
127   implicit def truish(s: String): Boolean = s != null && s != "";
128   implicit def truish(o: Option[_]): Boolean = o != None;
129   implicit def truish(i: Iterator[_]): Boolean = i != null && i.hasNext;
130   implicit def truish(c: Traversable[_]): Boolean =
131     c != null && c.nonEmpty;
132
133   /* Some additional bitwise operators.
134    *
135    * For now, just the `bic' operator `&~', because typing `& ~' is
136    * inconsistent with my current style.
137    */
138   class BitwiseIntImplicits(x: Int) {
139     def &~(y: Byte): Int = x & ~y;
140     def &~(y: Char): Int = x & ~y;
141     def &~(y: Short): Int = x & ~y;
142     def &~(y: Int): Int = x & ~y;
143     def &~(y: Long): Long = x & ~y;
144   }
145   class BitwiseLongImplicits(x: Long) {
146     def &~(y: Byte): Long = x & ~y;
147     def &~(y: Char): Long = x & ~y;
148     def &~(y: Short): Long = x & ~y;
149     def &~(y: Int): Long = x & ~y;
150     def &~(y: Long): Long = x & ~y;
151   }
152   implicit def bitwiseImplicits(x: Byte) = new BitwiseIntImplicits(x);
153   implicit def bitwiseImplicits(x: Char) = new BitwiseIntImplicits(x);
154   implicit def bitwiseImplicits(x: Short) = new BitwiseIntImplicits(x);
155   implicit def bitwiseImplicits(x: Int) = new BitwiseIntImplicits(x);
156   implicit def bitwiseImplicits(x: Long) = new BitwiseLongImplicits(x);
157 }
158
159 import Implicits.truish;
160
161 /*----- Cleanup assistant -------------------------------------------------*/
162
163 class Cleaner {
164   /* A helper class for avoiding deep nests of `try'/`finally'.
165    *
166    * Make a `Cleaner' instance CL at the start of your operation.  Apply it
167    * to blocks of code -- as CL { ACTION } -- as you proceed, to accumulate
168    * cleanup actions.   Finally, call CL.cleanup() to invoke the accumulated
169    * actions, in reverse order.
170    */
171
172   var cleanups: List[() => Unit] = Nil;
173   def apply(cleanup: => Unit) { cleanups +:= { () => cleanup; } }
174   def cleanup() { cleanups foreach { _() } }
175 }
176
177 def withCleaner[T](body: Cleaner => T): T = {
178   /* An easier way to use the `Cleaner' class.  Just
179    *
180    *    withCleaner { CL => BODY }
181    *
182    * The BODY can attach cleanup actions to the cleaner CL by saying
183    * CL { ACTION } as usual.  When the BODY exits, normally or otherwise, the
184    * cleanup actions are invoked in reverse order.
185    */
186
187   val cleaner = new Cleaner;
188   try { body(cleaner) }
189   finally { cleaner.cleanup(); }
190 }
191
192 def closing[T, U <: Closeable](thing: U)(body: U => T): T =
193   try { body(thing) }
194   finally { thing.close(); }
195
196 /*----- Control structures ------------------------------------------------*/
197
198 private case class ExitBlock[T](brand: Brand, result: T)
199         extends ControlThrowable;
200
201 def block[T](body: (T => Nothing) => T): T = {
202   /* block { exit[T] => ...; exit(x); ... }
203    *
204    * Execute the body until it calls the `exit' function or finishes.
205    * Annoyingly, Scala isn't clever enough to infer the return type, so
206    * you'll have to write it explicitly.
207    */
208
209   val mybrand = new Brand("block-exit");
210   try { body { result => throw new ExitBlock(mybrand, result) } }
211   catch {
212     case ExitBlock(brand, result) if brand eq mybrand =>
213       result.asInstanceOf[T]
214   }
215 }
216
217 def blockUnit(body: (=> Nothing) => Unit) {
218   /* blockUnit { exit => ...; exit; ... }
219    *
220    * Like `block'; it just saves you having to write `exit[Unit] => ...;
221    * exit(ok); ...'.
222    */
223
224   val mybrand = new Brand("block-exit");
225   try { body { throw new ExitBlock(mybrand, null) }; }
226   catch { case ExitBlock(brand, result) if brand eq mybrand => ok; }
227 }
228
229 def loop[T](body: (T => Nothing) => Unit): T = {
230   /* loop { exit[T] => ...; exit(x); ... }
231    *
232    * Repeatedly execute the body until it calls the `exit' function.
233    * Annoyingly, Scala isn't clever enough to infer the return type, so
234    * you'll have to write it explicitly.
235    */
236
237   block { exit => while (true) body(exit); unreachable }
238 }
239
240 def loopUnit(body: (=> Nothing) => Unit): Unit = {
241   /* loopUnit { exit => ...; exit; ... }
242    *
243    * Like `loop'; it just saves you having to write `exit[Unit] => ...;
244    * exit(()); ...'.
245    */
246
247   blockUnit { exit => while (true) body(exit); }
248 }
249
250 val BREAKS = new Breaks;
251 import BREAKS.{breakable, break};
252
253 /*----- Interruptably doing things ----------------------------------------*/
254
255 private class InterruptCatcher[T](body: => T, onWakeup: => Unit)
256         extends AbstractSelector(null) {
257   /* Hook onto the VM's thread interruption machinery.
258    *
259    * The `run' method is the only really interesting one.  It will run the
260    * BODY, returning its result; if the thread is interrupted during this
261    * time, ONWAKEUP is invoked for effect.  The expectation is that ONWAKEUP
262    * will somehow cause BODY to stop early.
263    *
264    * Credit for this hack goes to Nicholas Wilson: see
265    * <https://github.com/NWilson/javaInterruptHook>.
266    */
267
268   private def nope: Nothing =
269     { throw new UnsupportedOperationException("can't do that"); }
270   protected def implCloseSelector() { }
271   protected def register(chan: AbstractSelectableChannel,
272                                   ops: Int, att: Any): SelectionKey = nope;
273   def keys(): JSet[SelectionKey] = nope;
274   def selectedKeys(): JSet[SelectionKey] = nope;
275   def select(): Int = nope;
276   def select(millis: Long): Int = nope;
277   def selectNow(): Int = nope;
278
279   def run(): T = try {
280     begin();
281     val ret = body;
282     if (Thread.interrupted()) throw new InterruptedException;
283     ret
284   } finally {
285     end();
286   }
287   def wakeup(): Selector = { onWakeup; this }
288 }
289
290 class PendingInterruptable[T] private[tripe](body: => T) {
291   /* This class exists to provide the `onInterrupt THUNK' syntax. */
292
293   def onInterrupt(thunk: => Unit): T =
294     new InterruptCatcher(body, thunk).run();
295 }
296 def interruptably[T](body: => T) = {
297   /* interruptably { BODY } onInterrupt { THUNK }
298    *
299    * Execute BODY and return its result.  If the thread receives an
300    * interrupt -- or is already in an interrupted state -- execute THUNK for
301    * effect; it is expected to cause BODY to return expeditiously, and when
302    * the BODY completes, an `InterruptedException' is thrown.
303    */
304
305   new PendingInterruptable(body);
306 }
307
308 /*----- A gadget for fetching URLs ----------------------------------------*/
309
310 class URLFetchException(msg: String) extends Exception(msg);
311
312 trait URLFetchCallbacks {
313   def preflight(conn: URLConnection) { }
314   def write(buf: Array[Byte], n: Int, len: Long): Unit;
315   def done(win: Boolean) { }
316 }
317
318 def fetchURL(url: URL, cb: URLFetchCallbacks) {
319   /* Fetch the URL, feeding the data through the callbacks CB. */
320
321   withCleaner { clean =>
322     var win: Boolean = false; clean { cb.done(win); }
323
324     /* Set up the connection.  This isn't going to block, I think, and we
325      * need to use it in the interrupt handler.
326      */
327     val c = url.openConnection();
328
329     /* Java's default URL handlers don't respond to interrupts, so we have to
330      * take over this duty.
331      */
332     interruptably {
333       /* Run the caller's preflight check.  This must be done here, since it
334        * might well block while it discovers things like the content length.
335        */
336       cb.preflight(c);
337
338       /* Start fetching data. */
339       val in = c.getInputStream; clean { in.close(); }
340       val explen = c.getContentLength;
341
342       /* Read a buffer at a time, and give it to the callback.  Maintain a
343        * running total.
344        */
345       var len: Long = 0;
346       blockUnit { exit =>
347         for ((buf, n) <- blocks(in)) {
348           cb.write(buf, n, len);
349           len += n;
350           if (explen != -1 && len > explen) exit;
351         }
352       }
353
354       /* I can't find it documented anywhere that the existing machinery
355        * checks the received stream against the advertised content length.
356        * It doesn't hurt to check again, anyway.
357        */
358       if (explen != -1 && explen != len) {
359         throw new URLFetchException(
360           s"received $len /= $explen bytes from `$url'");
361       }
362
363       /* Glorious success is ours. */
364       win = true;
365     } onInterrupt {
366       /* Oh.  How do we do this? */
367
368       c match {
369         case c: HttpURLConnection =>
370           /* It's an HTTP connection (what happened to the case here?).
371            * HTTPS connections match too because they're a subclass.  Getting
372            * the input stream will block, but there's an easier way.
373            */
374           c.disconnect();
375
376         case _ =>
377           /* It's something else.  Let's hope that getting the input stream
378            * doesn't block.
379            */
380         c.getInputStream.close();
381       }
382     }
383   }
384 }
385
386 /*----- Threading things --------------------------------------------------*/
387
388 def thread(name: String, run: Boolean = true, daemon: Boolean = true)
389           (f: => Unit): Thread = {
390   /* Make a thread with a given name, and maybe start running it. */
391
392   val t = new Thread(new Runnable { def run() { f; } }, name);
393   if (daemon) t.setDaemon(true);
394   if (run) t.start();
395   t
396 }
397
398 class ValueThread[T](name: String, group: ThreadGroup = null,
399                      stacksz: Long = 0)(body: => T)
400         extends Thread(group, null, name, stacksz) {
401   private[this] var exc: Throwable = _;
402   private[this] var ret: T = _;
403
404   override def run() {
405     try { ret = body; }
406     catch { case e: Throwable => exc = e; }
407   }
408   def get: T =
409     if (isAlive) throw new IllegalArgumentException("still running");
410     else if (exc != null) throw exc;
411     else ret;
412 }
413 def valueThread[T](name: String, run: Boolean = true)
414                   (body: => T): ValueThread[T] = {
415   val t = new ValueThread(name)(body);
416   if (run) t.start();
417   t
418 }
419
420 /*----- Quoting and parsing tokens ----------------------------------------*/
421
422 def quoteTokens(v: Seq[String]): String = {
423   /* Return a string representing the token sequence V.
424    *
425    * The tokens are quoted as necessary.
426    */
427
428   val b = new StringBuilder;
429   var sep = false;
430   for (s <- v) {
431
432     /* If this isn't the first word, then write a separating space. */
433     if (!sep) sep = true;
434     else b += ' ';
435
436     /* Decide how to handle this token. */
437     if (s.length > 0 &&
438         (s forall { ch => (ch != ''' && ch != '"' && ch != '\\' &&
439                            !ch.isWhitespace) })) {
440       /* If this word is nonempty and contains no problematic characters,
441        * we can write it literally.
442        */
443
444       b ++= s;
445     } else {
446       /* Otherwise, we shall have to do this the hard way.  We could be
447        * cleverer about this, but it's not worth the effort.
448        */
449
450       b += '"';
451       s foreach { ch =>
452         if (ch == '"' || ch == '\\') b += '\\';
453         b += ch;
454       }
455       b += '"';
456     }
457   }
458   b.result
459 }
460
461 class InvalidQuotingException(msg: String) extends Exception(msg);
462
463 def nextToken(s: String, pos: Int = 0): Option[(String, Int)] = {
464   /* Parse the next token from a string S.
465    *
466    * If there is a token in S starting at or after index POS, then return
467    * it, and the index for the following token; otherwise return `None'.
468    */
469
470   val b = new StringBuilder;
471   val n = s.length;
472   var i = pos;
473   var q = 0;
474
475   /* Skip whitespace while we find the next token. */
476   while (i < n && s(i).isWhitespace) i += 1;
477
478   /* Maybe there just isn't anything to find. */
479   if (i >= n) return None;
480
481   /* There is something there.  Unpick the quoting and escaping. */
482   while (i < n && (q || !s(i).isWhitespace)) {
483     s(i) match {
484       case '\\' =>
485         if (i + 1 >= n) throw new InvalidQuotingException("trailing `\\'");
486         b += s(i + 1); i += 2;
487       case ch@('"' | ''') =>
488         if (!q) q = ch;
489         else if (q == ch) q = 0;
490         else b += ch;
491         i += 1;
492       case ch =>
493         b += ch;
494         i += 1;
495     }
496   }
497
498   /* Check that the quoting was valid. */
499   if (q) throw new InvalidQuotingException(s"unmatched `$q'");
500
501   /* Skip whitespace before the next token. */
502   while (i < n && s(i).isWhitespace) i += 1;
503
504   /* We're done. */
505   Some((b.result, i))
506 }
507
508 def splitTokens(s: String, pos: Int = 0): Seq[String] = {
509   /* Return all of the tokens in string S into tokens, starting at POS. */
510
511   val b = List.newBuilder[String];
512   var i = pos;
513
514   loopUnit { exit => nextToken(s, i) match {
515     case Some((w, j)) => b += w; i = j;
516     case None => exit;
517   } }
518   b.result
519 }
520
521 /*----- Hooks -------------------------------------------------------------*/
522
523 /* This is a really simple publisher/subscriber system.  The only slight
524  * tweak -- and the reason I'm not just using the Scala machinery -- is that
525  * being attached to a hook doesn't prevent the client from being garbage
526  * collected.
527  */
528
529 trait BaseHookClient[E] {
530   /* The minimal requirements for a hook client.  Honestly you should be
531    * using `HookClient' instead.
532    */
533
534   type H = Hook[E];                     // the type of hook we attach to
535   def hook(hk: H, evt: E);              // called with events from the hook
536 }
537
538 trait HookClient[E] extends BaseHookClient[E] {
539   /* The properly cooked hook client.  This keeps track of which hooks we're
540    * attached to so we can release them all easily.
541    */
542
543   private val hooks = new HashSet[H];
544   protected def attachHook(hk: H) { hk.addHookClient(this); hooks += hk; }
545   protected def detachHook(hk: H) { hk.rmHookClient(this); hooks -= hk; }
546   protected def detachAllHooks()
547     { for (hk <- hooks) hk.rmHookClient(this); hooks.clear(); }
548 }
549
550 trait Hook[E] {
551   type C = BaseHookClient[E];
552   private val clients = new WeakHashMap[C, Unit];
553   def addHookClient(c: C) { clients(c) = (); }
554   def rmHookClient(c: C) { clients -= c; }
555   protected def callHook(evt: E)
556     { for (c <- clients.keys) c.hook(this, evt); }
557 }
558
559 /*----- Fluid variables ---------------------------------------------------*/
560
561 object BaseFluid {
562   /* The multi-fluid `let' form is defined here so that it can access the
563    * `capture' method of individual fluids, but users should use the
564    * package-level veneer.
565    */
566
567   private[tripe] def let[U](fxs: (BaseFluid[T], T) forSome { type T }*)
568                            (body: => U): U = {
569       /* See the package-level `let' for details. */
570     val binds = for ((f, _) <- fxs) yield f.capture;
571     try { for ((f, x) <- fxs) f.v = x; body }
572     finally { for (b <- binds) b.restore(); }
573   }
574 }
575 def let[U](fxs: (BaseFluid[T], T) forSome { type T }*)(body: => U): U = {
576   /* let(F -> X, ...) { BODY }
577    *
578    * Evaluate BODY in a dynamic context where each fluid F is bound to the
579    * corresponding value X.
580    */
581
582   BaseFluid.let(fxs: _*)(body);
583 }
584
585 trait BaseFluid[T] {
586   /* The basic fluid protocol. */
587
588   override def toString(): String =
589     f"${getClass.getName}%s@${hashCode}%x($v%s)";
590
591   protected trait Binding {
592     /* A captured binding which can be restored later.  Implementing this is
593      * a subclass responsibility.
594      */
595
596     def restore();
597       /* Restore the fluid's state to the state captured here. */
598   }
599
600   /* Fetch and modify the current binding. */
601   def v: T;
602   def v_=(x: T);
603
604   protected def capture: Binding;
605     /* Capture and the current state of the fluid. */
606
607   def let[U](x: T)(body: => U): U = {
608     /* let(X) { BODY }
609      *
610      * Evaluate BODY in a dynamic context where the fluid is bound to the
611      * value X.
612      */
613
614     val b = capture;
615     try { v = x; body } finally { b.restore(); }
616   }
617 }
618
619 class SharedFluid[T](init: T) extends BaseFluid[T] {
620   /* A simple global fluid.  It's probably a mistake to try to access a
621    * `SharedFluid' from multiple threads without serious synchronization.
622    */
623
624   var v: T = init;
625   private class Binding(old: T) extends super.Binding
626     { def restore() { v = old; } }
627   protected def capture: super.Binding = new Binding(v);
628 }
629
630 class ThreadFluid[T](init: T) extends BaseFluid[T] {
631   /* A thread-aware fluid.  The top-level binding is truly global, shared by
632    * all threads, but `let'-bindings are thread-local.
633    */
634
635   private[this] var global: T = init;
636   private[this] var bound: ThreadLocal[Option[T]] = new ThreadLocal;
637   bound.set(None);
638
639   def v: T = bound.get match { case None => global; case Some(x) => x; };
640   def v_=(x: T) { bound.get match {
641     case None => global = x;
642     case _ => bound.set(Some(x));
643   } }
644
645   private class Binding(old: Option[T]) extends super.Binding
646     { def restore() { bound.set(old); } }
647   protected def capture: super.Binding = new Binding(bound.get);
648 }
649
650 /*----- Other random things -----------------------------------------------*/
651
652 trait LookaheadIterator[T] extends BufferedIterator[T] {
653   /* An iterator in terms of a single `maybe there's another item' function.
654    *
655    * It seems like every time I write an iterator in Scala, the only way to
656    * find out whether there's a next item, for `hasNext', is to actually try
657    * to fetch it.  So here's an iterator in terms of a function which goes
658    * off and maybe returns a next thing.  It turns out to be easy to satisfy
659    * the additional requirements for `BufferedIterator', so why not?
660    */
661
662   /* Subclass responsibility. */
663   protected def fetch(): Option[T];
664
665   /* The machinery.  `st' is `None' if there's no current item, null if we've
666    * actually hit the end, or `Some(x)' if the current item is x.
667    */
668   private[this] var st: Option[T] = None;
669   private[this] def peek() {
670     /* Arrange to have a current item. */
671     if (st == None) fetch() match {
672       case None => st = null;
673       case x@Some(_) => st = x;
674     }
675   }
676
677   /* The `BufferedIterator' protocol. */
678   override def hasNext: Boolean = { peek(); st != null }
679   override def head: T =
680     { peek(); if (st == null) throw new NoSuchElementException; st.get }
681   override def next(): T = { val it = head; st = None; it }
682 }
683
684 def bufferedReader(r: Reader): BufferedReader = r match {
685   case br: BufferedReader => br
686   case _ => new BufferedReader(r)
687 }
688
689 def lines(r: BufferedReader): BufferedIterator[String] =
690   new LookaheadIterator[String] {
691     /* Iterates over the lines of text in a `Reader' object. */
692     override protected def fetch() = Option(r.readLine());
693   }
694 def lines(r: Reader): BufferedIterator[String] = lines(bufferedReader(r));
695
696 def blocks(in: InputStream, blksz: Int):
697         BufferedIterator[(Array[Byte], Int)] =
698   /* Iterates over (possibly irregularly sized) blocks in a stream. */
699   new LookaheadIterator[(Array[Byte], Int)] {
700     val buf = new Array[Byte](blksz)
701     override protected def fetch() = {
702       val n = in.read(buf);
703       if (n < 0) None
704       else Some((buf, n))
705     }
706   }
707 def blocks(in: InputStream):
708         BufferedIterator[(Array[Byte], Int)] = blocks(in, 65536);
709
710 def blocks(in: BufferedReader, blksz: Int):
711         BufferedIterator[(Array[Char], Int)] =
712   /* Iterates over (possibly irregularly sized) blocks in a reader. */
713   new LookaheadIterator[(Array[Char], Int)] {
714     val buf = new Array[Char](blksz)
715     override protected def fetch() = {
716       val n = in.read(buf);
717       if (n < 0) None
718       else Some((buf, n))
719     }
720   }
721 def blocks(in: BufferedReader):
722         BufferedIterator[(Array[Char], Int)] = blocks(in, 65536);
723 def blocks(r: Reader, blksz: Int): BufferedIterator[(Array[Char], Int)] =
724   blocks(bufferedReader(r), blksz);
725 def blocks(r: Reader): BufferedIterator[(Array[Char], Int)] =
726   blocks(bufferedReader(r));
727
728 def oxford(conj: String, things: Seq[String]): String = things match {
729   case Seq() => "<nothing>"
730   case Seq(a) => a
731   case Seq(a, b) => s"$a $conj $b"
732   case Seq(a, tail@_*) =>
733     val sb = new StringBuilder;
734     sb ++= a; sb ++= ", ";
735     def iter(rest: Seq[String]) {
736       rest match {
737         case Seq() => unreachable;
738         case Seq(a) => sb ++= conj; sb += ' '; sb ++= a;
739         case Seq(a, tail@_*) => sb ++= a; sb ++= ", "; iter(tail);
740       }
741     }
742     iter(tail);
743     sb.result
744 }
745
746 val datefmt = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
747
748 def formatDuration(t: Int): String =
749   if (t < -1) "???"
750   else {
751     val (s, t1) = (t%60, t/60);
752     val (m, h) = (t1%60, t1/60);
753     if (h > 0) f"$h%d:$m%02d:$s%02d"
754     else f"$m%02d:$s%02d"
755   }
756
757 /*----- That's all, folks -------------------------------------------------*/
758
759 }