From a2bfd75dee9b4952771a66c555ba1d920a072ae9 Mon Sep 17 00:00:00 2001
From: Pacien TRAN-GIRARD
Date: Tue, 11 Jul 2017 14:49:08 +0200
Subject: Implement create and join network dialogs + refactoring

---
 .../org/pacien/tincapp/activities/BaseActivity.kt  |  9 ++-
 .../pacien/tincapp/activities/ConfigureActivity.kt | 61 ++++++++++++++++-
 .../org/pacien/tincapp/activities/StartActivity.kt |  2 +-
 .../pacien/tincapp/activities/StatusActivity.kt    | 12 ++--
 .../java/org/pacien/tincapp/commands/Executor.kt   |  7 +-
 .../java/org/pacien/tincapp/commands/TincApp.kt    | 29 ++++++++
 .../java/org/pacien/tincapp/context/AppPaths.kt    |  4 ++
 .../java/org/pacien/tincapp/data/CidrAddress.kt    | 18 +++++
 .../tincapp/data/VpnInterfaceConfiguration.kt      | 78 ++++++++++++++++++++++
 .../java/org/pacien/tincapp/extensions/Android.kt  | 24 +++++++
 .../tincapp/extensions/ApacheConfiguration.kt      | 16 +++++
 .../java/org/pacien/tincapp/extensions/Java.kt     | 18 +++++
 .../pacien/tincapp/extensions/VpnServiceBuilder.kt | 51 ++++++++++++++
 .../org/pacien/tincapp/service/TincVpnService.kt   |  9 +--
 .../tincapp/service/VpnInterfaceConfiguraton.kt    | 62 -----------------
 .../tincapp/service/VpnServiceBuilderExtensions.kt | 46 -------------
 .../org/pacien/tincapp/utils/AndroidExtensions.kt  | 23 -------
 .../java/org/pacien/tincapp/utils/Functions.kt     | 11 ---
 18 files changed, 323 insertions(+), 157 deletions(-)
 create mode 100644 app/src/main/java/org/pacien/tincapp/commands/TincApp.kt
 create mode 100644 app/src/main/java/org/pacien/tincapp/data/CidrAddress.kt
 create mode 100644 app/src/main/java/org/pacien/tincapp/data/VpnInterfaceConfiguration.kt
 create mode 100644 app/src/main/java/org/pacien/tincapp/extensions/Android.kt
 create mode 100644 app/src/main/java/org/pacien/tincapp/extensions/ApacheConfiguration.kt
 create mode 100644 app/src/main/java/org/pacien/tincapp/extensions/Java.kt
 create mode 100644 app/src/main/java/org/pacien/tincapp/extensions/VpnServiceBuilder.kt
 delete mode 100644 app/src/main/java/org/pacien/tincapp/service/VpnInterfaceConfiguraton.kt
 delete mode 100644 app/src/main/java/org/pacien/tincapp/service/VpnServiceBuilderExtensions.kt
 delete mode 100644 app/src/main/java/org/pacien/tincapp/utils/AndroidExtensions.kt
 delete mode 100644 app/src/main/java/org/pacien/tincapp/utils/Functions.kt

(limited to 'app/src/main/java/org/pacien')

diff --git a/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
index 274e1ba..000320c 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
@@ -1,5 +1,6 @@
 package org.pacien.tincapp.activities
 
+import android.app.ProgressDialog
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
@@ -38,12 +39,18 @@ abstract class BaseActivity : AppCompatActivity() {
                         resources.getString(R.string.app_license) + "\n\n" +
                         AppInfo.all())
                 .setNeutralButton(R.string.action_open_project_website) { _, _ -> openWebsite(R.string.app_website_url) }
-                .setPositiveButton(R.string.action_close) { _, _ -> /* nop */ }
+                .setPositiveButton(R.string.action_close, dismiss)
                 .show()
     }
 
     protected fun openWebsite(@StringRes url: Int) = startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(resources.getString(url))))
     protected fun notify(@StringRes msg: Int) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show()
     protected fun notify(msg: String) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show()
+    protected fun showProgressDialog(@StringRes msg: Int): ProgressDialog = ProgressDialog.show(this, null, getString(msg), true, false)
+    protected fun showErrorDialog(msg: String): AlertDialog = AlertDialog.Builder(this)
+            .setTitle(R.string.title_error).setMessage(msg)
+            .setPositiveButton(R.string.action_close, dismiss).show()
+
+    protected val dismiss = { _: Any, _: Any -> /* nop */ }
 
 }
