coroutines: convert the rest
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
@@ -14,7 +14,6 @@ import androidx.databinding.Observable
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.AppListDialogFragmentBinding
|
||||
@@ -22,8 +21,8 @@ import com.wireguard.android.databinding.ObservableKeyedArrayList
|
||||
import com.wireguard.android.model.ApplicationData
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.requireTargetFragment
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -37,7 +36,7 @@ class AppListDialogFragment : DialogFragment() {
|
||||
private fun loadData() {
|
||||
val activity = activity ?: return
|
||||
val pm = activity.packageManager
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
try {
|
||||
val applicationData: MutableList<ApplicationData> = ArrayList()
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -57,12 +56,12 @@ class AppListDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
applicationData.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
withContext(Dispatchers.Main) {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
appData.clear()
|
||||
appData.addAll(applicationData)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
} catch (e: Throwable) {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
val error = ErrorMessages[e]
|
||||
val message = activity.getString(R.string.error_fetching_apps, error)
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
|
||||
@@ -17,13 +17,15 @@ import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.BaseActivity
|
||||
import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelListItemBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Base class for fragments that need to know the currently-selected tunnel. Only does anything when
|
||||
@@ -70,14 +72,14 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
is TunnelListItemBinding -> binding.item
|
||||
else -> return
|
||||
} ?: return
|
||||
Application.getBackendAsync().thenAccept { backend: Backend? ->
|
||||
if (backend is GoBackend) {
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
if (Application.getBackend() is GoBackend) {
|
||||
val intent = GoBackend.VpnService.prepare(view.context)
|
||||
if (intent != null) {
|
||||
pendingTunnel = tunnel
|
||||
pendingTunnelUp = checked
|
||||
startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION)
|
||||
return@thenAccept
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
setTunnelStateWithPermissionsResult(tunnel, checked)
|
||||
@@ -85,19 +87,22 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
}
|
||||
|
||||
private fun setTunnelStateWithPermissionsResult(tunnel: ObservableTunnel, checked: Boolean) {
|
||||
tunnel.setStateAsync(Tunnel.State.of(checked)).whenComplete { _, throwable ->
|
||||
if (throwable == null) return@whenComplete
|
||||
val error = ErrorMessages[throwable]
|
||||
val messageResId = if (checked) R.string.error_up else R.string.error_down
|
||||
val message = requireContext().getString(messageResId, error)
|
||||
val view = view
|
||||
if (view != null)
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(view.findViewById<View>(R.id.create_fab))
|
||||
.show()
|
||||
else
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, message, throwable)
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
try {
|
||||
tunnel.setStateAsync(Tunnel.State.of(checked))
|
||||
} catch (e: Throwable) {
|
||||
val error = ErrorMessages[e]
|
||||
val messageResId = if (checked) R.string.error_up else R.string.error_down
|
||||
val message = requireContext().getString(messageResId, error)
|
||||
val view = view
|
||||
if (view != null)
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(view.findViewById<View>(R.id.create_fab))
|
||||
.show()
|
||||
else
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Config
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
@@ -28,11 +31,12 @@ class ConfigNamingDialogFragment : DialogFragment() {
|
||||
private fun createTunnelAndDismiss() {
|
||||
binding?.let {
|
||||
val name = it.tunnelNameText.text.toString()
|
||||
Application.getTunnelManager().create(name, config).whenComplete { tunnel, throwable ->
|
||||
if (tunnel != null) {
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
try {
|
||||
Application.getTunnelManager().create(name, config)
|
||||
dismiss()
|
||||
} else {
|
||||
it.tunnelNameTextLayout.error = throwable.message
|
||||
} catch (e: Throwable) {
|
||||
it.tunnelNameTextLayout.error = e.message
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +53,7 @@ class ConfigNamingDialogFragment : DialogFragment() {
|
||||
val configBytes = configText!!.toByteArray(StandardCharsets.UTF_8)
|
||||
config = try {
|
||||
Config.parse(ByteArrayInputStream(configBytes))
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
when (e) {
|
||||
is BadConfigException, is IOException -> throw IllegalArgumentException("Invalid config passed to ${javaClass.simpleName}", e)
|
||||
else -> throw e
|
||||
|
||||
@@ -18,6 +18,9 @@ import com.wireguard.android.databinding.TunnelDetailPeerBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpRoot
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
@@ -79,7 +82,13 @@ class TunnelDetailFragment : BaseFragment() {
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
|
||||
binding ?: return
|
||||
binding!!.tunnel = newTunnel
|
||||
if (newTunnel == null) binding!!.config = null else newTunnel.configAsync.thenAccept { config -> binding!!.config = config }
|
||||
if (newTunnel == null) binding!!.config = null else GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
try {
|
||||
binding!!.config = newTunnel.getConfigAsync()
|
||||
} catch (_: Throwable) {
|
||||
binding!!.config = null
|
||||
}
|
||||
}
|
||||
lastState = Tunnel.State.TOGGLE
|
||||
updateStats()
|
||||
}
|
||||
@@ -105,30 +114,31 @@ class TunnelDetailFragment : BaseFragment() {
|
||||
val state = tunnel.state
|
||||
if (state != Tunnel.State.UP && lastState == state) return
|
||||
lastState = state
|
||||
tunnel.statisticsAsync.whenComplete { statistics, throwable ->
|
||||
if (throwable != null) {
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
try {
|
||||
val statistics = tunnel.getStatisticsAsync()
|
||||
for (i in 0 until binding!!.peersLayout.childCount) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
val publicKey = peer.item!!.publicKey
|
||||
val rx = statistics.peerRx(publicKey)
|
||||
val tx = statistics.peerTx(publicKey)
|
||||
if (rx == 0L && tx == 0L) {
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
continue
|
||||
}
|
||||
peer.transferText.text = requireContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx))
|
||||
peer.transferLabel.visibility = View.VISIBLE
|
||||
peer.transferText.visibility = View.VISIBLE
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
for (i in 0 until binding!!.peersLayout.childCount) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
}
|
||||
return@whenComplete
|
||||
}
|
||||
for (i in 0 until binding!!.peersLayout.childCount) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
val publicKey = peer.item!!.publicKey
|
||||
val rx = statistics.peerRx(publicKey)
|
||||
val tx = statistics.peerTx(publicKey)
|
||||
if (rx == 0L && tx == 0L) {
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
continue
|
||||
}
|
||||
peer.transferText.text = requireContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx))
|
||||
peer.transferLabel.visibility = View.VISIBLE
|
||||
peer.transferText.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,13 +25,16 @@ import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelEditorFragmentBinding
|
||||
import com.wireguard.android.fragment.AppListDialogFragment.AppSelectionListener
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.BiometricAuthenticator
|
||||
import com.wireguard.android.util.AdminKnobs
|
||||
import com.wireguard.android.util.BiometricAuthenticator
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.viewmodel.ConfigProxy
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpRoot
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
|
||||
import com.wireguard.config.Config
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Fragment for editing a WireGuard configuration.
|
||||
@@ -130,7 +133,7 @@ class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
|
||||
binding ?: return false
|
||||
val newConfig = try {
|
||||
binding!!.config!!.resolve()
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
val error = ErrorMessages[e]
|
||||
val tunnelName = if (tunnel == null) binding!!.name else tunnel!!.name
|
||||
val message = getString(R.string.config_save_error, tunnelName, error)
|
||||
@@ -138,20 +141,35 @@ class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
|
||||
Snackbar.make(binding!!.mainContainer, error, Snackbar.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
when {
|
||||
tunnel == null -> {
|
||||
Log.d(TAG, "Attempting to create new tunnel " + binding!!.name)
|
||||
val manager = Application.getTunnelManager()
|
||||
manager.create(binding!!.name!!, newConfig).whenComplete(this::onTunnelCreated)
|
||||
}
|
||||
tunnel!!.name != binding!!.name -> {
|
||||
Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name)
|
||||
tunnel!!.setNameAsync(binding!!.name!!).whenComplete { _, t -> onTunnelRenamed(tunnel!!, newConfig, t) }
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Attempting to save config of " + tunnel!!.name)
|
||||
tunnel!!.setConfigAsync(newConfig)
|
||||
.whenComplete { _, t -> onConfigSaved(tunnel!!, t) }
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
when {
|
||||
tunnel == null -> {
|
||||
Log.d(TAG, "Attempting to create new tunnel " + binding!!.name)
|
||||
val manager = Application.getTunnelManager()
|
||||
try {
|
||||
onTunnelCreated(manager.create(binding!!.name!!, newConfig), null)
|
||||
} catch (e: Throwable) {
|
||||
onTunnelCreated(null, e)
|
||||
}
|
||||
}
|
||||
tunnel!!.name != binding!!.name -> {
|
||||
Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name)
|
||||
try {
|
||||
tunnel!!.setNameAsync(binding!!.name!!)
|
||||
onTunnelRenamed(tunnel!!, newConfig, null)
|
||||
} catch (e: Throwable) {
|
||||
onTunnelRenamed(tunnel!!, newConfig, e)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Attempting to save config of " + tunnel!!.name)
|
||||
try {
|
||||
tunnel!!.setConfigAsync(newConfig)
|
||||
onConfigSaved(tunnel!!, null)
|
||||
} catch (e: Throwable) {
|
||||
onConfigSaved(tunnel!!, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -187,13 +205,18 @@ class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
|
||||
binding!!.config = ConfigProxy()
|
||||
if (tunnel != null) {
|
||||
binding!!.name = tunnel!!.name
|
||||
tunnel!!.configAsync.thenAccept(this::onConfigLoaded)
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
try {
|
||||
onConfigLoaded(tunnel!!.getConfigAsync())
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding!!.name = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTunnelCreated(newTunnel: ObservableTunnel, throwable: Throwable?) {
|
||||
private fun onTunnelCreated(newTunnel: ObservableTunnel?, throwable: Throwable?) {
|
||||
val message: String
|
||||
if (throwable == null) {
|
||||
tunnel = newTunnel
|
||||
@@ -219,7 +242,14 @@ class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
|
||||
Log.d(TAG, message)
|
||||
// Now save the rest of configuration changes.
|
||||
Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel!!.name)
|
||||
renamedTunnel.setConfigAsync(newConfig).whenComplete { _, t -> onConfigSaved(renamedTunnel, t) }
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
try {
|
||||
renamedTunnel.setConfigAsync(newConfig)
|
||||
onConfigSaved(renamedTunnel, null)
|
||||
} catch (e: Throwable) {
|
||||
onConfigSaved(renamedTunnel, e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.tunnel_rename_error, error)
|
||||
|
||||
@@ -36,7 +36,14 @@ import com.wireguard.android.widget.EdgeToEdge.setUpRoot
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
|
||||
import com.wireguard.android.widget.MultiselectableRelativeLayout
|
||||
import com.wireguard.config.Config
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStreamReader
|
||||
@@ -61,108 +68,96 @@ class TunnelListFragment : BaseFragment() {
|
||||
|
||||
// Config text is valid, now create the tunnel…
|
||||
newInstance(configText).show(parentFragmentManager, null)
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
onTunnelImportFinished(emptyList(), listOf<Throwable>(e))
|
||||
}
|
||||
}
|
||||
|
||||
private fun importTunnel(uri: Uri?) {
|
||||
val activity = activity
|
||||
if (activity == null || uri == null) {
|
||||
return
|
||||
}
|
||||
val contentResolver = activity.contentResolver
|
||||
|
||||
val futureTunnels = ArrayList<CompletableFuture<ObservableTunnel>>()
|
||||
val throwables = ArrayList<Throwable>()
|
||||
Application.getAsyncWorker().supplyAsync {
|
||||
val columns = arrayOf(OpenableColumns.DISPLAY_NAME)
|
||||
var name = ""
|
||||
contentResolver.query(uri, columns, null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
||||
name = cursor.getString(0)
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val activity = activity
|
||||
if (activity == null || uri == null) {
|
||||
return@withContext
|
||||
}
|
||||
}
|
||||
if (name.isEmpty()) {
|
||||
name = Uri.decode(uri.lastPathSegment)
|
||||
}
|
||||
var idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
require(idx < name.length - 1) { resources.getString(R.string.illegal_filename_error, name) }
|
||||
name = name.substring(idx + 1)
|
||||
}
|
||||
val isZip = name.toLowerCase(Locale.ROOT).endsWith(".zip")
|
||||
if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
require(isZip) { resources.getString(R.string.bad_extension_error) }
|
||||
}
|
||||
val contentResolver = activity.contentResolver
|
||||
val futureTunnels = ArrayList<Deferred<ObservableTunnel>>()
|
||||
val throwables = ArrayList<Throwable>()
|
||||
try {
|
||||
val columns = arrayOf(OpenableColumns.DISPLAY_NAME)
|
||||
var name = ""
|
||||
contentResolver.query(uri, columns, null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
||||
name = cursor.getString(0)
|
||||
}
|
||||
}
|
||||
if (name.isEmpty()) {
|
||||
name = Uri.decode(uri.lastPathSegment)
|
||||
}
|
||||
var idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
require(idx < name.length - 1) { resources.getString(R.string.illegal_filename_error, name) }
|
||||
name = name.substring(idx + 1)
|
||||
}
|
||||
val isZip = name.toLowerCase(Locale.ROOT).endsWith(".zip")
|
||||
if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
require(isZip) { resources.getString(R.string.bad_extension_error) }
|
||||
}
|
||||
|
||||
if (isZip) {
|
||||
ZipInputStream(contentResolver.openInputStream(uri)).use { zip ->
|
||||
val reader = BufferedReader(InputStreamReader(zip, StandardCharsets.UTF_8))
|
||||
var entry: ZipEntry?
|
||||
while (true) {
|
||||
entry = zip.nextEntry ?: break
|
||||
name = entry.name
|
||||
idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
if (idx >= name.length - 1) {
|
||||
continue
|
||||
if (isZip) {
|
||||
ZipInputStream(contentResolver.openInputStream(uri)).use { zip ->
|
||||
val reader = BufferedReader(InputStreamReader(zip, StandardCharsets.UTF_8))
|
||||
var entry: ZipEntry?
|
||||
while (true) {
|
||||
entry = zip.nextEntry ?: break
|
||||
name = entry.name
|
||||
idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
if (idx >= name.length - 1) {
|
||||
continue
|
||||
}
|
||||
name = name.substring(name.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
Config.parse(reader)
|
||||
} catch (e: Throwable) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}?.let {
|
||||
val nameCopy = name
|
||||
futureTunnels.add(async(SupervisorJob()) { Application.getTunnelManager().create(nameCopy, it) })
|
||||
}
|
||||
}
|
||||
name = name.substring(name.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
futureTunnels.add(async(SupervisorJob()) { Application.getTunnelManager().create(name, Config.parse(contentResolver.openInputStream(uri)!!)) })
|
||||
}
|
||||
|
||||
if (futureTunnels.isEmpty()) {
|
||||
if (throwables.size == 1) {
|
||||
throw throwables[0]
|
||||
} else {
|
||||
continue
|
||||
require(throwables.isNotEmpty()) { resources.getString(R.string.no_configs_error) }
|
||||
}
|
||||
}
|
||||
val tunnels = futureTunnels.mapNotNull {
|
||||
try {
|
||||
Config.parse(reader)
|
||||
} catch (e: Exception) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}?.let {
|
||||
futureTunnels.add(Application.getTunnelManager().create(name, it).toCompletableFuture())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
futureTunnels.add(
|
||||
Application.getTunnelManager().create(
|
||||
name,
|
||||
Config.parse(contentResolver.openInputStream(uri)!!)
|
||||
).toCompletableFuture()
|
||||
)
|
||||
}
|
||||
|
||||
if (futureTunnels.isEmpty()) {
|
||||
if (throwables.size == 1) {
|
||||
throw throwables[0]
|
||||
} else {
|
||||
require(throwables.isNotEmpty()) { resources.getString(R.string.no_configs_error) }
|
||||
}
|
||||
}
|
||||
CompletableFuture.allOf(*futureTunnels.toTypedArray())
|
||||
}.whenComplete { future, exception ->
|
||||
if (exception != null) {
|
||||
onTunnelImportFinished(emptyList(), listOf(exception))
|
||||
} else {
|
||||
future.whenComplete { _, _ ->
|
||||
val tunnels = mutableListOf<ObservableTunnel>()
|
||||
for (futureTunnel in futureTunnels) {
|
||||
val tunnel: ObservableTunnel? = try {
|
||||
futureTunnel.getNow(null)
|
||||
} catch (e: Exception) {
|
||||
it.await()
|
||||
} catch (e: Throwable) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}
|
||||
|
||||
if (tunnel != null) {
|
||||
tunnels.add(tunnel)
|
||||
}
|
||||
}
|
||||
onTunnelImportFinished(tunnels, throwables)
|
||||
withContext(Dispatchers.Main.immediate) { onTunnelImportFinished(tunnels, throwables) }
|
||||
} catch (e: Throwable) {
|
||||
withContext(Dispatchers.Main.immediate) { onTunnelImportFinished(emptyList(), listOf(e)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,7 +221,8 @@ class TunnelListFragment : BaseFragment() {
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
|
||||
binding ?: return
|
||||
Application.getTunnelManager().tunnels.thenAccept { tunnels ->
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
val tunnels = Application.getTunnelManager().getTunnels()
|
||||
if (newTunnel != null) viewForTunnel(newTunnel, tunnels).setSingleSelected(true)
|
||||
if (oldTunnel != null) viewForTunnel(oldTunnel, tunnels).setSingleSelected(false)
|
||||
}
|
||||
@@ -268,11 +264,10 @@ class TunnelListFragment : BaseFragment() {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
binding ?: return
|
||||
binding!!.fragment = this
|
||||
Application.getTunnelManager().tunnels.thenAccept { tunnels -> binding!!.tunnels = tunnels }
|
||||
val parent = this
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) { binding!!.tunnels = Application.getTunnelManager().getTunnels() }
|
||||
binding!!.rowConfigurationHandler = object : RowConfigurationHandler<TunnelListItemBinding, ObservableTunnel> {
|
||||
override fun onConfigureRow(binding: TunnelListItemBinding, item: ObservableTunnel, position: Int) {
|
||||
binding.fragment = parent
|
||||
binding.fragment = this@TunnelListFragment
|
||||
binding.root.setOnClickListener {
|
||||
if (actionMode == null) {
|
||||
selectedTunnel = item
|
||||
@@ -321,20 +316,24 @@ class TunnelListFragment : BaseFragment() {
|
||||
scaleX = 1f
|
||||
scaleY = 1f
|
||||
}
|
||||
Application.getTunnelManager().tunnels.thenAccept { tunnels ->
|
||||
val tunnelsToDelete = ArrayList<ObservableTunnel>()
|
||||
for (position in copyCheckedItems) tunnelsToDelete.add(tunnels[position])
|
||||
val futures = tunnelsToDelete.map { it.delete().toCompletableFuture() }.toTypedArray()
|
||||
CompletableFuture.allOf(*futures)
|
||||
.thenApply { futures.size }
|
||||
.whenComplete(this@TunnelListFragment::onTunnelDeletionFinished)
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
try {
|
||||
val tunnels = Application.getTunnelManager().getTunnels()
|
||||
val tunnelsToDelete = ArrayList<ObservableTunnel>()
|
||||
for (position in copyCheckedItems) tunnelsToDelete.add(tunnels[position])
|
||||
val futures = tunnelsToDelete.map { async(SupervisorJob()) { it.deleteAsync() } }
|
||||
onTunnelDeletionFinished(futures.awaitAll().size, null)
|
||||
} catch (e: Throwable) {
|
||||
onTunnelDeletionFinished(0, e)
|
||||
}
|
||||
}
|
||||
checkedItems.clear()
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.menu_action_select_all -> {
|
||||
Application.getTunnelManager().tunnels.thenAccept { tunnels ->
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
val tunnels = Application.getTunnelManager().getTunnels()
|
||||
for (i in 0 until tunnels.size) {
|
||||
setItemChecked(i, true)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user