GlobalScope has numerous problems[1] that make it unfit for use in most applications and making it behave correctly requires an excessive amount of verbosity that's alleviated simply by using any other scope. Since we run multiple operations in the context of the application's lifecycle, introduce a new scope that is created when our application is, and cancelled upon its termination. While at it, make the scope default to Dispatchers.IO to reduce pressure on the UI event loop. Tasks requiring access to the UI thread appropriately switch context making the change completely safe. 1: https://medium.com/@elizarov/the-reason-to-avoid-globalscope-835337445abc Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
147 lines
4.3 KiB
Kotlin
147 lines
4.3 KiB
Kotlin
/*
|
|
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
package com.wireguard.android.model
|
|
|
|
import android.util.Log
|
|
import androidx.databinding.BaseObservable
|
|
import androidx.databinding.Bindable
|
|
import com.wireguard.android.BR
|
|
import com.wireguard.android.backend.Statistics
|
|
import com.wireguard.android.backend.Tunnel
|
|
import com.wireguard.android.databinding.Keyed
|
|
import com.wireguard.android.util.applicationScope
|
|
import com.wireguard.config.Config
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.withContext
|
|
|
|
/**
|
|
* Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
|
|
*/
|
|
class ObservableTunnel internal constructor(
|
|
private val manager: TunnelManager,
|
|
private var name: String,
|
|
config: Config?,
|
|
state: Tunnel.State
|
|
) : BaseObservable(), Keyed<String>, Tunnel {
|
|
override val key
|
|
get() = name
|
|
|
|
@Bindable
|
|
override fun getName() = name
|
|
|
|
suspend fun setNameAsync(name: String): String = withContext(Dispatchers.Main.immediate) {
|
|
if (name != this@ObservableTunnel.name)
|
|
manager.setTunnelName(this@ObservableTunnel, name)
|
|
else
|
|
this@ObservableTunnel.name
|
|
}
|
|
|
|
fun onNameChanged(name: String): String {
|
|
this.name = name
|
|
notifyPropertyChanged(BR.name)
|
|
return name
|
|
}
|
|
|
|
|
|
@get:Bindable
|
|
var state = state
|
|
private set
|
|
|
|
override fun onStateChange(newState: Tunnel.State) {
|
|
onStateChanged(newState)
|
|
}
|
|
|
|
fun onStateChanged(state: Tunnel.State): Tunnel.State {
|
|
if (state != Tunnel.State.UP) onStatisticsChanged(null)
|
|
this.state = state
|
|
notifyPropertyChanged(BR.state)
|
|
return state
|
|
}
|
|
|
|
suspend fun setStateAsync(state: Tunnel.State): Tunnel.State = withContext(Dispatchers.Main.immediate) {
|
|
if (state != this@ObservableTunnel.state)
|
|
manager.setTunnelState(this@ObservableTunnel, state)
|
|
else
|
|
this@ObservableTunnel.state
|
|
}
|
|
|
|
|
|
@get:Bindable
|
|
var config = config
|
|
get() {
|
|
if (field == null)
|
|
// Opportunistically fetch this if we don't have a cached one, and rely on data bindings to update it eventually
|
|
applicationScope.launch {
|
|
try {
|
|
manager.getTunnelConfig(this@ObservableTunnel)
|
|
} catch (e: Throwable) {
|
|
Log.e(TAG, Log.getStackTraceString(e))
|
|
}
|
|
}
|
|
return field
|
|
}
|
|
private set
|
|
|
|
suspend fun getConfigAsync(): Config = withContext(Dispatchers.Main.immediate) {
|
|
config ?: manager.getTunnelConfig(this@ObservableTunnel)
|
|
}
|
|
|
|
suspend fun setConfigAsync(config: Config): Config = withContext(Dispatchers.Main.immediate) {
|
|
this@ObservableTunnel.config.let {
|
|
if (config != it)
|
|
manager.setTunnelConfig(this@ObservableTunnel, config)
|
|
else
|
|
it
|
|
}
|
|
}
|
|
|
|
fun onConfigChanged(config: Config?): Config? {
|
|
this.config = config
|
|
notifyPropertyChanged(BR.config)
|
|
return config
|
|
}
|
|
|
|
|
|
@get:Bindable
|
|
var statistics: Statistics? = null
|
|
get() {
|
|
if (field == null || field?.isStale != false)
|
|
// Opportunistically fetch this if we don't have a cached one, and rely on data bindings to update it eventually
|
|
applicationScope.launch {
|
|
try {
|
|
manager.getTunnelStatistics(this@ObservableTunnel)
|
|
} catch (e: Throwable) {
|
|
Log.e(TAG, Log.getStackTraceString(e))
|
|
}
|
|
}
|
|
return field
|
|
}
|
|
private set
|
|
|
|
suspend fun getStatisticsAsync(): Statistics = withContext(Dispatchers.Main.immediate) {
|
|
statistics.let {
|
|
if (it == null || it.isStale)
|
|
manager.getTunnelStatistics(this@ObservableTunnel)
|
|
else
|
|
it
|
|
}
|
|
}
|
|
|
|
fun onStatisticsChanged(statistics: Statistics?): Statistics? {
|
|
this.statistics = statistics
|
|
notifyPropertyChanged(BR.statistics)
|
|
return statistics
|
|
}
|
|
|
|
|
|
suspend fun deleteAsync() = manager.delete(this)
|
|
|
|
|
|
companion object {
|
|
private const val TAG = "WireGuard/ObservableTunnel"
|
|
}
|
|
}
|