diff --git a/app/src/main/java/org/pacien/tincapp/activities/ConfigureActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/ConfigureActivity.kt
index 3590f99..d11809c 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/ConfigureActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/ConfigureActivity.kt
@@ -1,11 +1,20 @@
 package org.pacien.tincapp.activities
 
 import android.os.Bundle
+import android.support.annotation.StringRes
+import android.support.v7.app.AlertDialog
 import android.view.View
+import android.view.ViewGroup
+import android.widget.EditText
+import android.widget.FrameLayout
+import java8.util.concurrent.CompletableFuture
 import kotlinx.android.synthetic.main.base.*
 import kotlinx.android.synthetic.main.page_configure.*
 import org.pacien.tincapp.R
+import org.pacien.tincapp.commands.Tinc
+import org.pacien.tincapp.commands.TincApp
 import org.pacien.tincapp.context.AppPaths
+import org.pacien.tincapp.extensions.Java.exceptionallyAccept
 
 /**
  * @author pacien
@@ -19,13 +28,61 @@ class ConfigureActivity : BaseActivity() {
         writeContent()
     }
 
+    fun openGenerateConfDialog(@Suppress("UNUSED_PARAMETER") v: View) {
+        val netNameField = EditText(this)
+        netNameField.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
+        netNameField.setHint(R.string.field_net_name)
+
+        val dialogFrame = layoutInflater.inflate(R.layout.dialog_frame, main_content, false) as ViewGroup
+        dialogFrame.addView(netNameField)
+
+        AlertDialog.Builder(this).setTitle(R.string.title_new_network).setView(dialogFrame)
+                .setPositiveButton(R.string.action_create) { _, _ -> generateConf(netNameField.text.toString()) }
+                .setNegativeButton(R.string.action_cancel, dismiss).show()
+    }
+
+    fun openJoinNetworkDialog(@Suppress("UNUSED_PARAMETER") v: View) {
+        val netNameField = EditText(this)
+        netNameField.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
+        netNameField.setHint(R.string.field_net_name)
+
+        val joinUrlField = EditText(this)
+        joinUrlField.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
+        joinUrlField.setHint(R.string.field_invitation_url)
+
+        val dialogFrame = layoutInflater.inflate(R.layout.dialog_frame, main_content, false) as ViewGroup
+        dialogFrame.addView(netNameField)
+        dialogFrame.addView(joinUrlField)
+
+        AlertDialog.Builder(this).setTitle(R.string.title_join_network).setView(dialogFrame)
+                .setPositiveButton(R.string.action_join) { _, _ -> joinNetwork(netNameField.text.toString(), joinUrlField.text.toString()) }
+                .setNegativeButton(R.string.action_cancel, dismiss).show()
+    }
+
     private fun writeContent() {
         text_configuration_directory.text = AppPaths.confDir().absolutePath
         text_log_directory.text = AppPaths.cacheDir().absolutePath
         text_tinc_binary.text = AppPaths.tinc().absolutePath
     }
 
-    fun generateConf(@Suppress("UNUSED_PARAMETER") v: View) = notify("Not implemented yet")
-    fun joinNetwork(@Suppress("UNUSED_PARAMETER") v: View) = notify("Not implemented yet")
+    private fun generateConf(netName: String) = execAction(
+            R.string.message_generating_configuration,
+            Tinc.init(netName)
+                    .thenCompose { TincApp.removeScripts(netName) })
+
+    private fun joinNetwork(netName: String, url: String) = execAction(
+            R.string.message_joining_network,
+            Tinc.join(netName, url)
+                    .thenCompose { TincApp.removeScripts(netName) }
+                    .thenCompose { TincApp.generateIfaceCfg(netName) })
+
+    private fun execAction(@StringRes label: Int, action: CompletableFuture<Void>) {
+        showProgressDialog(label).let { progressDialog ->
+            action
+                    .whenComplete { _, _ -> progressDialog.dismiss() }
+                    .thenAccept { notify(R.string.message_network_configuration_created) }
+                    .exceptionallyAccept { runOnUiThread { showErrorDialog(it.cause!!.localizedMessage) } }
+        }
+    }
 
 }
diff --git a/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
index cecd474..6bd845d 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
@@ -13,9 +13,9 @@ import kotlinx.android.synthetic.main.base.*
 import kotlinx.android.synthetic.main.page_start.*
 import org.pacien.tincapp.R
 import org.pacien.tincapp.context.AppPaths
+import org.pacien.tincapp.extensions.Android.setElements
 import org.pacien.tincapp.service.TincVpnService
 import org.pacien.tincapp.utils.FileObserver
-import org.pacien.tincapp.utils.setElements
 
 /**
  * @author pacien
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 fb6ab73..a2c39dd 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt
@@ -17,10 +17,10 @@ import kotlinx.android.synthetic.main.fragment_network_status_header.*
 import kotlinx.android.synthetic.main.page_status.*
 import org.pacien.tincapp.R
 import org.pacien.tincapp.commands.Tinc
+import org.pacien.tincapp.data.VpnInterfaceConfiguration
+import org.pacien.tincapp.extensions.Android.setElements
+import org.pacien.tincapp.extensions.Android.setText
 import org.pacien.tincapp.service.TincVpnService
-import org.pacien.tincapp.service.VpnInterfaceConfiguration
-import org.pacien.tincapp.utils.setElements
-import org.pacien.tincapp.utils.setText
 import java.util.*
 import kotlin.concurrent.timerTask
 
@@ -105,8 +105,8 @@ class StatusActivity : BaseActivity(), AdapterView.OnItemClickListener, SwipeRef
 
     fun writeNetworkInfo(cfg: VpnInterfaceConfiguration) {
         text_network_name.text = TincVpnService.getCurrentNetName() ?: getString(R.string.value_none)
-        text_network_ip_addresses.setText(cfg.addresses.map { it.toString() })
-        text_network_routes.setText(cfg.routes.map { it.toString() })
+        text_network_ip_addresses.setText(cfg.addresses.map { it.toSlashSeparated() })
+        text_network_routes.setText(cfg.routes.map { it.toSlashSeparated() })
         text_network_dns_servers.setText(cfg.dnsServers)
         text_network_search_domains.setText(cfg.searchDomains)
         text_network_allow_bypass.text = getString(if (cfg.allowBypass) R.string.value_yes else R.string.value_no)
@@ -135,7 +135,7 @@ class StatusActivity : BaseActivity(), AdapterView.OnItemClickListener, SwipeRef
         private val REFRESH_RATE = 5000L
 
         fun getNodeNames(): CompletableFuture<List<String>> = when (TincVpnService.isConnected()) {
-            true -> Tinc.dumpNodes(TincVpnService.getCurrentNetName()!!).thenApply<List<String>> { it.map { it.substringBefore(" ") } }
+            true -> Tinc.dumpNodes(TincVpnService.getCurrentNetName()!!).thenApply<List<String>> { it.map { it.substringBefore(' ') } }
             false -> CompletableFuture.supplyAsync<List<String>> { 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 eccd2f9..38c2cb5 100644
--- a/app/src/main/java/org/pacien/tincapp/commands/Executor.kt
+++ b/app/src/main/java/org/pacien/tincapp/commands/Executor.kt
@@ -1,6 +1,8 @@
 package org.pacien.tincapp.commands
 
+import android.os.AsyncTask
 import java8.util.concurrent.CompletableFuture
+import java8.util.function.Supplier
 import java.io.BufferedReader
 import java.io.IOException
 import java.io.InputStream
@@ -35,10 +37,13 @@ internal object Executor {
             throw CommandExecutionException(e.message ?: "Could not start process.")
         }
 
-        return CompletableFuture.supplyAsync<List<String>> {
+        return supplyAsyncTask<List<String>> {
             if (proc.waitFor() == 0) read(proc.inputStream)
             else throw CommandExecutionException(read(proc.errorStream).lastOrNull() ?: "Non-zero exit status.")
         }
     }
 
+    fun runAsyncTask(r: () -> Unit) = CompletableFuture.runAsync(Runnable(r), AsyncTask.THREAD_POOL_EXECUTOR)!!
+    fun <U> supplyAsyncTask(s: () -> U) = CompletableFuture.supplyAsync(Supplier(s), AsyncTask.THREAD_POOL_EXECUTOR)!!
+
 }
diff --git a/app/src/main/java/org/pacien/tincapp/commands/TincApp.kt b/app/src/main/java/org/pacien/tincapp/commands/TincApp.kt
new file mode 100644
index 0000000..108b27d
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/commands/TincApp.kt
@@ -0,0 +1,29 @@
+package org.pacien.tincapp.commands
+
+import java8.util.concurrent.CompletableFuture
+import org.pacien.tincapp.commands.Executor.runAsyncTask
+import org.pacien.tincapp.context.AppPaths
+import org.pacien.tincapp.data.VpnInterfaceConfiguration
+
+/**
+ * @author pacien
+ */
+object TincApp {
+
+    private val SCRIPT_SUFFIXES = listOf("-up", "-down", "-created", "-accepted")
+    private val STATIC_SCRIPTS = listOf("tinc", "host", "subnet", "invitation").flatMap { s -> SCRIPT_SUFFIXES.map { s + it } }
+
+    private fun listScripts(netName: String) = AppPaths.confDir(netName).listFiles { f -> f.name in STATIC_SCRIPTS } +
+            AppPaths.hostsDir(netName).listFiles { f -> SCRIPT_SUFFIXES.none { f.name.endsWith(it) } }
+
+    fun removeScripts(netName: String): CompletableFuture<Void> = runAsyncTask {
+        listScripts(netName).forEach { it.delete() }
+    }
+
+    fun generateIfaceCfg(netName: String): CompletableFuture<Void> = runAsyncTask {
+        VpnInterfaceConfiguration
+                .fromInvitation(AppPaths.invitationFile(netName))
+                .write(AppPaths.netConfFile(netName))
+    }
+
+}
diff --git a/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt b/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt
index 078a81d..c745d4d 100644
--- a/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt
+++ b/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt
@@ -16,13 +16,17 @@ object AppPaths {
     private val PIDFILE_FORMAT = "tinc.%s.pid"
 
     private val NET_CONF_FILE = "network.conf"
+    private val NET_HOSTS_DIR = "hosts"
+    private val NET_INVITATION_FILE = "invitation-data"
 
     fun cacheDir() = App.getContext().externalCacheDir!!
     fun confDir() = App.getContext().getExternalFilesDir(null)!!
     fun binDir() = File(App.getContext().applicationInfo.nativeLibraryDir)
 
     fun confDir(netName: String) = File(confDir(), netName)
+    fun hostsDir(netName: String) = File(confDir(netName), NET_HOSTS_DIR)
     fun netConfFile(netName: String) = File(confDir(netName), NET_CONF_FILE)
+    fun invitationFile(netName: String) = File(confDir(netName), NET_INVITATION_FILE)
     fun logFile(netName: String) = File(cacheDir(), String.format(LOGFILE_FORMAT, netName))
     fun pidFile(netName: String) = File(App.getContext().cacheDir, String.format(PIDFILE_FORMAT, netName))
 
diff --git a/app/src/main/java/org/pacien/tincapp/data/CidrAddress.kt b/app/src/main/java/org/pacien/tincapp/data/CidrAddress.kt
new file mode 100644
index 0000000..bce9894
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/data/CidrAddress.kt
@@ -0,0 +1,18 @@
+package org.pacien.tincapp.data
+
+/**
+ * @author pacien
+ */
+data class CidrAddress(val address: String, val prefix: Int) {
+
+    companion object {
+
+        private val SEPARATOR = "/"
+
+        fun fromSlashSeparated(s: String) = CidrAddress(s.substringBefore(SEPARATOR), Integer.parseInt(s.substringAfter(SEPARATOR)))
+
+    }
+
+    fun toSlashSeparated() = address + SEPARATOR + prefix
+
+}
diff --git a/app/src/main/java/org/pacien/tincapp/data/VpnInterfaceConfiguration.kt b/app/src/main/java/org/pacien/tincapp/data/VpnInterfaceConfiguration.kt
new file mode 100644
index 0000000..70c8b96
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/data/VpnInterfaceConfiguration.kt
@@ -0,0 +1,78 @@
+package org.pacien.tincapp.data
+
+import org.apache.commons.configuration2.Configuration
+import org.apache.commons.configuration2.FileBasedConfiguration
+import org.apache.commons.configuration2.PropertiesConfiguration
+import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder
+import org.apache.commons.configuration2.builder.fluent.Configurations
+import org.apache.commons.configuration2.builder.fluent.Parameters
+import org.pacien.tincapp.extensions.ApacheConfiguration.getCidrList
+import org.pacien.tincapp.extensions.ApacheConfiguration.getIntList
+import org.pacien.tincapp.extensions.ApacheConfiguration.getStringList
+import org.pacien.tincapp.extensions.Java.applyIgnoringException
+import java.io.File
+
+/**
+ * @author pacien
+ */
+data class VpnInterfaceConfiguration(val addresses: List<CidrAddress> = emptyList(),
+                                     val routes: List<CidrAddress> = emptyList(),
+                                     val dnsServers: List<String> = emptyList(),
+                                     val searchDomains: List<String> = emptyList(),
+                                     val allowedApplications: List<String> = emptyList(),
+                                     val disallowedApplications: List<String> = emptyList(),
+                                     val allowedFamilies: List<Int> = emptyList(),
+                                     val allowBypass: Boolean = false,
+                                     val blocking: Boolean = false,
+                                     val mtu: Int? = null) {
+
+    companion object {
+
+        private val KEY_ADDRESSES = "Address"
+        private val KEY_ROUTES = "Route"
+        private val KEY_DNS_SERVERS = "DNSServer"
+        private val KEY_SEARCH_DOMAINS = "SearchDomain"
+        private val KEY_ALLOWED_APPLICATIONS = "AllowApplication"
+        private val KEY_DISALLOWED_APPLICATIONS = "DisallowApplication"
+        private val KEY_ALLOWED_FAMILIES = "AllowFamily"
+        private val KEY_ALLOW_BYPASS = "AllowBypass"
+        private val KEY_BLOCKING = "Blocking"
+        private val KEY_MTU = "MTU"
+
+        private val INVITATION_KEY_ADDRESSES = "Ifconfig"
+        private val INVITATION_KEY_ROUTES = "Route"
+
+        fun fromIfaceConfiguration(f: File) = fromIfaceConfiguration(Configurations().properties(f))
+        fun fromIfaceConfiguration(c: Configuration) = VpnInterfaceConfiguration(
+                c.getCidrList(KEY_ADDRESSES),
+                c.getCidrList(KEY_ROUTES),
+                c.getStringList(KEY_DNS_SERVERS),
+                c.getStringList(KEY_SEARCH_DOMAINS),
+                c.getStringList(KEY_ALLOWED_APPLICATIONS),
+                c.getStringList(KEY_DISALLOWED_APPLICATIONS),
+                c.getIntList(KEY_ALLOWED_FAMILIES),
+                c.getBoolean(KEY_ALLOW_BYPASS, false),
+                c.getBoolean(KEY_BLOCKING, false),
+                c.getInteger(KEY_MTU, null))
+
+        fun fromInvitation(f: File) = fromInvitation(Configurations().properties(f))
+        fun fromInvitation(c: Configuration) = VpnInterfaceConfiguration(
+                c.getStringList(INVITATION_KEY_ADDRESSES)
+                        .map { applyIgnoringException(CidrAddress.Companion::fromSlashSeparated, it) }
+                        .filterNotNull(),
+                c.getStringList(INVITATION_KEY_ROUTES)
+                        .map { it.substringBefore(' ') }
+                        .map { CidrAddress.fromSlashSeparated(it) })
+
+    }
+
+    fun write(f: File) = FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration::class.java)
+            .configure(Parameters().properties().setFile(f.apply { createNewFile() })).let { builder ->
+        builder.configuration.let { cfg ->
+            addresses.forEach { cfg.addProperty(KEY_ADDRESSES, it.toSlashSeparated()) }
+            routes.forEach { cfg.addProperty(KEY_ROUTES, it.toSlashSeparated()) }
+        }
+        builder.save()
+    }
+
+}
diff --git a/app/src/main/java/org/pacien/tincapp/extensions/Android.kt b/app/src/main/java/org/pacien/tincapp/extensions/Android.kt
new file mode 100644
index 0000000..f10c4c7
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/extensions/Android.kt
@@ -0,0 +1,24 @@
+package org.pacien.tincapp.extensions
+
+import android.widget.ArrayAdapter
+import android.widget.TextView
+import org.pacien.tincapp.R
+import org.pacien.tincapp.context.App
+
+/**
+ * @author pacien
+ */
+object Android {
+
+    fun <T> ArrayAdapter<T>.setElements(elems: Collection<T>) {
+        setNotifyOnChange(false)
+        clear()
+        addAll(elems)
+        notifyDataSetChanged()
+        setNotifyOnChange(true)
+    }
+
+    fun TextView.setText(list: List<String>) =
+            if (list.isNotEmpty()) text = list.joinToString("\n") else text = App.getContext().getString(R.string.value_none)
+
+}
diff --git a/app/src/main/java/org/pacien/tincapp/extensions/ApacheConfiguration.kt b/app/src/main/java/org/pacien/tincapp/extensions/ApacheConfiguration.kt
new file mode 100644
index 0000000..eb4bb47
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/extensions/ApacheConfiguration.kt
@@ -0,0 +1,16 @@
+package org.pacien.tincapp.extensions
+
+
+import org.apache.commons.configuration2.Configuration
+import org.pacien.tincapp.data.CidrAddress
+
+/**
+ * @author pacien
+ */
+object ApacheConfiguration {
+
+    fun Configuration.getStringList(key: String): List<String> = getList(String::class.java, key, emptyList())
+    fun Configuration.getCidrList(key: String): List<CidrAddress> = getStringList(key).map { CidrAddress.fromSlashSeparated(it) }
+    fun Configuration.getIntList(key: String): List<Int> = getList(Int::class.java, key, emptyList())
+
+}
diff --git a/app/src/main/java/org/pacien/tincapp/extensions/Java.kt b/app/src/main/java/org/pacien/tincapp/extensions/Java.kt
new file mode 100644
index 0000000..af3c1af
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/extensions/Java.kt
@@ -0,0 +1,18 @@
+package org.pacien.tincapp.extensions
+
+import java8.util.concurrent.CompletableFuture
+
+/**
+ * @author pacien
+ */
+object Java {
+
+    fun <T> CompletableFuture<T>.exceptionallyAccept(fn: (Throwable) -> Unit) = exceptionally { fn(it); null }!!
+
+    fun <A, R> applyIgnoringException(f: (A) -> R, x: A, alt: R? = null) = try {
+        f(x)
+    } catch (_: Exception) {
+        alt
+    }
+
+}
diff --git a/app/src/main/java/org/pacien/tincapp/extensions/VpnServiceBuilder.kt b/app/src/main/java/org/pacien/tincapp/extensions/VpnServiceBuilder.kt
new file mode 100644
index 0000000..870668a
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/extensions/VpnServiceBuilder.kt
@@ -0,0 +1,51 @@
+package org.pacien.tincapp.extensions
+
+import android.net.VpnService
+import org.pacien.tincapp.data.CidrAddress
+import org.pacien.tincapp.data.VpnInterfaceConfiguration
+import org.pacien.tincapp.extensions.Java.applyIgnoringException
+
+/**
+ * @author pacien
+ */
+object VpnServiceBuilder {
+
+    fun VpnService.Builder.addAddress(cidr: CidrAddress): VpnService.Builder = addAddress(cidr.address, cidr.prefix)
+    fun VpnService.Builder.addRoute(cidr: CidrAddress): VpnService.Builder = addRoute(cidr.address, cidr.prefix)
+    fun VpnService.Builder.allowBypass(allow: Boolean): VpnService.Builder = if (allow) allowBypass() else this
+    fun VpnService.Builder.overrideMtu(mtu: Int?): VpnService.Builder = if (mtu != null) setMtu(mtu) else this
+
+    fun VpnService.Builder.addAddresses(cidrList: List<CidrAddress>): VpnService.Builder =
+            cidrList.fold(this, { net, cidr -> net.addAddress(cidr) })
+
+    fun VpnService.Builder.addRoutes(cidrList: List<CidrAddress>): VpnService.Builder =
+            cidrList.fold(this, { net, cidr -> net.addRoute(cidr) })
+
+    fun VpnService.Builder.addDnsServers(dnsList: List<String>): VpnService.Builder =
+            dnsList.fold(this, { net, dns -> net.addDnsServer(dns) })
+
+    fun VpnService.Builder.addSearchDomains(domainList: List<String>): VpnService.Builder =
+            domainList.fold(this, { net, domain -> net.addSearchDomain(domain) })
+
+    fun VpnService.Builder.allowFamilies(familyList: List<Int>): VpnService.Builder =
+            familyList.fold(this, { net, family -> net.allowFamily(family) })
+
+    fun VpnService.Builder.addAllowedApplications(apps: List<String>): VpnService.Builder =
+            apps.fold(this, { net, app -> applyIgnoringException(net::addAllowedApplication, app, net)!! })
+
+    fun VpnService.Builder.addDisallowedApplications(apps: List<String>): VpnService.Builder =
+            apps.fold(this, { net, app -> applyIgnoringException(net::addDisallowedApplication, app, net)!! })
+
+    fun VpnService.Builder.applyCfg(cfg: VpnInterfaceConfiguration): VpnService.Builder = this
+            .addAddresses(cfg.addresses)
+            .addRoutes(cfg.routes)
+            .addDnsServers(cfg.dnsServers)
+            .addSearchDomains(cfg.searchDomains)
+            .addAllowedApplications(cfg.allowedApplications)
+            .addDisallowedApplications(cfg.disallowedApplications)
+            .allowFamilies(cfg.allowedFamilies)
+            .allowBypass(cfg.allowBypass)
+            .setBlocking(cfg.blocking)
+            .overrideMtu(cfg.mtu)
+
+}
diff --git a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
index 45f901b..e2eae00 100644
--- a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
+++ b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
@@ -9,10 +9,11 @@ import org.pacien.tincapp.commands.Tinc
 import org.pacien.tincapp.commands.Tincd
 import org.pacien.tincapp.context.App
 import org.pacien.tincapp.context.AppPaths
