Migrate to LiveData
Benefit includes: no more flush after changing nickname. Yep.
This commit is contained in:
@@ -14,7 +14,6 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import androidx.lifecycle.get
|
import androidx.lifecycle.get
|
||||||
import be.mygod.vpnhotspot.client.Client
|
|
||||||
import be.mygod.vpnhotspot.client.ClientViewModel
|
import be.mygod.vpnhotspot.client.ClientViewModel
|
||||||
import be.mygod.vpnhotspot.client.ClientsFragment
|
import be.mygod.vpnhotspot.client.ClientsFragment
|
||||||
import be.mygod.vpnhotspot.databinding.ActivityMainBinding
|
import be.mygod.vpnhotspot.databinding.ActivityMainBinding
|
||||||
@@ -45,6 +44,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
|
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
|
||||||
|
binding.setLifecycleOwner(this)
|
||||||
binding.navigation.setOnNavigationItemSelectedListener(this)
|
binding.navigation.setOnNavigationItemSelectedListener(this)
|
||||||
if (savedInstanceState == null) displayFragment(TetheringFragment())
|
if (savedInstanceState == null) displayFragment(TetheringFragment())
|
||||||
badge = QBadgeView(this)
|
badge = QBadgeView(this)
|
||||||
@@ -55,7 +55,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
|||||||
badge.setGravityOffset(16f, 0f, true)
|
badge.setGravityOffset(16f, 0f, true)
|
||||||
val model = ViewModelProviders.of(this).get<ClientViewModel>()
|
val model = ViewModelProviders.of(this).get<ClientViewModel>()
|
||||||
if (RepeaterService.supported) ServiceForegroundConnector(this, model, RepeaterService::class)
|
if (RepeaterService.supported) ServiceForegroundConnector(this, model, RepeaterService::class)
|
||||||
model.clients.observe(this, Observer<List<Client>> { badge.badgeNumber = it.size })
|
model.clients.observe(this, Observer { badge.badgeNumber = it.size })
|
||||||
SmartSnackbar.Register(lifecycle, binding.fragmentHolder)
|
SmartSnackbar.Register(lifecycle, binding.fragmentHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,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.style.StrikethroughSpan
|
import android.text.style.StrikethroughSpan
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
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
|
||||||
@@ -10,42 +11,42 @@ import be.mygod.vpnhotspot.net.InetAddressComparator
|
|||||||
import be.mygod.vpnhotspot.net.IpNeighbour
|
import be.mygod.vpnhotspot.net.IpNeighbour
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
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.*
|
import java.util.*
|
||||||
|
|
||||||
abstract class Client {
|
open class Client(val mac: String, val iface: String) {
|
||||||
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
|
||||||
override fun areContentsTheSame(oldItem: Client, newItem: Client) = oldItem == newItem
|
override fun areContentsTheSame(oldItem: Client, newItem: Client) = oldItem == newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract val iface: String
|
|
||||||
abstract val mac: String
|
|
||||||
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 = AppDatabase.instance.clientRecordDao.lookupSync(mac.macToLong())
|
||||||
|
|
||||||
|
val nickname get() = record.value?.nickname ?: ""
|
||||||
|
val blocked get() = record.value?.blocked == true
|
||||||
|
|
||||||
open val icon get() = TetherType.ofInterface(iface).icon
|
open val icon get() = TetherType.ofInterface(iface).icon
|
||||||
val title: CharSequence get() {
|
val title = Transformations.map(record) { record ->
|
||||||
val result = SpannableStringBuilder(record.nickname.onEmpty(macIface))
|
SpannableStringBuilder(record.nickname.onEmpty(macIface)).apply {
|
||||||
if (record.blocked) result.setSpan(StrikethroughSpan(), 0, result.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
|
if (record.blocked) setSpan(StrikethroughSpan(), 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
|
||||||
return result
|
|
||||||
}
|
|
||||||
val description: String get() {
|
|
||||||
val result = StringBuilder(if (record.nickname.isEmpty()) "" else "$macIface\n")
|
|
||||||
ip.entries.forEach { (ip, state) ->
|
|
||||||
result.appendln(app.getString(when (state) {
|
|
||||||
IpNeighbour.State.INCOMPLETE -> R.string.connected_state_incomplete
|
|
||||||
IpNeighbour.State.VALID -> R.string.connected_state_valid
|
|
||||||
IpNeighbour.State.FAILED -> R.string.connected_state_failed
|
|
||||||
else -> throw IllegalStateException("Invalid IpNeighbour.State: $state")
|
|
||||||
}, ip.hostAddress))
|
|
||||||
}
|
}
|
||||||
return result.toString().trimEnd()
|
}
|
||||||
|
val description = Transformations.map(record) { record ->
|
||||||
|
StringBuilder(if (record.nickname.isEmpty()) "" else "$macIface\n").apply {
|
||||||
|
ip.entries.forEach { (ip, state) ->
|
||||||
|
appendln(app.getString(when (state) {
|
||||||
|
IpNeighbour.State.INCOMPLETE -> R.string.connected_state_incomplete
|
||||||
|
IpNeighbour.State.VALID -> R.string.connected_state_valid
|
||||||
|
IpNeighbour.State.FAILED -> R.string.connected_state_failed
|
||||||
|
else -> throw IllegalStateException("Invalid IpNeighbour.State: $state")
|
||||||
|
}, ip.hostAddress))
|
||||||
|
}
|
||||||
|
}.toString().trimEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -57,9 +58,9 @@ abstract class Client {
|
|||||||
if (iface != other.iface) return false
|
if (iface != other.iface) return false
|
||||||
if (mac != other.mac) return false
|
if (mac != other.mac) return false
|
||||||
if (ip != other.ip) return false
|
if (ip != other.ip) return false
|
||||||
if (record != other.record) return false
|
if (record.value != other.record.value) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
override fun hashCode() = Objects.hash(iface, mac, ip, record)
|
override fun hashCode() = Objects.hash(iface, mac, ip, record.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
|
|||||||
var client = clients[key]
|
var client = clients[key]
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
if (!tetheredInterfaces.contains(neighbour.dev)) continue
|
if (!tetheredInterfaces.contains(neighbour.dev)) continue
|
||||||
client = TetheringClient(neighbour)
|
client = Client(neighbour.lladdr, neighbour.dev)
|
||||||
clients[key] = client
|
clients[key] = client
|
||||||
}
|
}
|
||||||
client.ip += Pair(neighbour.ip, neighbour.state)
|
client.ip += Pair(neighbour.ip, neighbour.state)
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.versionedparcelable.VersionedParcelable
|
import androidx.versionedparcelable.VersionedParcelable
|
||||||
import be.mygod.vpnhotspot.AlertDialogFragment
|
import be.mygod.vpnhotspot.AlertDialogFragment
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.R
|
|
||||||
import be.mygod.vpnhotspot.Empty
|
import be.mygod.vpnhotspot.Empty
|
||||||
|
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
|
||||||
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
||||||
@@ -53,11 +53,9 @@ class ClientsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||||
AppDatabase.instance.clientRecordDao.lookup(arg.mac.macToLong()).apply {
|
AppDatabase.instance.clientRecordDao.upsert(arg.mac.macToLong()) {
|
||||||
nickname = this@NicknameDialogFragment.dialog!!.findViewById<EditText>(android.R.id.edit).text
|
nickname = this@NicknameDialogFragment.dialog!!.findViewById<EditText>(android.R.id.edit).text
|
||||||
AppDatabase.instance.clientRecordDao.update(this)
|
|
||||||
}
|
}
|
||||||
IpNeighbourMonitor.instance?.flush()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,13 +93,14 @@ class ClientsFragment : Fragment() {
|
|||||||
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 {
|
||||||
|
binding.setLifecycleOwner(this@ClientsFragment) // todo some way better?
|
||||||
binding.root.setOnClickListener(this)
|
binding.root.setOnClickListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
PopupMenu(binding.root.context, binding.root).apply {
|
PopupMenu(binding.root.context, binding.root).apply {
|
||||||
menuInflater.inflate(R.menu.popup_client, menu)
|
menuInflater.inflate(R.menu.popup_client, menu)
|
||||||
menu.removeItem(if (binding.client!!.record.blocked) R.id.block else R.id.unblock)
|
menu.removeItem(if (binding.client!!.blocked) R.id.block else R.id.unblock)
|
||||||
setOnMenuItemClickListener(this@ClientViewHolder)
|
setOnMenuItemClickListener(this@ClientViewHolder)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
@@ -111,7 +110,7 @@ class ClientsFragment : Fragment() {
|
|||||||
return when (item?.itemId) {
|
return when (item?.itemId) {
|
||||||
R.id.nickname -> {
|
R.id.nickname -> {
|
||||||
val client = binding.client ?: return false
|
val client = binding.client ?: return false
|
||||||
NicknameDialogFragment().withArg(NicknameArg(client.mac, client.record.nickname))
|
NicknameDialogFragment().withArg(NicknameArg(client.mac, client.nickname))
|
||||||
.show(fragmentManager ?: return false, "NicknameDialogFragment")
|
.show(fragmentManager ?: return false, "NicknameDialogFragment")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -119,7 +118,9 @@ class ClientsFragment : Fragment() {
|
|||||||
val client = binding.client ?: return false
|
val client = binding.client ?: return false
|
||||||
val wasWorking = TrafficRecorder.isWorking(client.mac.macToLong())
|
val wasWorking = TrafficRecorder.isWorking(client.mac.macToLong())
|
||||||
client.record.apply {
|
client.record.apply {
|
||||||
AppDatabase.instance.clientRecordDao.update(ClientRecord(mac, nickname, !blocked))
|
val value = value ?: ClientRecord(client.mac.macToLong())
|
||||||
|
value.blocked = !value.blocked
|
||||||
|
AppDatabase.instance.clientRecordDao.update(value)
|
||||||
}
|
}
|
||||||
IpNeighbourMonitor.instance?.flush()
|
IpNeighbourMonitor.instance?.flush()
|
||||||
if (!wasWorking && item.itemId == R.id.block) {
|
if (!wasWorking && item.itemId == R.id.block) {
|
||||||
@@ -129,7 +130,7 @@ class ClientsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
R.id.stats -> {
|
R.id.stats -> {
|
||||||
val client = binding.client ?: return false
|
val client = binding.client ?: return false
|
||||||
StatsDialogFragment().withArg(StatsArg(client.title,
|
StatsDialogFragment().withArg(StatsArg(client.title.value!!, // todo?
|
||||||
AppDatabase.instance.trafficRecordDao.queryStats(client.mac.macToLong())))
|
AppDatabase.instance.trafficRecordDao.queryStats(client.mac.macToLong())))
|
||||||
.show(fragmentManager ?: return false, "StatsDialogFragment")
|
.show(fragmentManager ?: return false, "StatsDialogFragment")
|
||||||
true
|
true
|
||||||
@@ -186,6 +187,7 @@ class ClientsFragment : Fragment() {
|
|||||||
|
|
||||||
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)
|
||||||
|
binding.setLifecycleOwner(this)
|
||||||
binding.clients.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
binding.clients.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||||
binding.clients.itemAnimator = DefaultItemAnimator()
|
binding.clients.itemAnimator = DefaultItemAnimator()
|
||||||
binding.clients.adapter = adapter
|
binding.clients.adapter = adapter
|
||||||
@@ -194,7 +196,7 @@ class ClientsFragment : Fragment() {
|
|||||||
IpNeighbourMonitor.instance?.flush()
|
IpNeighbourMonitor.instance?.flush()
|
||||||
}
|
}
|
||||||
ViewModelProviders.of(requireActivity()).get<ClientViewModel>().clients.observe(this,
|
ViewModelProviders.of(requireActivity()).get<ClientViewModel>().clients.observe(this,
|
||||||
Observer<List<Client>> { adapter.submitList(it.toMutableList()) })
|
Observer { adapter.submitList(it.toMutableList()) })
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package be.mygod.vpnhotspot.client
|
|
||||||
|
|
||||||
import be.mygod.vpnhotspot.net.IpNeighbour
|
|
||||||
|
|
||||||
class TetheringClient(private val neighbour: IpNeighbour) : Client() {
|
|
||||||
override val iface get() = neighbour.dev
|
|
||||||
override val mac get() = neighbour.lladdr
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,6 @@ package be.mygod.vpnhotspot.client
|
|||||||
import android.net.wifi.p2p.WifiP2pDevice
|
import android.net.wifi.p2p.WifiP2pDevice
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
|
|
||||||
class WifiP2pClient(p2pInterface: String, p2p: WifiP2pDevice) : Client() {
|
class WifiP2pClient(p2pInterface: String, p2p: WifiP2pDevice) : Client(p2p.deviceAddress ?: "", p2pInterface) {
|
||||||
override val iface = p2pInterface
|
|
||||||
override val mac = p2p.deviceAddress ?: ""
|
|
||||||
override val icon: Int get() = TetherType.WIFI_P2P.icon
|
override val icon: Int get() = TetherType.WIFI_P2P.icon
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ class TetheringFragment : Fragment(), ServiceConnection {
|
|||||||
|
|
||||||
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_tethering, container, false)
|
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_tethering, container, false)
|
||||||
|
binding.setLifecycleOwner(this)
|
||||||
binding.interfaces.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
binding.interfaces.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||||
binding.interfaces.itemAnimator = DefaultItemAnimator()
|
binding.interfaces.itemAnimator = DefaultItemAnimator()
|
||||||
binding.interfaces.adapter = adapter
|
binding.interfaces.adapter = adapter
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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.net.monitor.UpstreamMonitor
|
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.room.lookup
|
|
||||||
import be.mygod.vpnhotspot.room.macToLong
|
import be.mygod.vpnhotspot.room.macToLong
|
||||||
import be.mygod.vpnhotspot.util.RootSession
|
import be.mygod.vpnhotspot.util.RootSession
|
||||||
import be.mygod.vpnhotspot.util.computeIfAbsentCompat
|
import be.mygod.vpnhotspot.util.computeIfAbsentCompat
|
||||||
@@ -173,7 +172,7 @@ class Routing(val downstream: String, ownerAddress: InterfaceAddress? = null) :
|
|||||||
val toRemove = HashSet(clients.keys)
|
val toRemove = HashSet(clients.keys)
|
||||||
for (neighbour in neighbours) {
|
for (neighbour in neighbours) {
|
||||||
if (neighbour.dev != downstream || neighbour.ip !is Inet4Address ||
|
if (neighbour.dev != downstream || neighbour.ip !is Inet4Address ||
|
||||||
AppDatabase.instance.clientRecordDao.lookup(neighbour.lladdr.macToLong()).blocked) continue
|
AppDatabase.instance.clientRecordDao.lookup(neighbour.lladdr.macToLong())?.blocked == true) continue
|
||||||
toRemove.remove(neighbour.ip)
|
toRemove.remove(neighbour.ip)
|
||||||
try {
|
try {
|
||||||
clients.computeIfAbsentCompat(neighbour.ip) { Client(neighbour.ip, neighbour.lladdr) }
|
clients.computeIfAbsentCompat(neighbour.ip) { Client(neighbour.ip, neighbour.lladdr) }
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import be.mygod.vpnhotspot.DebugHelper
|
|||||||
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
|
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.room.TrafficRecord
|
import be.mygod.vpnhotspot.room.TrafficRecord
|
||||||
import be.mygod.vpnhotspot.room.insert
|
|
||||||
import be.mygod.vpnhotspot.room.macToLong
|
import be.mygod.vpnhotspot.room.macToLong
|
||||||
import be.mygod.vpnhotspot.util.Event2
|
import be.mygod.vpnhotspot.util.Event2
|
||||||
import be.mygod.vpnhotspot.util.RootSession
|
import be.mygod.vpnhotspot.util.RootSession
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package be.mygod.vpnhotspot.room
|
package be.mygod.vpnhotspot.room
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -8,14 +9,23 @@ data class ClientRecord(@PrimaryKey
|
|||||||
var nickname: CharSequence = "",
|
var nickname: CharSequence = "",
|
||||||
var blocked: Boolean = false) {
|
var blocked: Boolean = false) {
|
||||||
@androidx.room.Dao
|
@androidx.room.Dao
|
||||||
interface Dao {
|
abstract class Dao {
|
||||||
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
||||||
fun lookupOrNull(mac: Long): ClientRecord?
|
abstract fun lookup(mac: Long): ClientRecord?
|
||||||
|
|
||||||
|
fun lookupOrDefault(mac: Long) = lookup(mac) ?: ClientRecord(mac)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
|
||||||
|
abstract fun lookupSync(mac: Long): LiveData<ClientRecord>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun updateInternal(value: ClientRecord): Long
|
protected abstract fun updateInternal(value: ClientRecord): Long
|
||||||
|
fun update(value: ClientRecord) = check(updateInternal(value) == value.mac)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun upsert(mac: Long, operation: ClientRecord.() -> Unit) = lookupOrDefault(mac).apply {
|
||||||
|
operation()
|
||||||
|
update(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ClientRecord.Dao.lookup(mac: Long) = lookupOrNull(mac) ?: ClientRecord(mac)
|
|
||||||
fun ClientRecord.Dao.update(value: ClientRecord) = check(updateInternal(value) == value.mac)
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import java.net.InetAddress
|
|||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
class Converters {
|
object Converters {
|
||||||
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun persistCharSequence(cs: CharSequence): ByteArray {
|
fun persistCharSequence(cs: CharSequence): ByteArray {
|
||||||
val p = Parcel.obtain()
|
val p = Parcel.obtain()
|
||||||
@@ -19,6 +20,7 @@ class Converters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun unpersistCharSequence(data: ByteArray): CharSequence {
|
fun unpersistCharSequence(data: ByteArray): CharSequence {
|
||||||
val p = Parcel.obtain()
|
val p = Parcel.obtain()
|
||||||
@@ -31,9 +33,11 @@ class Converters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun persistInetAddress(address: InetAddress): ByteArray = address.address
|
fun persistInetAddress(address: InetAddress): ByteArray = address.address
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun unpersistInetAddress(data: ByteArray): InetAddress = InetAddress.getByAddress(data)
|
fun unpersistInetAddress(data: ByteArray): InetAddress = InetAddress.getByAddress(data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,13 @@ data class TrafficRecord(
|
|||||||
*/
|
*/
|
||||||
val previousId: Long? = null) {
|
val previousId: Long? = null) {
|
||||||
@androidx.room.Dao
|
@androidx.room.Dao
|
||||||
interface Dao {
|
abstract class Dao {
|
||||||
@Insert
|
@Insert
|
||||||
fun insertInternal(value: TrafficRecord): Long
|
protected abstract fun insertInternal(value: TrafficRecord): Long
|
||||||
|
fun insert(value: TrafficRecord) {
|
||||||
|
check(value.id == null)
|
||||||
|
value.id = insertInternal(value)
|
||||||
|
}
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT MIN(TrafficRecord.timestamp) AS timestamp,
|
SELECT MIN(TrafficRecord.timestamp) AS timestamp,
|
||||||
@@ -52,15 +56,10 @@ data class TrafficRecord(
|
|||||||
FROM TrafficRecord LEFT JOIN TrafficRecord AS Next ON TrafficRecord.id = Next.previousId
|
FROM TrafficRecord LEFT JOIN TrafficRecord AS Next ON TrafficRecord.id = Next.previousId
|
||||||
/* We only want to find the last record for each chain so that we don't double count */
|
/* We only want to find the last record for each chain so that we don't double count */
|
||||||
WHERE TrafficRecord.mac = :mac AND Next.id IS NULL""")
|
WHERE TrafficRecord.mac = :mac AND Next.id IS NULL""")
|
||||||
fun queryStats(mac: Long): ClientStats
|
abstract fun queryStats(mac: Long): ClientStats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TrafficRecord.Dao.insert(value: TrafficRecord) {
|
|
||||||
check(value.id == null)
|
|
||||||
value.id = insertInternal(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ClientStats(
|
data class ClientStats(
|
||||||
val timestamp: Long = 0,
|
val timestamp: Long = 0,
|
||||||
val count: Long = 0,
|
val count: Long = 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user