Refactor command client

This commit is contained in:
世界 2023-08-15 18:13:45 +08:00
parent c2c3db6835
commit 8147ec71da
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
4 changed files with 148 additions and 111 deletions

View file

@ -16,15 +16,9 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import go.Seq
import io.nekohasekai.libbox.CommandClient
import io.nekohasekai.libbox.CommandClientHandler
import io.nekohasekai.libbox.CommandClientOptions
import io.nekohasekai.libbox.Libbox
import io.nekohasekai.libbox.OutboundGroup
import io.nekohasekai.libbox.OutboundGroupItem
import io.nekohasekai.libbox.OutboundGroupIterator
import io.nekohasekai.libbox.StatusMessage
import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.constant.Status
import io.nekohasekai.sfa.databinding.FragmentDashboardGroupsBinding
@ -33,23 +27,26 @@ import io.nekohasekai.sfa.databinding.ViewDashboardGroupItemBinding
import io.nekohasekai.sfa.ktx.colorForURLTestDelay
import io.nekohasekai.sfa.ktx.errorDialogBuilder
import io.nekohasekai.sfa.ui.MainActivity
import io.nekohasekai.sfa.utils.CommandClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class GroupsFragment : Fragment(), CommandClientHandler {
class GroupsFragment : Fragment(), CommandClient.Handler {
private val activity: MainActivity? get() = super.getActivity() as MainActivity?
private var _binding: FragmentDashboardGroupsBinding? = null
private val binding get() = _binding!!
private var commandClient: CommandClient? = null
private var _adapter: Adapter? = null
private val adapter get() = _adapter!!
private val commandClient =
CommandClient(lifecycleScope, CommandClient.ConnectionType.Groups, this)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
@ -65,41 +62,11 @@ class GroupsFragment : Fragment(), CommandClientHandler {
binding.container.layoutManager = LinearLayoutManager(requireContext())
activity.serviceStatus.observe(viewLifecycleOwner) {
if (it == Status.Started) {
reconnect()
commandClient.connect()
}
}
}
private fun reconnect() {
disconnect()
val options = CommandClientOptions()
options.command = Libbox.CommandGroup
options.statusInterval = 2 * 1000 * 1000 * 1000
val commandClient = CommandClient(requireContext().filesDir.absolutePath, this, options)
this.commandClient = commandClient
lifecycleScope.launch(Dispatchers.IO) {
for (i in 1..3) {
delay(100)
try {
commandClient.connect()
break
} catch (e: Exception) {
break
}
}
}
}
private fun disconnect() {
commandClient?.apply {
runCatching {
disconnect()
}
Seq.destroyRef(refnum)
}
commandClient = null
}
private var displayed = false
private fun updateDisplayed(newValue: Boolean) {
if (displayed != newValue) {
@ -109,24 +76,20 @@ class GroupsFragment : Fragment(), CommandClientHandler {
}
}
override fun connected() {
override fun onConnected() {
lifecycleScope.launch(Dispatchers.Main) {
updateDisplayed(true)
}
}
override fun disconnected(message: String?) {
override fun onDisconnected() {
lifecycleScope.launch(Dispatchers.Main) {
updateDisplayed(false)
}
}
@SuppressLint("NotifyDataSetChanged")
override fun writeGroups(message: OutboundGroupIterator) {
val groups = mutableListOf<OutboundGroup>()
while (message.hasNext()) {
groups.add(message.next())
}
override fun updateGroups(groups: List<OutboundGroup>) {
activity?.runOnUiThread {
updateDisplayed(groups.isNotEmpty())
adapter.groups = groups
@ -134,12 +97,6 @@ class GroupsFragment : Fragment(), CommandClientHandler {
}
}
override fun writeLog(message: String?) {
}
override fun writeStatus(message: StatusMessage?) {
}
private class Adapter : RecyclerView.Adapter<GroupView>() {
lateinit var groups: List<OutboundGroup>

View file

@ -10,12 +10,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDividerItemDecoration
import go.Seq
import io.nekohasekai.libbox.CommandClient
import io.nekohasekai.libbox.CommandClientHandler
import io.nekohasekai.libbox.CommandClientOptions
import io.nekohasekai.libbox.Libbox
import io.nekohasekai.libbox.OutboundGroupIterator
import io.nekohasekai.libbox.StatusMessage
import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.bg.BoxService
@ -27,18 +22,20 @@ import io.nekohasekai.sfa.databinding.FragmentDashboardOverviewBinding
import io.nekohasekai.sfa.databinding.ViewProfileItemBinding
import io.nekohasekai.sfa.ktx.errorDialogBuilder
import io.nekohasekai.sfa.ui.MainActivity
import io.nekohasekai.sfa.utils.CommandClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class OverviewFragment : Fragment(), CommandClientHandler {
class OverviewFragment : Fragment(), CommandClient.Handler {
private val activity: MainActivity? get() = super.getActivity() as MainActivity?
private var _binding: FragmentDashboardOverviewBinding? = null
private val binding get() = _binding!!
private var commandClient: CommandClient? = null
private val commandClient =
CommandClient(lifecycleScope, CommandClient.ConnectionType.Status, this)
private var _adapter: Adapter? = null
private val adapter get() = _adapter!!
@ -64,47 +61,17 @@ class OverviewFragment : Fragment(), CommandClientHandler {
activity.serviceStatus.observe(viewLifecycleOwner) {
binding.statusContainer.isVisible = it == Status.Starting || it == Status.Started
if (it == Status.Started) {
reconnect()
commandClient.connect()
}
}
ProfileManager.registerCallback(this::updateProfiles)
}
private fun reconnect() {
disconnect()
val options = CommandClientOptions()
options.command = Libbox.CommandStatus
options.statusInterval = 2 * 1000 * 1000 * 1000
val commandClient = CommandClient(requireContext().filesDir.absolutePath, this, options)
this.commandClient = commandClient
lifecycleScope.launch(Dispatchers.IO) {
for (i in 1..3) {
delay(100)
try {
commandClient.connect()
break
} catch (e: Exception) {
break
}
}
}
}
private fun disconnect() {
commandClient?.apply {
runCatching {
disconnect()
}
Seq.destroyRef(refnum)
}
commandClient = null
}
override fun onDestroyView() {
super.onDestroyView()
_adapter = null
_binding = null
disconnect()
commandClient.disconnect()
ProfileManager.unregisterCallback(this::updateProfiles)
}
@ -112,7 +79,7 @@ class OverviewFragment : Fragment(), CommandClientHandler {
_adapter?.reload()
}
override fun connected() {
override fun onConnected() {
val binding = _binding ?: return
lifecycleScope.launch(Dispatchers.Main) {
binding.memoryText.text = getString(R.string.loading)
@ -120,7 +87,7 @@ class OverviewFragment : Fragment(), CommandClientHandler {
}
}
override fun disconnected(message: String?) {
override fun onDisconnected() {
val binding = _binding ?: return
lifecycleScope.launch(Dispatchers.Main) {
binding.memoryText.text = getString(R.string.loading)
@ -128,30 +95,24 @@ class OverviewFragment : Fragment(), CommandClientHandler {
}
}
override fun writeLog(message: String) {
}
override fun writeStatus(message: StatusMessage) {
override fun updateStatus(status: StatusMessage) {
val binding = _binding ?: return
lifecycleScope.launch(Dispatchers.Main) {
binding.memoryText.text = Libbox.formatBytes(message.memory)
binding.goroutinesText.text = message.goroutines.toString()
val trafficAvailable = message.trafficAvailable
binding.memoryText.text = Libbox.formatBytes(status.memory)
binding.goroutinesText.text = status.goroutines.toString()
val trafficAvailable = status.trafficAvailable
binding.trafficContainer.isVisible = trafficAvailable
if (trafficAvailable) {
binding.inboundConnectionsText.text = message.connectionsIn.toString()
binding.outboundConnectionsText.text = message.connectionsOut.toString()
binding.uplinkText.text = Libbox.formatBytes(message.uplink) + "/s"
binding.downlinkText.text = Libbox.formatBytes(message.downlink) + "/s"
binding.uplinkTotalText.text = Libbox.formatBytes(message.uplinkTotal)
binding.downlinkTotalText.text = Libbox.formatBytes(message.downlinkTotal)
binding.inboundConnectionsText.text = status.connectionsIn.toString()
binding.outboundConnectionsText.text = status.connectionsOut.toString()
binding.uplinkText.text = Libbox.formatBytes(status.uplink) + "/s"
binding.downlinkText.text = Libbox.formatBytes(status.downlink) + "/s"
binding.uplinkTotalText.text = Libbox.formatBytes(status.uplinkTotal)
binding.downlinkTotalText.text = Libbox.formatBytes(status.downlinkTotal)
}
}
}
override fun writeGroups(message: OutboundGroupIterator?) {
}
class Adapter(
internal val scope: CoroutineScope,
private val parent: FragmentDashboardOverviewBinding

View file

@ -15,7 +15,6 @@ import androidx.recyclerview.widget.RecyclerView
import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.database.Profile
import io.nekohasekai.sfa.database.ProfileManager
import io.nekohasekai.sfa.database.TypedProfile
import io.nekohasekai.sfa.databinding.FragmentConfigurationBinding
import io.nekohasekai.sfa.databinding.ViewConfigutationItemBinding
import io.nekohasekai.sfa.ktx.errorDialogBuilder

View file

@ -0,0 +1,120 @@
package io.nekohasekai.sfa.utils
import go.Seq
import io.nekohasekai.libbox.CommandClient
import io.nekohasekai.libbox.CommandClientHandler
import io.nekohasekai.libbox.CommandClientOptions
import io.nekohasekai.libbox.Libbox
import io.nekohasekai.libbox.OutboundGroup
import io.nekohasekai.libbox.OutboundGroupIterator
import io.nekohasekai.libbox.StatusMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
class CommandClient(
private val scope: CoroutineScope,
private val connectionType: ConnectionType,
private val handler: Handler
) {
enum class ConnectionType {
Status, Groups, Log
}
interface Handler {
fun onConnected() {}
fun onDisconnected() {}
fun updateStatus(status: StatusMessage) {}
fun updateGroups(groups: List<OutboundGroup>) {}
fun appendLog(message: String) {}
}
private var commandClient: CommandClient? = null
private val clientHandler = ClientHandler()
fun connect() {
disconnect()
val options = CommandClientOptions()
options.command = when (connectionType) {
ConnectionType.Status -> Libbox.CommandStatus
ConnectionType.Groups -> Libbox.CommandGroup
ConnectionType.Log -> Libbox.CommandLog
}
options.statusInterval = 2 * 1000 * 1000 * 1000
val commandClient = CommandClient(clientHandler, options)
scope.launch(Dispatchers.IO) {
for (i in 1..10) {
delay(100 + i.toLong() * 50)
try {
commandClient.connect()
} catch (ignored: Exception) {
continue
}
if (!isActive) {
runCatching {
commandClient.disconnect()
}
return@launch
}
this@CommandClient.commandClient = commandClient
return@launch
}
runCatching {
commandClient.disconnect()
}
}
}
fun disconnect() {
commandClient?.apply {
runCatching {
disconnect()
}
Seq.destroyRef(refnum)
}
}
private inner class ClientHandler : CommandClientHandler {
override fun connected() {
handler.onConnected()
}
override fun disconnected(message: String?) {
handler.onDisconnected()
}
override fun writeGroups(message: OutboundGroupIterator?) {
if (message == null) {
return
}
val groups = mutableListOf<OutboundGroup>()
while (message.hasNext()) {
groups.add(message.next())
}
handler.updateGroups(groups)
}
override fun writeLog(message: String?) {
if (message == null) {
return
}
handler.appendLog(message)
}
override fun writeStatus(message: StatusMessage?) {
if (message == null) {
return
}
handler.updateStatus(message)
}
}
}