summaryrefslogtreecommitdiff
path: root/library/src/main/java
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-08-14 16:38:14 +0200
committerFlorian Dold <florian.dold@gmail.com>2019-08-14 16:38:14 +0200
commit66eb46b1a40c088b1096ba2f294796404b1a530c (patch)
tree785f6d979885157f4987972cf7246004c5c2cb9d /library/src/main/java
parentda736d8259331a8ef13bf4bbb10bbb8a5c0e5299 (diff)
downloadakono-66eb46b1a40c088b1096ba2f294796404b1a530c.tar.gz
akono-66eb46b1a40c088b1096ba2f294796404b1a530c.tar.bz2
akono-66eb46b1a40c088b1096ba2f294796404b1a530c.zip
messaging and module loading
Diffstat (limited to 'library/src/main/java')
-rw-r--r--library/src/main/java/akono/AkoniJni.kt39
-rw-r--r--library/src/main/java/akono/AkonoJni.kt208
2 files changed, 208 insertions, 39 deletions
diff --git a/library/src/main/java/akono/AkoniJni.kt b/library/src/main/java/akono/AkoniJni.kt
deleted file mode 100644
index 2210a7ef..00000000
--- a/library/src/main/java/akono/AkoniJni.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package akono
-
-import java.nio.ByteBuffer
-
-typealias AkonoNativePointer = ByteBuffer
-
-class AkonoJni {
- external fun stringFromJNI(): String
-
- private external fun evalJs(source: String, p: AkonoNativePointer): String
-
- private external fun destroyNative(b: AkonoNativePointer)
- private external fun initNative(nodeArgv: Array<out String>): AkonoNativePointer
-
- private external fun runNodeLoop(b: AkonoNativePointer)
-
- private external fun postMessageToNode(message: String, b: AkonoNativePointer)
-
- private external fun waitForMessageFromNode(b: AkonoNativePointer): String
-
- private var internalNativePointer: AkonoNativePointer
-
- fun evalJs(source: String): String = evalJs(source, internalNativePointer)
-
- @Override
- protected fun finalize() {
- destroyNative(internalNativePointer)
- }
-
- constructor(vararg nodeArgv: String) {
- internalNativePointer = initNative(nodeArgv)
- }
-
- companion object {
- init {
- System.loadLibrary("akono-jni")
- }
- }
-} \ No newline at end of file
diff --git a/library/src/main/java/akono/AkonoJni.kt b/library/src/main/java/akono/AkonoJni.kt
new file mode 100644
index 00000000..4a89a3f6
--- /dev/null
+++ b/library/src/main/java/akono/AkonoJni.kt
@@ -0,0 +1,208 @@
+package akono
+
+import android.util.Base64
+import android.util.Log
+import org.json.JSONObject
+import java.lang.Exception
+import java.nio.ByteBuffer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.LinkedBlockingDeque
+import kotlin.concurrent.thread
+
+typealias AkonoNativePointer = ByteBuffer
+
+data class ModuleResult(val path: String, val contents: String)
+
+class AkonoJni(vararg nodeArgv: String) {
+ private var getDataHandler: GetDataHandler? = null
+ private var messageHandler: MessageHandler? = null
+ private var loadModuleHandler: LoadModuleHandler? = null
+ private val initializedLatch = CountDownLatch(1)
+
+ private val workQueue = LinkedBlockingDeque<() -> Unit>()
+
+ private external fun evalJs(source: String, p: AkonoNativePointer): String
+ private external fun runNode(p: AkonoNativePointer)
+
+ private external fun makeCallbackNative(source: String, p: AkonoNativePointer)
+
+ private external fun destroyNative(b: AkonoNativePointer)
+ private external fun initNative(nodeArgv: Array<out String>): AkonoNativePointer
+ private external fun notifyNative(b: AkonoNativePointer)
+
+ private lateinit var internalNativePointer: AkonoNativePointer
+
+ private val jniThread: Thread
+
+ private var stopped = false
+
+ /**
+ * Schedule a block do be executed in the node thread.
+ */
+ private fun scheduleNodeThread(b: () -> Unit) {
+ initializedLatch.await()
+ workQueue.put(b)
+ notifyNative()
+ }
+
+ /**
+ * Called by node/v8 from its thread.
+ */
+ @Suppress("unused")
+ private fun internalOnNotify(type: String, payload: String) {
+ Log.i("myapp", "internalOnNotify called")
+ Log.i("myapp", "type: $type")
+ Log.i("myapp", "payload: $payload")
+ messageHandler?.handleMessage(payload)
+ }
+
+ /**
+ * Called by node/v8 from its thread.
+ */
+ @Suppress("unused")
+ private fun internalOnModuleLoad(loadInfoStr: String): String {
+ Log.i("myapp", "internalOnModuleLoad called")
+ Log.i("myapp", "loadInfoStr is $loadInfoStr")
+ try {
+ val loadInfo = JSONObject(loadInfoStr)
+ val request: String = loadInfo.getString("request")
+ Log.i("myapp", "request is $request")
+ val handler = loadModuleHandler
+ if (handler != null) {
+ val modResult = handler.loadModule(request, arrayOf()) ?: return "null"
+ val result = JSONObject()
+ result.put("path", modResult.path)
+ result.put("content", modResult.contents)
+ return result.toString()
+ } else {
+ Log.v("myapp", "no module load handler registered")
+ return "null"
+ }
+ } catch (e: Exception) {
+ Log.v("myapp", "exception during internalOnModuleLoad: $e")
+ return "null"
+ }
+ }
+
+ /**
+ * Called by node/v8 from its thread.
+ */
+ @Suppress("unused")
+ private fun internalOnGetData(what: String): String? {
+ Log.i("myapp", "internalOnGetData called for $what")
+ val data = getDataHandler?.handleGetData(what) ?: return null
+ return Base64.encodeToString(data, Base64.NO_WRAP)
+ }
+
+
+ fun notifyNative() {
+ initializedLatch.await()
+ notifyNative(internalNativePointer)
+ }
+
+ /**
+ * Schedule Node.JS to be run.
+ */
+ fun evalSimpleJs(source: String): String {
+ val latch = CountDownLatch(1)
+ var result: String? = null
+ scheduleNodeThread {
+ result = evalJs(source, internalNativePointer)
+ latch.countDown()
+ }
+ latch.await()
+ return result ?: throw Exception("invariant failed")
+ }
+
+ fun evalNodeCode(source: String) {
+ scheduleNodeThread {
+ makeCallbackNative(source, internalNativePointer)
+ }
+ }
+
+ /**
+ * Send a message to node, calling global.__akono_onMessage.
+ */
+ fun sendMessage(message: String) {
+ val encoded = Base64.encodeToString(message.toByteArray(), Base64.NO_WRAP)
+ val source = """
+ if (global.__akono_onMessage) {
+ const msg = (new Buffer('$encoded', 'base64')).toString('ascii');
+ global.__akono_onMessage(msg);
+ } else {
+ console.log("WARN: no __akono_onMessage defined");
+ }
+ """.trimIndent()
+ evalNodeCode(source)
+ }
+
+ /**
+ *
+ */
+ fun waitStopped(): Unit {
+ Log.i("myapp", "waiting for stop")
+ scheduleNodeThread {
+ stopped = true
+ }
+ jniThread.join()
+ return
+ }
+
+ /**
+ * Register a message handler that is called when the JavaScript code
+ * running in [runNodeJs] calls __akono_sendMessage
+ *
+ * Does not block.
+ */
+ fun setMessageHandler(handler: MessageHandler) {
+ this.messageHandler = handler
+ }
+
+ fun setLoadModuleHandler(handler: LoadModuleHandler) {
+ this.loadModuleHandler = handler
+ }
+
+ fun setGetDataHandler(handler: GetDataHandler) {
+ this.getDataHandler = handler
+ }
+
+ @Override
+ protected fun finalize() {
+ destroyNative(internalNativePointer)
+ }
+
+ init {
+ jniThread = thread {
+ internalNativePointer = initNative(nodeArgv)
+ initializedLatch.countDown()
+ while (true) {
+ runNode(internalNativePointer)
+ while (true) {
+ val w = workQueue.poll() ?: break
+ w()
+ }
+ if (stopped) {
+ break
+ }
+ }
+ }
+ }
+
+ companion object {
+ init {
+ System.loadLibrary("akono-jni")
+ }
+ }
+
+ interface MessageHandler {
+ fun handleMessage(message: String)
+ }
+
+ interface LoadModuleHandler {
+ fun loadModule(name: String, paths: Array<String>): ModuleResult?
+ }
+
+ interface GetDataHandler {
+ fun handleGetData(what: String): ByteArray?
+ }
+} \ No newline at end of file