refactor: Move persisters to monitor package and fix stale BLE device eviction

Move DeviceStatePersister and AapKeyPersister from reaction to monitor package since they are always-on infrastructure, not user-togglable reactions.

Add periodic ticker to BlePodMonitor to force stale device eviction when BLE scanner produces no results, fixing cached card not appearing after disconnect.
This commit is contained in:
darken
2026-04-02 15:14:47 +02:00
parent e91243e577
commit 360067b07e
5 changed files with 20 additions and 9 deletions
@@ -1,6 +1,7 @@
package eu.darken.capod.monitor.core
import android.bluetooth.le.ScanFilter
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.bluetooth.BleScanner
import eu.darken.capod.common.bluetooth.BluetoothManager2
import eu.darken.capod.common.bluetooth.ScannerMode
@@ -27,8 +28,10 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.sync.Mutex
@@ -65,7 +68,13 @@ class BlePodMonitor @Inject constructor(
log(TAG, WARN) { "Bluetooth is not ready" }
flowOf(null)
} else {
createBleScanner()
val staleEvictionTicker: Flow<Collection<BleScanResult>> = flow {
while (true) {
delay(STALE_EVICTION_INTERVAL.toMillis())
emit(emptyList())
}
}
merge(createBleScanner(), staleEvictionTicker)
}
}
.map { results -> results?.mapNotNull { podFactory.createPod(it) } }
@@ -167,7 +176,7 @@ class BlePodMonitor @Inject constructor(
val now = Instant.now()
deviceCache.toList().forEach { (key, value) ->
if (Duration.between(value.seenLastAt, now) > Duration.ofSeconds(20)) {
if (Duration.between(value.seenLastAt, now) > STALE_DEVICE_TIMEOUT) {
log(TAG, VERBOSE) { "Removing stale device from cache: $value" }
deviceCache.remove(key)
}
@@ -187,5 +196,7 @@ class BlePodMonitor @Inject constructor(
companion object {
private val TAG = logTag("Monitor", "PodMonitor")
private val STALE_DEVICE_TIMEOUT = Duration.ofSeconds(20)
private val STALE_EVICTION_INTERVAL = Duration.ofSeconds(10)
}
}
@@ -1,4 +1,4 @@
package eu.darken.capod.reaction.core
package eu.darken.capod.monitor.core
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.log
@@ -87,6 +87,6 @@ class DeviceStatePersister @Inject constructor(
}
companion object {
private val TAG = logTag("Reaction", "DeviceStatePersister")
private val TAG = logTag("Monitor", "DeviceStatePersister")
}
}
@@ -1,4 +1,4 @@
package eu.darken.capod.reaction.core.aap
package eu.darken.capod.monitor.core.aap
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
@@ -47,6 +47,6 @@ class AapKeyPersister @Inject constructor(
.setupCommonEventHandlers(TAG) { "keyPersister" }
companion object {
private val TAG = logTag("Reaction", "AapKeyPersister")
private val TAG = logTag("Monitor", "AapKeyPersister")
}
}
@@ -36,8 +36,8 @@ import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesRepo
import eu.darken.capod.pods.core.apple.aap.AapConnectionManager
import eu.darken.capod.reaction.core.aap.AapAutoConnect
import eu.darken.capod.reaction.core.DeviceStatePersister
import eu.darken.capod.reaction.core.aap.AapKeyPersister
import eu.darken.capod.monitor.core.DeviceStatePersister
import eu.darken.capod.monitor.core.aap.AapKeyPersister
import eu.darken.capod.reaction.core.autoconnect.AutoConnect
import eu.darken.capod.reaction.core.playpause.PlayPause
import eu.darken.capod.reaction.core.popup.PopUpReaction
@@ -1,4 +1,4 @@
package eu.darken.capod.reaction.core
package eu.darken.capod.monitor.core
import eu.darken.capod.monitor.core.CachedDeviceState
import eu.darken.capod.monitor.core.DeviceMonitor