-import org.pacien.tincapp.utils.applyIgnoringException
+import org.pacien.tincapp.data.VpnInterfaceConfiguration
+import org.pacien.tincapp.extensions.Java.applyIgnoringException
+import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg
 import java.io.IOException
 
-
 /**
  * @author pacien
  */
@@ -46,9 +47,9 @@ class TincVpnService : VpnService() {
     private fun startVpn(netName: String) {
         if (isConnected()) onDestroy()
         TincVpnService.netName = netName
-        TincVpnService.interfaceCfg = VpnInterfaceConfiguration(AppPaths.netConfFile(netName))
+        TincVpnService.interfaceCfg = VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.netConfFile(netName))
 
-        val net = Builder().setSession(netName).apply(TincVpnService.interfaceCfg!!)
+        val net = Builder().setSession(netName).applyCfg(TincVpnService.interfaceCfg!!)
         applyIgnoringException(net::addDisallowedApplication, BuildConfig.APPLICATION_ID)
 
         try {
diff --git a/app/src/main/java/org/pacien/tincapp/service/VpnInterfaceConfiguraton.kt b/app/src/main/java/org/pacien/tincapp/service/VpnInterfaceConfiguraton.kt
deleted file mode 100644
index 50ccb20..0000000
--- a/app/src/main/java/org/pacien/tincapp/service/VpnInterfaceConfiguraton.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.pacien.tincapp.service
-
-/**
- * @author pacien
- */
-
-import org.apache.commons.configuration2.Configuration
-import org.apache.commons.configuration2.builder.fluent.Configurations
-import java.io.File
-
-private val KEY_ADDRESSES = "Address"
-private val KEY_ROUTES = "Route"
-private val KEY_DNS_SERVERS = "DNSServer"
-private val KEY_SEARCH_DOMAINS = "SearchDomain"
-private val KEY_ALLOWED_APPLICATIONS = "AllowApplication"
-private val KEY_DISALLOWED_APPLICATIONS = "DisallowApplication"
-private val KEY_ALLOWED_FAMILIES = "AllowFamily"
-private val KEY_ALLOW_BYPASS = "AllowBypass"
-private val KEY_BLOCKING = "Blocking"
-private val KEY_MTU = "MTU"
-
-private fun Configuration.getStringList(key: String): List<String> = getList(String::class.java, key, emptyList())
-private fun Configuration.getCidrList(key: String): List<CidrAddress> = getStringList(key).map { CidrAddress(it) }
-private fun Configuration.getIntList(key: String): List<Int> = getList(Int::class.java, key, emptyList())
-
-data class CidrAddress(val address: String, val prefix: Int) {
-    constructor(slashSeparated: String) :
-            this(slashSeparated.substringBefore(SEPARATOR), Integer.parseInt(slashSeparated.substringAfter(SEPARATOR)))
-
-    override fun toString() = address + SEPARATOR + prefix
-
-    companion object {
-        private val SEPARATOR = "/"
-    }
-}
-
-data class VpnInterfaceConfiguration(val addresses: List<CidrAddress> = emptyList(),
-                                     val routes: List<CidrAddress> = emptyList(),
-                                     val dnsServers: List<String> = emptyList(),
-                                     val searchDomains: List<String> = emptyList(),
-                                     val allowedApplications: List<String> = emptyList(),
-                                     val disallowedApplications: List<String> = emptyList(),
-                                     val allowedFamilies: List<Int> = emptyList(),
-                                     val allowBypass: Boolean = false,
-                                     val blocking: Boolean = false,
-                                     val mtu: Int? = null) {
-
-    constructor(cfg: Configuration) : this(
-            cfg.getCidrList(KEY_ADDRESSES),
-            cfg.getCidrList(KEY_ROUTES),
-            cfg.getStringList(KEY_DNS_SERVERS),
-            cfg.getStringList(KEY_SEARCH_DOMAINS),
-            cfg.getStringList(KEY_ALLOWED_APPLICATIONS),
-            cfg.getStringList(KEY_DISALLOWED_APPLICATIONS),
-            cfg.getIntList(KEY_ALLOWED_FAMILIES),
-            cfg.getBoolean(KEY_ALLOW_BYPASS, false),
-            cfg.getBoolean(KEY_BLOCKING, false),
-            cfg.getInteger(KEY_MTU, null))
-
-    constructor(cfgFile: File) : this(Configurations().properties(cfgFile))
-
-}
diff --git a/app/src/main/java/org/pacien/tincapp/service/VpnServiceBuilderExtensions.kt b/app/src/main/java/org/pacien/tincapp/service/VpnServiceBuilderExtensions.kt
deleted file mode 100644
index 22edff9..0000000
--- a/app/src/main/java/org/pacien/tincapp/service/VpnServiceBuilderExtensions.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.pacien.tincapp.service
-
-/**
- * @author pacien
- */
-
-import android.net.VpnService
-import org.pacien.tincapp.utils.applyIgnoringException
-
-fun VpnService.Builder.addAddress(cidr: CidrAddress): VpnService.Builder = addAddress(cidr.address, cidr.prefix)
-fun VpnService.Builder.addRoute(cidr: CidrAddress): VpnService.Builder = addRoute(cidr.address, cidr.prefix)
-fun VpnService.Builder.allowBypass(allow: Boolean): VpnService.Builder = if (allow) allowBypass() else this
-fun VpnService.Builder.overrideMtu(mtu: Int?): VpnService.Builder = if (mtu != null) setMtu(mtu) else this
-
-fun VpnService.Builder.addAddresses(cidrList: List<CidrAddress>): VpnService.Builder =
-        cidrList.fold(this, { net, cidr -> net.addAddress(cidr) })
-
-fun VpnService.Builder.addRoutes(cidrList: List<CidrAddress>): VpnService.Builder =
-        cidrList.fold(this, { net, cidr -> net.addRoute(cidr) })
-
-fun VpnService.Builder.addDnsServers(dnsList: List<String>): VpnService.Builder =
-        dnsList.fold(this, { net, dns -> net.addDnsServer(dns) })
-
-fun VpnService.Builder.addSearchDomains(domainList: List<String>): VpnService.Builder =
-        domainList.fold(this, { net, domain -> net.addSearchDomain(domain) })
-
-fun VpnService.Builder.allowFamilies(familyList: List<Int>): VpnService.Builder =
-        familyList.fold(this, { net, family -> net.allowFamily(family) })
-
-fun VpnService.Builder.addAllowedApplications(apps: List<String>): VpnService.Builder =
-        apps.fold(this, { net, app -> applyIgnoringException(net::addAllowedApplication, app, net)!! })
-
-fun VpnService.Builder.addDisallowedApplications(apps: List<String>): VpnService.Builder =
-        apps.fold(this, { net, app -> applyIgnoringException(net::addDisallowedApplication, app, net)!! })
-
-fun VpnService.Builder.apply(cfg: VpnInterfaceConfiguration): VpnService.Builder = this
-        .addAddresses(cfg.addresses)
-        .addRoutes(cfg.routes)
-        .addDnsServers(cfg.dnsServers)
-        .addSearchDomains(cfg.searchDomains)
-        .addAllowedApplications(cfg.allowedApplications)
-        .addDisallowedApplications(cfg.disallowedApplications)
-        .allowFamilies(cfg.allowedFamilies)
-        .allowBypass(cfg.allowBypass)
-        .setBlocking(cfg.blocking)
-        .overrideMtu(cfg.mtu)
diff --git a/app/src/main/java/org/pacien/tincapp/utils/AndroidExtensions.kt b/app/src/main/java/org/pacien/tincapp/utils/AndroidExtensions.kt
deleted file mode 100644
index 732b5b2..0000000
--- a/app/src/main/java/org/pacien/tincapp/utils/AndroidExtensions.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.pacien.tincapp.utils
-
-import android.widget.ArrayAdapter
-import android.widget.TextView
-import org.pacien.tincapp.R
-import org.pacien.tincapp.context.App
-
-/**
- * @author pacien
- */
-
-fun <T> ArrayAdapter<T>.setElements(list: Collection<T>) {
-    setNotifyOnChange(false)
-    clear()
-    addAll(list)
-    notifyDataSetChanged()
-    setNotifyOnChange(true)
-}
-
-fun TextView.setText(list: List<String>) {
-    if (list.isNotEmpty()) text = list.joinToString("\n")
-    else text = App.getContext().getString(R.string.value_none)
-}
diff --git a/app/src/main/java/org/pacien/tincapp/utils/Functions.kt b/app/src/main/java/org/pacien/tincapp/utils/Functions.kt
deleted file mode 100644
index 6ed77ce..0000000
--- a/app/src/main/java/org/pacien/tincapp/utils/Functions.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.pacien.tincapp.utils
-
-/**
- * @author pacien
- */
-
-fun <A, R> applyIgnoringException(f: (A) -> R, x: A, alt: R? = null) = try {
-    f(x)
-} catch (_: Exception) {
-    alt
-}
-- 
cgit v1.2.3