Fix rate not working for polling clients
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user