The work! The progress!
[tripe-android] / app.scala
diff --git a/app.scala b/app.scala
new file mode 100644 (file)
index 0000000..f82846c
--- /dev/null
+++ b/app.scala
@@ -0,0 +1,141 @@
+/* -*-scala-*-
+ *
+ * Setting up the Android environment
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Trivial IP Encryption (TrIPE) Android app.
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package uk.org.distorted.tripe; package object app {
+
+/*----- Imports -----------------------------------------------------------*/
+
+import java.io.{File, IOException};
+
+import scala.collection.mutable.HashMap;
+
+import android.content.Context; import Context.MODE_WORLD_READABLE;
+import android.os.Build; import Build.{CPU_ABI, CPU_ABI2};
+import android.util.Log;
+
+import sys.FileImplicits._;
+
+/*----- Regular expressions for parsing the `.installed file --------------*/
+
+private final val RX_COMMENT = """(?x) ^ \s* (?: \# .* )? $""".r;
+private final val RX_KEYVAL = """(?x) ^ \s*
+      ([-\w]+)
+      (?:\s+(?!=)|\s*=\s*)
+      (|\S|\S.*\S)
+      \s* $""".r;
+
+/*----- Main code ---------------------------------------------------------*/
+
+private final val TAG = "TrIPE";
+
+var root: File = null;
+
+private def install(ctx: Context, inst: HashMap[String, String]) {
+
+  /* First, figure out which ABIs are wanted on this device.  Unfortunately,
+   * the good way of doing this isn't available in our minimum API level, so
+   * we must use reflection.
+   */
+  val abis = try {
+    classOf[Build].getField("SUPPORTED_ABIS").get(null).
+      asInstanceOf[Array[String]]
+  } catch { case _: NoSuchFieldException =>
+    Array(CPU_ABI, CPU_ABI2) flatMap {
+      case null | "" => None
+      case s => Some(s)
+    }
+  }
+  Log.d(TAG, s"abis = ${abis.mkString(", ")}");
+
+  /* Clear out whatever might be there already. */
+  val bindir = root/"bin";
+  bindir.rmTree();
+  bindir.mkdir_!();
+
+  /* Now extract each of our binaries using the best available ABI. */
+  val assets = ctx.getAssets;
+  for (abi <- abis) {
+    val binsrc = s"bin/$abi";
+    for (base <- assets.list(binsrc)) {
+      val outfile = bindir/base;
+      if (!outfile.exists_!) {
+       Log.d(TAG, s"install: extract `$base' using abi `$abi'");
+       outfile.withOutput { out =>
+         closing(assets.open(s"$binsrc/$base")) { in =>
+           for ((buf, n) <- blocks(in)) out.write(buf, 0, n);
+         }
+       }
+      }
+      outfile.chmod_!(0x1ed);
+    }
+  }
+
+  /* Write out a new install file. */
+  val infofile = root/".installed";
+  val newinfofile = root/".installed.new";
+  newinfofile.withWriter { out =>
+    out.write(s"""### -*-conf-*-
+
+uuid = ${ctx.getString(R.string.auto_build_uuid)}
+""");
+  }
+  newinfofile.rename_!(infofile);
+}
+
+def setup(ctx: Context) {
+
+  /* Make our root directory and remember where it is. */
+  root = ctx.getFilesDir;
+  if (!root.isdir_!) {
+    throw new IOException("system failed to create `files' " +
+                         "(but didn't tell us)");
+  }
+
+  /* Find out which build, if any, corresponds to what's there already. */
+  val inst = HashMap[String, String]();
+  try { root/".installed" withReader { in =>
+    var lno = 1;
+    for (line <- lines(in)) {
+      line match {
+       case RX_COMMENT() => ok;
+       case RX_KEYVAL(k, v) => inst(k) = v;
+       case _ => Log.w(TAG, s".installed:$lno: ignored unparseable line");
+      }
+      lno += 1;
+    }
+  } } catch {
+    case e: IOException =>
+      Log.w(TAG, s".installed: I/O error: ${e.getMessage}");
+  }
+
+  /* If this doesn't match, then we have some work to do. */
+  if (inst.getOrElse("uuid", "<nothing>") !=
+      ctx.getString(R.string.auto_build_uuid))
+    install(ctx, inst);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
+
+}