Fix rate not working for polling clients

This commit is contained in:
Mygod
2018-10-03 15:58:49 +08:00
parent c0ee5a0b89
commit 48d6307b2b
3 changed files with 40 additions and 28 deletions

View File

@@ -2,10 +2,7 @@ package be.mygod.vpnhotspot.client
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.format.Formatter
import android.text.style.StrikethroughSpan import android.text.style.StrikethroughSpan
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.R
@@ -17,10 +14,9 @@ import be.mygod.vpnhotspot.room.lookup
import be.mygod.vpnhotspot.room.macToLong import be.mygod.vpnhotspot.room.macToLong
import be.mygod.vpnhotspot.util.onEmpty import be.mygod.vpnhotspot.util.onEmpty
import java.net.InetAddress import java.net.InetAddress
import java.util.Objects import java.util.*
import java.util.TreeMap
abstract class Client : BaseObservable() { abstract class Client {
companion object DiffCallback : DiffUtil.ItemCallback<Client>() { companion object DiffCallback : DiffUtil.ItemCallback<Client>() {
override fun areItemsTheSame(oldItem: Client, newItem: Client) = override fun areItemsTheSame(oldItem: Client, newItem: Client) =
oldItem.iface == newItem.iface && oldItem.mac == newItem.mac oldItem.iface == newItem.iface && oldItem.mac == newItem.mac
@@ -32,8 +28,6 @@ abstract class Client : BaseObservable() {
private val macIface get() = "$mac%$iface" private val macIface get() = "$mac%$iface"
val ip = TreeMap<InetAddress, IpNeighbour.State>(InetAddressComparator) val ip = TreeMap<InetAddress, IpNeighbour.State>(InetAddressComparator)
val record by lazy { AppDatabase.instance.clientRecordDao.lookup(mac.macToLong()) } val record by lazy { AppDatabase.instance.clientRecordDao.lookup(mac.macToLong()) }
var sendRate = -1L
var receiveRate = -1L
open val icon get() = TetherType.ofInterface(iface).icon open val icon get() = TetherType.ofInterface(iface).icon
val title: CharSequence get() { val title: CharSequence get() {
@@ -41,7 +35,7 @@ abstract class Client : BaseObservable() {
if (record.blocked) result.setSpan(StrikethroughSpan(), 0, result.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) if (record.blocked) result.setSpan(StrikethroughSpan(), 0, result.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
return result return result
} }
val description: String @Bindable get() { val description: String get() {
val result = StringBuilder(if (record.nickname.isEmpty()) "" else "$macIface\n") val result = StringBuilder(if (record.nickname.isEmpty()) "" else "$macIface\n")
ip.entries.forEach { (ip, state) -> ip.entries.forEach { (ip, state) ->
result.appendln(app.getString(when (state) { result.appendln(app.getString(when (state) {
@@ -51,8 +45,6 @@ abstract class Client : BaseObservable() {
else -> throw IllegalStateException("Invalid IpNeighbour.State: $state") else -> throw IllegalStateException("Invalid IpNeighbour.State: $state")
}, ip.hostAddress)) }, ip.hostAddress))
} }
if (sendRate >= 0 && receiveRate >= 0) result.appendln(
"${Formatter.formatFileSize(app, sendRate)}/s\t\t${Formatter.formatFileSize(app, receiveRate)}/s")
return result.toString().trimEnd() return result.toString().trimEnd()
} }

View File

@@ -16,6 +16,7 @@ import android.widget.EditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.databinding.BaseObservable
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
@@ -23,7 +24,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.AlertDialogFragment import be.mygod.vpnhotspot.AlertDialogFragment
import be.mygod.vpnhotspot.BR import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.databinding.FragmentClientsBinding import be.mygod.vpnhotspot.databinding.FragmentClientsBinding
import be.mygod.vpnhotspot.databinding.ListitemClientBinding import be.mygod.vpnhotspot.databinding.ListitemClientBinding
@@ -31,6 +32,7 @@ import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.monitor.TrafficRecorder import be.mygod.vpnhotspot.net.monitor.TrafficRecorder
import be.mygod.vpnhotspot.room.* import be.mygod.vpnhotspot.room.*
import be.mygod.vpnhotspot.util.ServiceForegroundConnector import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.util.computeIfAbsentCompat
import be.mygod.vpnhotspot.util.toPluralInt import be.mygod.vpnhotspot.util.toPluralInt
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import java.text.NumberFormat import java.text.NumberFormat
@@ -92,6 +94,16 @@ class ClientsFragment : Fragment(), ServiceConnection {
} }
} }
data class TrafficRate(var send: Long = -1, var receive: Long = -1) : BaseObservable() {
fun clear() {
send = -1
receive = -1
}
override fun toString() = if (send < 0 || receive < 0) "" else
"${Formatter.formatFileSize(app, send)}/s\t\t${Formatter.formatFileSize(app, receive)}/s"
}
private inner class ClientViewHolder(val binding: ListitemClientBinding) : RecyclerView.ViewHolder(binding.root), private inner class ClientViewHolder(val binding: ListitemClientBinding) : RecyclerView.ViewHolder(binding.root),
View.OnClickListener, PopupMenu.OnMenuItemClickListener { View.OnClickListener, PopupMenu.OnMenuItemClickListener {
init { init {
@@ -144,12 +156,8 @@ class ClientsFragment : Fragment(), ServiceConnection {
} }
private inner class ClientAdapter : ListAdapter<Client, ClientViewHolder>(Client) { private inner class ClientAdapter : ListAdapter<Client, ClientViewHolder>(Client) {
private var clientsLookup: LongSparseArray<Client>? = null
override fun submitList(list: MutableList<Client>?) { override fun submitList(list: MutableList<Client>?) {
super.submitList(list) super.submitList(list)
clientsLookup = if (list == null) null else LongSparseArray<Client>(list.size).apply {
list.forEach { put(it.mac.macToLong(), it) }
}
binding.swipeRefresher.isRefreshing = false binding.swipeRefresher.isRefreshing = false
} }
@@ -157,20 +165,17 @@ class ClientsFragment : Fragment(), ServiceConnection {
ClientViewHolder(ListitemClientBinding.inflate(LayoutInflater.from(parent.context), parent, false)) ClientViewHolder(ListitemClientBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun onBindViewHolder(holder: ClientViewHolder, position: Int) { override fun onBindViewHolder(holder: ClientViewHolder, position: Int) {
holder.binding.client = getItem(position) val client = getItem(position)
holder.binding.client = client
holder.binding.rate =
rates.computeIfAbsentCompat(Pair(client.iface, client.mac.macToLong())) { TrafficRate() }
holder.binding.executePendingBindings() holder.binding.executePendingBindings()
} }
fun updateTraffic(newRecords: Collection<TrafficRecord>, oldRecords: LongSparseArray<TrafficRecord>) { fun updateTraffic(newRecords: Collection<TrafficRecord>, oldRecords: LongSparseArray<TrafficRecord>) {
val clientsLookup = clientsLookup ?: return for (rate in rates.values) rate.clear()
val seenClients = HashSet<Client>()
for (newRecord in newRecords) { for (newRecord in newRecords) {
val oldRecord = oldRecords[newRecord.previousId ?: continue] ?: continue val oldRecord = oldRecords[newRecord.previousId ?: continue] ?: continue
val client = clientsLookup[newRecord.mac]
if (seenClients.add(client)) {
client.sendRate = 0
client.receiveRate = 0
}
val elapsed = newRecord.timestamp - oldRecord.timestamp val elapsed = newRecord.timestamp - oldRecord.timestamp
if (elapsed == 0L) { if (elapsed == 0L) {
check(newRecord.sentPackets == oldRecord.sentPackets) check(newRecord.sentPackets == oldRecord.sentPackets)
@@ -178,17 +183,23 @@ class ClientsFragment : Fragment(), ServiceConnection {
check(newRecord.receivedPackets == oldRecord.receivedPackets) check(newRecord.receivedPackets == oldRecord.receivedPackets)
check(newRecord.receivedBytes == oldRecord.receivedBytes) check(newRecord.receivedBytes == oldRecord.receivedBytes)
} else { } else {
client.sendRate += (newRecord.sentBytes - oldRecord.sentBytes) * 1000 / elapsed val rate = rates.computeIfAbsentCompat(Pair(newRecord.downstream, newRecord.mac)) { TrafficRate() }
client.receiveRate += (newRecord.receivedBytes - oldRecord.receivedBytes) * 1000 / elapsed if (rate.send < 0 || rate.receive < 0) {
client.notifyPropertyChanged(BR.description) rate.send = 0
rate.receive = 0
}
rate.send += (newRecord.sentBytes - oldRecord.sentBytes) * 1000 / elapsed
rate.receive += (newRecord.receivedBytes - oldRecord.receivedBytes) * 1000 / elapsed
} }
} }
for (rate in rates.values) rate.notifyChange()
} }
} }
private lateinit var binding: FragmentClientsBinding private lateinit var binding: FragmentClientsBinding
private val adapter = ClientAdapter() private val adapter = ClientAdapter()
private var clients: ClientMonitorService.Binder? = null private var clients: ClientMonitorService.Binder? = null
private var rates = HashMap<Pair<String, Long>, TrafficRate>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_clients, container, false) binding = DataBindingUtil.inflate(inflater, R.layout.fragment_clients, container, false)

View File

@@ -6,6 +6,9 @@
<variable <variable
name="client" name="client"
type="be.mygod.vpnhotspot.client.Client"/> type="be.mygod.vpnhotspot.client.Client"/>
<variable
name="rate"
type="be.mygod.vpnhotspot.client.ClientsFragment.TrafficRate"/>
</data> </data>
<LinearLayout <LinearLayout
@@ -41,12 +44,18 @@
android:textIsSelectable="@{client.record.nickname.length() == 0}" android:textIsSelectable="@{client.record.nickname.length() == 0}"
tools:text="01:23:45:ab:cd:ef%p2p-p2p0-0"/> tools:text="01:23:45:ab:cd:ef%p2p-p2p0-0"/>
<TextView <be.mygod.vpnhotspot.widget.AutoCollapseTextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{client.description}" android:text="@{client.description}"
android:textIsSelectable="true" android:textIsSelectable="true"
tools:text="192.168.49.123 (reachable)\nfe80::abcd:efff:1234:5678%p2p-p2p0-0 (reachable)"/> tools:text="192.168.49.123 (reachable)\nfe80::abcd:efff:1234:5678%p2p-p2p0-0 (reachable)"/>
<be.mygod.vpnhotspot.widget.AutoCollapseTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{rate.toString()}"
tools:text="▲ 3.23KB/s\t\t▼ 5.12GB/s"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</layout> </layout>