diff --git a/mobile/build.gradle b/mobile/build.gradle index 81d28c06..bd51f078 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -94,6 +94,7 @@ dependencies { implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0' implementation 'net.glxn.qrgen:android:2.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + implementation 'org.jetbrains.kotlinx:kotlinx-collections-immutable:0.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-RC2' for (dep in aux) { freedomImplementation dep diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/IpNeighbourMonitoringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/IpNeighbourMonitoringService.kt index 01fb5cad..7ec88494 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/IpNeighbourMonitoringService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/IpNeighbourMonitoringService.kt @@ -5,12 +5,12 @@ import be.mygod.vpnhotspot.net.IpNeighbour import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor abstract class IpNeighbourMonitoringService : Service(), IpNeighbourMonitor.Callback { - private var neighbours = emptyList() + private var neighbours: Collection = emptyList() protected abstract val activeIfaces: List protected open val inactiveIfaces get() = emptyList() - override fun onIpNeighbourAvailable(neighbours: List) { + override fun onIpNeighbourAvailable(neighbours: Collection) { this.neighbours = neighbours updateNotification() } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt index 53b2904a..35b0f65f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt @@ -133,7 +133,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope { stopSelf() } - override fun onIpNeighbourAvailable(neighbours: List) { + override fun onIpNeighbourAvailable(neighbours: Collection) { super.onIpNeighbourAvailable(neighbours) if (Build.VERSION.SDK_INT >= 28) timeoutMonitor?.onClientsChanged(neighbours.none { it.state != IpNeighbour.State.FAILED diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt index 09c7c479..312193c9 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt @@ -27,7 +27,7 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb private var repeater: RepeaterService.Binder? = null private var p2p: Collection = emptyList() - private var neighbours = emptyList() + private var neighbours: Collection = emptyList() val clients = MutableLiveData>() private fun populateClients() { @@ -81,7 +81,7 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb binder.groupChanged -= this } - override fun onIpNeighbourAvailable(neighbours: List) { + override fun onIpNeighbourAvailable(neighbours: Collection) { this.neighbours = neighbours populateClients() } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt index d7d5cdd0..8e4866bb 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt @@ -218,7 +218,7 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh } } private val clients = HashMap() - override fun onIpNeighbourAvailable(neighbours: List) = synchronized(this) { + override fun onIpNeighbourAvailable(neighbours: Collection) = synchronized(this) { val toRemove = HashSet(clients.keys) for (neighbour in neighbours) { if (neighbour.dev != downstream || neighbour.ip !is Inet4Address || runBlocking { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpNeighbourMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpNeighbourMonitor.kt index 4a799816..35f9f79b 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpNeighbourMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpNeighbourMonitor.kt @@ -1,11 +1,13 @@ package be.mygod.vpnhotspot.net.monitor import be.mygod.vpnhotspot.net.IpNeighbour -import kotlinx.coroutines.Dispatchers +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.actor +import kotlinx.coroutines.channels.sendBlocking import java.net.InetAddress -import java.util.* class IpNeighbourMonitor private constructor() : IpMonitor() { companion object { @@ -31,46 +33,34 @@ class IpNeighbourMonitor private constructor() : IpMonitor() { } interface Callback { - fun onIpNeighbourAvailable(neighbours: List) + fun onIpNeighbourAvailable(neighbours: Collection) } - private var updatePosted = false - private val neighbours = HashMap() + private val aggregator = GlobalScope.actor>(capacity = Channel.CONFLATED) { + for (value in channel) { + val neighbours = value.values + synchronized(callbacks) { for (callback in callbacks) callback.onIpNeighbourAvailable(neighbours) } + } + } + private var neighbours = persistentMapOf() override val monitoredObject: String get() = "neigh" override fun processLine(line: String) { - synchronized(neighbours) { - if (IpNeighbour.parse(line).map { neighbour -> - if (neighbour.state == IpNeighbour.State.DELETING) - neighbours.remove(neighbour.ip) != null - else neighbours.put(neighbour.ip, neighbour) != neighbour - }.any { it }) postUpdateLocked() + val old = neighbours + for (neighbour in IpNeighbour.parse(line)) neighbours = when (neighbour.state) { + IpNeighbour.State.DELETING -> neighbours.remove(neighbour.ip) + else -> neighbours.put(neighbour.ip, neighbour) } + if (neighbours != old) aggregator.sendBlocking(neighbours) } override fun processLines(lines: Sequence) { - synchronized(neighbours) { - neighbours.clear() - neighbours.putAll(lines - .flatMap { IpNeighbour.parse(it).asSequence() } - .filter { it.state != IpNeighbour.State.DELETING } // skip entries without lladdr - .associateBy { it.ip }) - postUpdateLocked() - } - } - - private fun postUpdateLocked() { - if (updatePosted || instance != this) return - GlobalScope.launch { - val neighbours = synchronized(neighbours) { - updatePosted = false - neighbours.values.toList() - } - synchronized(callbacks) { - for (callback in callbacks) callback.onIpNeighbourAvailable(neighbours) - } - } - updatePosted = true + neighbours = lines + .flatMap { IpNeighbour.parse(it).asSequence() } + .filter { it.state != IpNeighbour.State.DELETING } // skip entries without lladdr + .associateByTo(persistentMapOf().builder()) { it.ip } + .build() + aggregator.sendBlocking(neighbours) } }