tools: Fix missing cleanup
This commit is contained in:
+26
-17
@@ -7,10 +7,10 @@ import io.nekohasekai.libbox.Libbox
|
|||||||
import io.nekohasekai.libbox.NetworkQualityProgress
|
import io.nekohasekai.libbox.NetworkQualityProgress
|
||||||
import io.nekohasekai.libbox.NetworkQualityResult
|
import io.nekohasekai.libbox.NetworkQualityResult
|
||||||
import io.nekohasekai.libbox.NetworkQualityTestHandler
|
import io.nekohasekai.libbox.NetworkQualityTestHandler
|
||||||
|
import io.nekohasekai.libbox.NetworkQualityTestSession
|
||||||
import io.nekohasekai.sfa.R
|
import io.nekohasekai.sfa.R
|
||||||
import io.nekohasekai.sfa.compose.base.BaseViewModel
|
import io.nekohasekai.sfa.compose.base.BaseViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ data class NetworkQualityState(
|
|||||||
|
|
||||||
class NetworkQualityViewModel : BaseViewModel<NetworkQualityState, Nothing>() {
|
class NetworkQualityViewModel : BaseViewModel<NetworkQualityState, Nothing>() {
|
||||||
private var standaloneTest: io.nekohasekai.libbox.NetworkQualityTest? = null
|
private var standaloneTest: io.nekohasekai.libbox.NetworkQualityTest? = null
|
||||||
private var grpcJob: Job? = null
|
private var nqSession: NetworkQualityTestSession? = null
|
||||||
|
|
||||||
override fun createInitialState() = NetworkQualityState()
|
override fun createInitialState() = NetworkQualityState()
|
||||||
|
|
||||||
@@ -114,22 +114,23 @@ class NetworkQualityViewModel : BaseViewModel<NetworkQualityState, Nothing>() {
|
|||||||
val handler = createHandler()
|
val handler = createHandler()
|
||||||
|
|
||||||
if (vpnRunning) {
|
if (vpnRunning) {
|
||||||
grpcJob = viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
Libbox.newStandaloneCommandClient()
|
nqSession =
|
||||||
.startNetworkQualityTest(
|
Libbox.newStandaloneCommandClient()
|
||||||
configURL,
|
.startNetworkQualityTest(
|
||||||
outboundTag,
|
configURL,
|
||||||
serial,
|
outboundTag,
|
||||||
maxRuntimeSeconds,
|
serial,
|
||||||
http3,
|
maxRuntimeSeconds,
|
||||||
handler,
|
http3,
|
||||||
)
|
handler,
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (!currentState.isRunning) return@withContext
|
if (!currentState.isRunning) return@withContext
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
grpcJob = null
|
nqSession = null
|
||||||
sendError(e)
|
sendError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,13 +147,21 @@ class NetworkQualityViewModel : BaseViewModel<NetworkQualityState, Nothing>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun cancelTest() {
|
fun cancelTest() {
|
||||||
grpcJob?.cancel()
|
try {
|
||||||
grpcJob = null
|
nqSession?.close()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
nqSession = null
|
||||||
standaloneTest?.cancel()
|
standaloneTest?.cancel()
|
||||||
standaloneTest = null
|
standaloneTest = null
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
cancelTest()
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createHandler(): NetworkQualityTestHandler {
|
private fun createHandler(): NetworkQualityTestHandler {
|
||||||
return object : NetworkQualityTestHandler {
|
return object : NetworkQualityTestHandler {
|
||||||
override fun onProgress(progress: NetworkQualityProgress?) {
|
override fun onProgress(progress: NetworkQualityProgress?) {
|
||||||
@@ -196,7 +205,7 @@ class NetworkQualityViewModel : BaseViewModel<NetworkQualityState, Nothing>() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
standaloneTest = null
|
standaloneTest = null
|
||||||
grpcJob = null
|
nqSession = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +214,7 @@ class NetworkQualityViewModel : BaseViewModel<NetworkQualityState, Nothing>() {
|
|||||||
if (!currentState.isRunning) return@launch
|
if (!currentState.isRunning) return@launch
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
standaloneTest = null
|
standaloneTest = null
|
||||||
grpcJob = null
|
nqSession = null
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
sendErrorMessage(message)
|
sendErrorMessage(message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import io.nekohasekai.libbox.Libbox
|
|||||||
import io.nekohasekai.libbox.STUNTestHandler
|
import io.nekohasekai.libbox.STUNTestHandler
|
||||||
import io.nekohasekai.libbox.STUNTestProgress
|
import io.nekohasekai.libbox.STUNTestProgress
|
||||||
import io.nekohasekai.libbox.STUNTestResult
|
import io.nekohasekai.libbox.STUNTestResult
|
||||||
|
import io.nekohasekai.libbox.STUNTestSession
|
||||||
import io.nekohasekai.sfa.compose.base.BaseViewModel
|
import io.nekohasekai.sfa.compose.base.BaseViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ data class STUNTestState(
|
|||||||
|
|
||||||
class STUNTestViewModel : BaseViewModel<STUNTestState, Nothing>() {
|
class STUNTestViewModel : BaseViewModel<STUNTestState, Nothing>() {
|
||||||
private var standaloneTest: io.nekohasekai.libbox.STUNTest? = null
|
private var standaloneTest: io.nekohasekai.libbox.STUNTest? = null
|
||||||
private var grpcJob: Job? = null
|
private var stunSession: STUNTestSession? = null
|
||||||
|
|
||||||
override fun createInitialState() = STUNTestState()
|
override fun createInitialState() = STUNTestState()
|
||||||
|
|
||||||
@@ -60,15 +60,16 @@ class STUNTestViewModel : BaseViewModel<STUNTestState, Nothing>() {
|
|||||||
val handler = createHandler()
|
val handler = createHandler()
|
||||||
|
|
||||||
if (vpnRunning) {
|
if (vpnRunning) {
|
||||||
grpcJob = viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
Libbox.newStandaloneCommandClient()
|
stunSession =
|
||||||
.startSTUNTest(server, outboundTag, handler)
|
Libbox.newStandaloneCommandClient()
|
||||||
|
.startSTUNTest(server, outboundTag, handler)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (!currentState.isRunning) return@withContext
|
if (!currentState.isRunning) return@withContext
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
grpcJob = null
|
stunSession = null
|
||||||
sendError(e)
|
sendError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,13 +86,21 @@ class STUNTestViewModel : BaseViewModel<STUNTestState, Nothing>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun cancelTest() {
|
fun cancelTest() {
|
||||||
grpcJob?.cancel()
|
try {
|
||||||
grpcJob = null
|
stunSession?.close()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
stunSession = null
|
||||||
standaloneTest?.cancel()
|
standaloneTest?.cancel()
|
||||||
standaloneTest = null
|
standaloneTest = null
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
cancelTest()
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createHandler(): STUNTestHandler {
|
private fun createHandler(): STUNTestHandler {
|
||||||
return object : STUNTestHandler {
|
return object : STUNTestHandler {
|
||||||
override fun onProgress(progress: STUNTestProgress?) {
|
override fun onProgress(progress: STUNTestProgress?) {
|
||||||
@@ -126,7 +135,7 @@ class STUNTestViewModel : BaseViewModel<STUNTestState, Nothing>() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
standaloneTest = null
|
standaloneTest = null
|
||||||
grpcJob = null
|
stunSession = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +144,7 @@ class STUNTestViewModel : BaseViewModel<STUNTestState, Nothing>() {
|
|||||||
if (!currentState.isRunning) return@launch
|
if (!currentState.isRunning) return@launch
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
standaloneTest = null
|
standaloneTest = null
|
||||||
grpcJob = null
|
stunSession = null
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
sendErrorMessage(message)
|
sendErrorMessage(message)
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-49
@@ -1,13 +1,12 @@
|
|||||||
package io.nekohasekai.sfa.compose.screen.tools
|
package io.nekohasekai.sfa.compose.screen.tools
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.nekohasekai.libbox.CommandClient
|
|
||||||
import io.nekohasekai.libbox.Libbox
|
import io.nekohasekai.libbox.Libbox
|
||||||
import io.nekohasekai.libbox.TailscalePingHandler
|
import io.nekohasekai.libbox.TailscalePingHandler
|
||||||
import io.nekohasekai.libbox.TailscalePingResult
|
import io.nekohasekai.libbox.TailscalePingResult
|
||||||
|
import io.nekohasekai.libbox.TailscalePingSession
|
||||||
import io.nekohasekai.sfa.compose.base.BaseViewModel
|
import io.nekohasekai.sfa.compose.base.BaseViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@@ -23,8 +22,7 @@ data class TailscalePingState(
|
|||||||
|
|
||||||
class TailscalePingViewModel : BaseViewModel<TailscalePingState, Nothing>() {
|
class TailscalePingViewModel : BaseViewModel<TailscalePingState, Nothing>() {
|
||||||
private val maxHistorySize = 30
|
private val maxHistorySize = 30
|
||||||
private var commandClient: CommandClient? = null
|
private var pingSession: TailscalePingSession? = null
|
||||||
private var grpcJob: Job? = null
|
|
||||||
|
|
||||||
override fun createInitialState() = TailscalePingState()
|
override fun createInitialState() = TailscalePingState()
|
||||||
|
|
||||||
@@ -37,67 +35,62 @@ class TailscalePingViewModel : BaseViewModel<TailscalePingState, Nothing>() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val client = Libbox.newStandaloneCommandClient()
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
commandClient = client
|
|
||||||
|
|
||||||
grpcJob = viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
try {
|
||||||
client.startTailscalePing(
|
pingSession =
|
||||||
endpointTag,
|
Libbox.newStandaloneCommandClient()
|
||||||
peerIP,
|
.startTailscalePing(
|
||||||
object : TailscalePingHandler {
|
endpointTag,
|
||||||
override fun onPingResult(result: TailscalePingResult?) {
|
peerIP,
|
||||||
result ?: return
|
object : TailscalePingHandler {
|
||||||
viewModelScope.launch {
|
override fun onPingResult(result: TailscalePingResult?) {
|
||||||
if (!currentState.isRunning) return@launch
|
result ?: return
|
||||||
if (result.error.isNotEmpty()) return@launch
|
viewModelScope.launch {
|
||||||
val newHistory = currentState.latencyHistory.toMutableList()
|
if (!currentState.isRunning) return@launch
|
||||||
newHistory.add(result.latencyMs.toFloat())
|
if (result.error.isNotEmpty()) return@launch
|
||||||
if (newHistory.size > maxHistorySize) {
|
val newHistory = currentState.latencyHistory.toMutableList()
|
||||||
newHistory.removeFirstOrNull()
|
newHistory.add(result.latencyMs.toFloat())
|
||||||
|
if (newHistory.size > maxHistorySize) {
|
||||||
|
newHistory.removeFirstOrNull()
|
||||||
|
}
|
||||||
|
updateState {
|
||||||
|
copy(
|
||||||
|
hasResult = true,
|
||||||
|
latencyMs = result.latencyMs,
|
||||||
|
isDirect = result.isDirect,
|
||||||
|
derpRegionCode = result.derpRegionCode,
|
||||||
|
endpoint = result.endpoint,
|
||||||
|
latencyHistory = newHistory,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateState {
|
|
||||||
copy(
|
|
||||||
hasResult = true,
|
|
||||||
latencyMs = result.latencyMs,
|
|
||||||
isDirect = result.isDirect,
|
|
||||||
derpRegionCode = result.derpRegionCode,
|
|
||||||
endpoint = result.endpoint,
|
|
||||||
latencyHistory = newHistory,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(message: String?) {
|
override fun onError(message: String?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!currentState.isRunning) return@launch
|
if (!currentState.isRunning) return@launch
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
commandClient = null
|
pingSession = null
|
||||||
grpcJob = null
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (!currentState.isRunning) return@withContext
|
if (!currentState.isRunning) return@withContext
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
commandClient = null
|
pingSession = null
|
||||||
grpcJob = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopPing() {
|
fun stopPing() {
|
||||||
grpcJob?.cancel()
|
|
||||||
grpcJob = null
|
|
||||||
try {
|
try {
|
||||||
commandClient?.disconnect()
|
pingSession?.close()
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
commandClient = null
|
pingSession = null
|
||||||
updateState { copy(isRunning = false) }
|
updateState { copy(isRunning = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-22
@@ -3,10 +3,10 @@ package io.nekohasekai.sfa.compose.screen.tools
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.nekohasekai.libbox.Libbox
|
import io.nekohasekai.libbox.Libbox
|
||||||
import io.nekohasekai.libbox.TailscaleStatusHandler
|
import io.nekohasekai.libbox.TailscaleStatusHandler
|
||||||
|
import io.nekohasekai.libbox.TailscaleStatusSubscription
|
||||||
import io.nekohasekai.libbox.TailscaleStatusUpdate
|
import io.nekohasekai.libbox.TailscaleStatusUpdate
|
||||||
import io.nekohasekai.sfa.compose.base.BaseViewModel
|
import io.nekohasekai.sfa.compose.base.BaseViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
data class TailscalePeerData(
|
data class TailscalePeerData(
|
||||||
@@ -63,7 +63,7 @@ data class TailscaleStatusState(
|
|||||||
)
|
)
|
||||||
|
|
||||||
class TailscaleStatusViewModel : BaseViewModel<TailscaleStatusState, Nothing>() {
|
class TailscaleStatusViewModel : BaseViewModel<TailscaleStatusState, Nothing>() {
|
||||||
private var grpcJob: Job? = null
|
private var statusSubscription: TailscaleStatusSubscription? = null
|
||||||
|
|
||||||
override fun createInitialState() = TailscaleStatusState()
|
override fun createInitialState() = TailscaleStatusState()
|
||||||
|
|
||||||
@@ -71,39 +71,43 @@ class TailscaleStatusViewModel : BaseViewModel<TailscaleStatusState, Nothing>()
|
|||||||
if (currentState.isSubscribed) return
|
if (currentState.isSubscribed) return
|
||||||
updateState { copy(isSubscribed = true) }
|
updateState { copy(isSubscribed = true) }
|
||||||
|
|
||||||
grpcJob = viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
Libbox.newStandaloneCommandClient()
|
statusSubscription =
|
||||||
.subscribeTailscaleStatus(object : TailscaleStatusHandler {
|
Libbox.newStandaloneCommandClient()
|
||||||
override fun onStatusUpdate(status: TailscaleStatusUpdate) {
|
.subscribeTailscaleStatus(object : TailscaleStatusHandler {
|
||||||
val endpoints = convertUpdate(status)
|
override fun onStatusUpdate(status: TailscaleStatusUpdate) {
|
||||||
viewModelScope.launch {
|
val endpoints = convertUpdate(status)
|
||||||
if (!currentState.isSubscribed) return@launch
|
viewModelScope.launch {
|
||||||
updateState { copy(endpoints = endpoints) }
|
if (!currentState.isSubscribed) return@launch
|
||||||
|
updateState { copy(endpoints = endpoints) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(message: String) {
|
override fun onError(message: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!currentState.isSubscribed) return@launch
|
if (!currentState.isSubscribed) return@launch
|
||||||
updateState { copy(endpoints = emptyList(), isSubscribed = false) }
|
updateState { copy(endpoints = emptyList(), isSubscribed = false) }
|
||||||
grpcJob = null
|
statusSubscription = null
|
||||||
sendErrorMessage(message)
|
sendErrorMessage(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
updateState { copy(endpoints = emptyList(), isSubscribed = false) }
|
updateState { copy(endpoints = emptyList(), isSubscribed = false) }
|
||||||
grpcJob = null
|
statusSubscription = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancel() {
|
fun cancel() {
|
||||||
grpcJob?.cancel()
|
try {
|
||||||
grpcJob = null
|
statusSubscription?.close()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
statusSubscription = null
|
||||||
updateState { copy(endpoints = emptyList(), isSubscribed = false) }
|
updateState { copy(endpoints = emptyList(), isSubscribed = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user