From 2c5673b187233a30a0dd284bc37436fc30596c66 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Sun, 9 Jul 2017 20:20:14 +0200 Subject: Make tincctl calls properly async --- app/build.gradle | 2 ++ .../pacien/tincapp/activities/StatusActivity.kt | 36 +++++++++++++--------- .../java/org/pacien/tincapp/commands/Executor.kt | 26 +++++++++++----- .../main/java/org/pacien/tincapp/commands/Tinc.kt | 30 ++++++++++++------ .../main/java/org/pacien/tincapp/commands/Tincd.kt | 2 -- 5 files changed, 61 insertions(+), 35 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c25ab83..2d31df9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,6 +51,8 @@ dependencies { exclude group: 'commons-logging', module: 'commons-logging' } + compile 'net.sourceforge.streamsupport:streamsupport-cfuture:1.5.5' + compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" } diff --git a/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt index 44f4f89..fb6ab73 100644 --- a/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt +++ b/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt @@ -10,6 +10,7 @@ import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.TextView +import java8.util.concurrent.CompletableFuture import kotlinx.android.synthetic.main.base.* import kotlinx.android.synthetic.main.dialog_text_monopsace.view.* import kotlinx.android.synthetic.main.fragment_network_status_header.* @@ -78,24 +79,28 @@ class StatusActivity : BaseActivity(), AdapterView.OnItemClickListener, SwipeRef } override fun onRefresh() { - val nodes = getNodeNames() - runOnUiThread { - nodeListAdapter?.setElements(nodes) - node_list_wrapper.isRefreshing = false - if (!TincVpnService.isConnected()) openStartActivity() + getNodeNames().thenAccept { + runOnUiThread { + nodeListAdapter?.setElements(it) + node_list_wrapper.isRefreshing = false + if (!TincVpnService.isConnected()) openStartActivity() + } } } override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { val nodeName = (view as TextView).text.toString() val dialogTextView = layoutInflater.inflate(R.layout.dialog_text_monopsace, main_content, false) - dialogTextView.dialog_text_monospace.text = Tinc.info(TincVpnService.getCurrentNetName()!!, nodeName) - - AlertDialog.Builder(this) - .setTitle(R.string.title_node_info) - .setView(dialogTextView) - .setPositiveButton(R.string.action_close) { _, _ -> /* nop */ } - .show() + Tinc.info(TincVpnService.getCurrentNetName()!!, nodeName).thenAccept { + runOnUiThread { + dialogTextView.dialog_text_monospace.text = it + AlertDialog.Builder(this) + .setTitle(R.string.title_node_info) + .setView(dialogTextView) + .setPositiveButton(R.string.action_close) { _, _ -> /* nop */ } + .show() + } + } } fun writeNetworkInfo(cfg: VpnInterfaceConfiguration) { @@ -129,9 +134,10 @@ class StatusActivity : BaseActivity(), AdapterView.OnItemClickListener, SwipeRef companion object { private val REFRESH_RATE = 5000L - fun getNodeNames() = - if (TincVpnService.isConnected()) Tinc.dumpNodes(TincVpnService.getCurrentNetName()!!).map { it.substringBefore(" ") } - else emptyList() + fun getNodeNames(): CompletableFuture> = when (TincVpnService.isConnected()) { + true -> Tinc.dumpNodes(TincVpnService.getCurrentNetName()!!).thenApply> { it.map { it.substringBefore(" ") } } + false -> CompletableFuture.supplyAsync> { emptyList() } + } } } diff --git a/app/src/main/java/org/pacien/tincapp/commands/Executor.kt b/app/src/main/java/org/pacien/tincapp/commands/Executor.kt index c93de64..eccd2f9 100644 --- a/app/src/main/java/org/pacien/tincapp/commands/Executor.kt +++ b/app/src/main/java/org/pacien/tincapp/commands/Executor.kt @@ -1,7 +1,9 @@ package org.pacien.tincapp.commands +import java8.util.concurrent.CompletableFuture import java.io.BufferedReader import java.io.IOException +import java.io.InputStream import java.io.InputStreamReader /** @@ -9,6 +11,8 @@ import java.io.InputStreamReader */ internal object Executor { + class CommandExecutionException(msg: String) : Exception(msg) + init { System.loadLibrary("exec") } @@ -18,17 +22,23 @@ internal object Executor { */ private external fun forkExec(argcv: Array): Int - @Throws(IOException::class) + private fun read(stream: InputStream) = BufferedReader(InputStreamReader(stream)).readLines() + fun forkExec(cmd: Command) { - if (forkExec(cmd.asArray()) == -1) - throw IOException() + if (forkExec(cmd.asArray()) == -1) throw CommandExecutionException("Could not fork child process.") } - @Throws(IOException::class) - fun call(cmd: Command): List { - val proc = ProcessBuilder(cmd.asList()).start() - val outputReader = BufferedReader(InputStreamReader(proc.inputStream)) - return outputReader.readLines() + fun call(cmd: Command): CompletableFuture> { + val proc = try { + ProcessBuilder(cmd.asList()).start() + } catch (e: IOException) { + throw CommandExecutionException(e.message ?: "Could not start process.") + } + + return CompletableFuture.supplyAsync> { + if (proc.waitFor() == 0) read(proc.inputStream) + else throw CommandExecutionException(read(proc.errorStream).lastOrNull() ?: "Non-zero exit status.") + } } } diff --git a/app/src/main/java/org/pacien/tincapp/commands/Tinc.kt b/app/src/main/java/org/pacien/tincapp/commands/Tinc.kt index 9b57233..120525d 100644 --- a/app/src/main/java/org/pacien/tincapp/commands/Tinc.kt +++ b/app/src/main/java/org/pacien/tincapp/commands/Tinc.kt @@ -1,7 +1,7 @@ package org.pacien.tincapp.commands +import java8.util.concurrent.CompletableFuture import org.pacien.tincapp.context.AppPaths -import java.io.IOException /** * @author pacien @@ -13,19 +13,29 @@ object Tinc { .withOption("config", AppPaths.confDir(netName).absolutePath) .withOption("pidfile", AppPaths.pidFile(netName).absolutePath) - @Throws(IOException::class) - fun stop(netName: String) { - Executor.call(newCommand(netName).withArguments("stop")) - } + fun stop(netName: String): CompletableFuture = + Executor.call(newCommand(netName).withArguments("stop")) + .thenApply { } - @Throws(IOException::class) - fun dumpNodes(netName: String, reachable: Boolean = false): List = + fun dumpNodes(netName: String, reachable: Boolean = false): CompletableFuture> = Executor.call( if (reachable) newCommand(netName).withArguments("dump", "reachable", "nodes") else newCommand(netName).withArguments("dump", "nodes")) - @Throws(IOException::class) - fun info(netName: String, node: String): String = - Executor.call(newCommand(netName).withArguments("info", node)).joinToString("\n") + fun info(netName: String, node: String): CompletableFuture = + Executor.call(newCommand(netName).withArguments("info", node)) + .thenApply { it.joinToString("\n") } + + fun init(netName: String): CompletableFuture = + Executor.call(Command(AppPaths.tinc().absolutePath) + .withOption("config", AppPaths.confDir(netName).absolutePath) + .withArguments("init", netName)) + .thenApply { it.joinToString("\n") } + + fun join(netName: String, invitationUrl: String): CompletableFuture = + Executor.call(Command(AppPaths.tinc().absolutePath) + .withOption("config", AppPaths.confDir(netName).absolutePath) + .withArguments("join", invitationUrl)) + .thenApply { it.joinToString("\n") } } diff --git a/app/src/main/java/org/pacien/tincapp/commands/Tincd.kt b/app/src/main/java/org/pacien/tincapp/commands/Tincd.kt index 19ebfbd..db113cc 100644 --- a/app/src/main/java/org/pacien/tincapp/commands/Tincd.kt +++ b/app/src/main/java/org/pacien/tincapp/commands/Tincd.kt @@ -1,14 +1,12 @@ package org.pacien.tincapp.commands import org.pacien.tincapp.context.AppPaths -import java.io.IOException /** * @author pacien */ object Tincd { - @Throws(IOException::class) fun start(netName: String, fd: Int) { Executor.forkExec(Command(AppPaths.tincd().absolutePath) .withOption("no-detach") -- cgit v1.2.3