Refactor command client

This commit is contained in:
世界
2023-08-15 18:13:45 +08:00
parent c2c3db6835
commit 8147ec71da
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.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator 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.Libbox
import io.nekohasekai.libbox.OutboundGroup import io.nekohasekai.libbox.OutboundGroup
import io.nekohasekai.libbox.OutboundGroupItem import io.nekohasekai.libbox.OutboundGroupItem
import io.nekohasekai.libbox.OutboundGroupIterator
import io.nekohasekai.libbox.StatusMessage
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.constant.Status import io.nekohasekai.sfa.constant.Status
import io.nekohasekai.sfa.databinding.FragmentDashboardGroupsBinding 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.colorForURLTestDelay
import io.nekohasekai.sfa.ktx.errorDialogBuilder import io.nekohasekai.sfa.ktx.errorDialogBuilder
import io.nekohasekai.sfa.ui.MainActivity import io.nekohasekai.sfa.ui.MainActivity
import io.nekohasekai.sfa.utils.CommandClient
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class GroupsFragment : Fragment(), CommandClientHandler { class GroupsFragment : Fragment(), CommandClient.Handler {
private val activity: MainActivity? get() = super.getActivity() as MainActivity? private val activity: MainActivity? get() = super.getActivity() as MainActivity?
private var _binding: FragmentDashboardGroupsBinding? = null private var _binding: FragmentDashboardGroupsBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private var commandClient: CommandClient? = null
private var _adapter: Adapter? = null private var _adapter: Adapter? = null
private val adapter get() = _adapter!! private val adapter get() = _adapter!!
private val commandClient =
CommandClient(lifecycleScope, CommandClient.ConnectionType.Groups, this)
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View { ): View {
@@ -65,41 +62,11 @@ class GroupsFragment : Fragment(), CommandClientHandler {
binding.container.layoutManager = LinearLayoutManager(requireContext()) binding.container.layoutManager = LinearLayoutManager(requireContext())
activity.serviceStatus.observe(viewLifecycleOwner) { activity.serviceStatus.observe(viewLifecycleOwner) {
if (it == Status.Started) { 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 var displayed = false
private fun updateDisplayed(newValue: Boolean) { private fun updateDisplayed(newValue: Boolean) {
if (displayed != newValue) { if (displayed != newValue) {
@@ -109,24 +76,20 @@ class GroupsFragment : Fragment(), CommandClientHandler {
} }
} }
override fun connected() { override fun onConnected() {
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
updateDisplayed(true) updateDisplayed(true)
} }
} }
override fun disconnected(message: String?) { override fun onDisconnected() {
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
updateDisplayed(false) updateDisplayed(false)
} }
} }
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
override fun writeGroups(message: OutboundGroupIterator) { override fun updateGroups(groups: List<OutboundGroup>) {
val groups = mutableListOf<OutboundGroup>()
while (message.hasNext()) {
groups.add(message.next())
}
activity?.runOnUiThread { activity?.runOnUiThread {
updateDisplayed(groups.isNotEmpty()) updateDisplayed(groups.isNotEmpty())
adapter.groups = groups 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>() { private class Adapter : RecyclerView.Adapter<GroupView>() {
lateinit var groups: List<OutboundGroup> lateinit var groups: List<OutboundGroup>

View File

@@ -10,12 +10,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDividerItemDecoration 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.Libbox
import io.nekohasekai.libbox.OutboundGroupIterator
import io.nekohasekai.libbox.StatusMessage import io.nekohasekai.libbox.StatusMessage
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.bg.BoxService 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.databinding.ViewProfileItemBinding
import io.nekohasekai.sfa.ktx.errorDialogBuilder import io.nekohasekai.sfa.ktx.errorDialogBuilder
import io.nekohasekai.sfa.ui.MainActivity import io.nekohasekai.sfa.ui.MainActivity
import io.nekohasekai.sfa.utils.CommandClient
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class OverviewFragment : Fragment(), CommandClientHandler { class OverviewFragment : Fragment(), CommandClient.Handler {
private val activity: MainActivity? get() = super.getActivity() as MainActivity? private val activity: MainActivity? get() = super.getActivity() as MainActivity?
private var _binding: FragmentDashboardOverviewBinding? = null private var _binding: FragmentDashboardOverviewBinding? = null
private val binding get() = _binding!! 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 var _adapter: Adapter? = null
private val adapter get() = _adapter!! private val adapter get() = _adapter!!
@@ -64,47 +61,17 @@ class OverviewFragment : Fragment(), CommandClientHandler {
activity.serviceStatus.observe(viewLifecycleOwner) { activity.serviceStatus.observe(viewLifecycleOwner) {
binding.statusContainer.isVisible = it == Status.Starting || it == Status.Started binding.statusContainer.isVisible = it == Status.Starting || it == Status.Started
if (it == Status.Started) { if (it == Status.Started) {
reconnect() commandClient.connect()
} }
} }
ProfileManager.registerCallback(this::updateProfiles) 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() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_adapter = null _adapter = null
_binding = null _binding = null
disconnect() commandClient.disconnect()
ProfileManager.unregisterCallback(this::updateProfiles) ProfileManager.unregisterCallback(this::updateProfiles)
} }
@@ -112,7 +79,7 @@ class OverviewFragment : Fragment(), CommandClientHandler {
_adapter?.reload() _adapter?.reload()
} }
override fun connected() { override fun onConnected() {
val binding = _binding ?: return val binding = _binding ?: return
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
binding.memoryText.text = getString(R.string.loading) 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 val binding = _binding ?: return
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
binding.memoryText.text = getString(R.string.loading) binding.memoryText.text = getString(R.string.loading)
@@ -128,30 +95,24 @@ class OverviewFragment : Fragment(), CommandClientHandler {
} }
} }
override fun writeLog(message: String) { override fun updateStatus(status: StatusMessage) {
}
override fun writeStatus(message: StatusMessage) {
val binding = _binding ?: return val binding = _binding ?: return
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
binding.memoryText.text = Libbox.formatBytes(message.memory) binding.memoryText.text = Libbox.formatBytes(status.memory)
binding.goroutinesText.text = message.goroutines.toString() binding.goroutinesText.text = status.goroutines.toString()
val trafficAvailable = message.trafficAvailable val trafficAvailable = status.trafficAvailable
binding.trafficContainer.isVisible = trafficAvailable binding.trafficContainer.isVisible = trafficAvailable
if (trafficAvailable) { if (trafficAvailable) {
binding.inboundConnectionsText.text = message.connectionsIn.toString() binding.inboundConnectionsText.text = status.connectionsIn.toString()
binding.outboundConnectionsText.text = message.connectionsOut.toString() binding.outboundConnectionsText.text = status.connectionsOut.toString()
binding.uplinkText.text = Libbox.formatBytes(message.uplink) + "/s" binding.uplinkText.text = Libbox.formatBytes(status.uplink) + "/s"
binding.downlinkText.text = Libbox.formatBytes(message.downlink) + "/s" binding.downlinkText.text = Libbox.formatBytes(status.downlink) + "/s"
binding.uplinkTotalText.text = Libbox.formatBytes(message.uplinkTotal) binding.uplinkTotalText.text = Libbox.formatBytes(status.uplinkTotal)
binding.downlinkTotalText.text = Libbox.formatBytes(message.downlinkTotal) binding.downlinkTotalText.text = Libbox.formatBytes(status.downlinkTotal)
} }
} }
} }
override fun writeGroups(message: OutboundGroupIterator?) {
}
class Adapter( class Adapter(
internal val scope: CoroutineScope, internal val scope: CoroutineScope,
private val parent: FragmentDashboardOverviewBinding private val parent: FragmentDashboardOverviewBinding

View File

@@ -15,7 +15,6 @@ import androidx.recyclerview.widget.RecyclerView
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.database.Profile import io.nekohasekai.sfa.database.Profile
import io.nekohasekai.sfa.database.ProfileManager import io.nekohasekai.sfa.database.ProfileManager
import io.nekohasekai.sfa.database.TypedProfile
import io.nekohasekai.sfa.databinding.FragmentConfigurationBinding import io.nekohasekai.sfa.databinding.FragmentConfigurationBinding
import io.nekohasekai.sfa.databinding.ViewConfigutationItemBinding import io.nekohasekai.sfa.databinding.ViewConfigutationItemBinding
import io.nekohasekai.sfa.ktx.errorDialogBuilder 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)
}
}
}