/* * Copyright © 2017-2023 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, 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" } }