refactor: replace service local node db with Room NodeDB

pull/1243/head
andrekir 2024-09-13 00:19:21 -03:00
rodzic 84939a74d2
commit 39a18e6418
3 zmienionych plików z 31 dodań i 92 usunięć

Wyświetl plik

@ -70,13 +70,15 @@ class NodeDB @Inject constructor(
nodeInfoDao.upsert(node)
}
suspend fun installNodeDB(mi: MyNodeInfo, nodes: List<NodeInfo>) = withContext(Dispatchers.IO) {
nodeInfoDao.apply {
clearNodeInfo()
clearMyNodeInfo()
setMyNodeInfo(mi) // set MyNodeInfo first
putAll(nodes)
suspend fun clearNodeDB() = withContext(Dispatchers.IO) {
nodeInfoDao.clearNodeInfo()
nodeInfoDao.clearMyNodeInfo()
}
suspend fun installNodeDB(mi: MyNodeInfo, nodes: List<NodeInfo>) = withContext(Dispatchers.IO) {
clearNodeDB()
nodeInfoDao.setMyNodeInfo(mi) // set MyNodeInfo first
nodeInfoDao.putAll(nodes)
}
suspend fun deleteNode(num: Int) = withContext(Dispatchers.IO) {

Wyświetl plik

@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
/**
@ -50,17 +49,15 @@ class RadioConfigRepository @Inject constructor(
/**
* Flow representing the [MyNodeInfo] database.
*/
fun myNodeInfoFlow(): Flow<MyNodeInfo?> = nodeDB.myNodeInfoFlow()
suspend fun getMyNodeInfo(): MyNodeInfo? = myNodeInfoFlow().firstOrNull()
val myNodeInfo: StateFlow<MyNodeInfo?> get() = nodeDB.myNodeInfo
val nodeDBbyNum: StateFlow<Map<Int, NodeInfo>> get() = nodeDB.nodeDBbyNum
/**
* Flow representing the [NodeInfo] database.
*/
suspend fun getNodes(): List<NodeInfo>? = nodeDB.getNodes().firstOrNull()
val nodeDBbyNum: StateFlow<Map<Int, NodeInfo>> get() = nodeDB.nodeDBbyNum
suspend fun upsert(node: NodeInfo) = nodeDB.upsert(node)
suspend fun clearNodeDB() = nodeDB.clearNodeDB()
suspend fun installNodeDB(mi: MyNodeInfo, nodes: List<NodeInfo>) {
nodeDB.installNodeDB(mi, nodes)
}

Wyświetl plik

@ -46,7 +46,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withTimeoutOrNull
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
@ -263,8 +262,6 @@ class MeshService : Service(), Logging {
radioConfigRepository.channelSetFlow.onEach { channelSet = it }
.launchIn(serviceScope)
loadSettings() // Load our last known node DB
// the rest of our init will happen once we are in radioConnection.onServiceConnected
}
@ -326,42 +323,16 @@ class MeshService : Service(), Logging {
/// BEGINNING OF MODEL - FIXME, move elsewhere
///
private fun installNewNodeDB(ni: MyNodeInfo, nodes: List<NodeInfo>) {
discardNodeDB() // Get rid of any old state
myNodeInfo = ni
// put our node array into our two different map representations
nodeDBbyNodeNum.putAll(nodes.map { it.num to it })
}
private fun loadSettings() {
try {
serviceScope.handledLaunch {
val myInfo = radioConfigRepository.getMyNodeInfo()
val nodeDB = radioConfigRepository.getNodes()
if (myInfo != null && nodeDB != null) installNewNodeDB(myInfo, nodeDB)
// Note: we do not haveNodeDB = true because that means we've got a valid db from a real device (rather than this possibly stale hint)
}
} catch (ex: Exception) {
errormsg("Ignoring error loading saved state for service: ${ex.message}")
}
}
/**
* discard entire node db & message state - used when downloading a new db from the device
* discard entire node db - used before downloading a new db from the device
*/
private fun discardNodeDB() {
private fun discardNodeDB() = serviceScope.handledLaunch {
debug("Discarding NodeDB")
myNodeInfo = null
nodeDBbyNodeNum.clear()
radioConfigRepository.clearNodeDB()
haveNodeDB = false
}
var myNodeInfo: MyNodeInfo? = null
val myNodeInfo: MyNodeInfo? get() = radioConfigRepository.myNodeInfo.value
private val configTotal by lazy { ConfigProtos.Config.getDescriptor().fields.size }
private val moduleTotal by lazy { ModuleConfigProtos.ModuleConfig.getDescriptor().fields.size }
@ -376,7 +347,7 @@ class MeshService : Service(), Logging {
private var haveNodeDB = false
// The database of active nodes, index is the node number
private val nodeDBbyNodeNum = ConcurrentHashMap<Int, NodeInfo>()
private val nodeDBbyNodeNum get() = radioConfigRepository.nodeDBbyNum.value
// The database of active nodes, index is the node user ID string
// NOTE: some NodeInfos might be in only nodeDBbyNodeNum (because we don't yet know an ID).
@ -402,15 +373,15 @@ class MeshService : Service(), Logging {
else nodeDBbyNodeNum[n]?.user?.id ?: DataPacket.nodeNumToDefaultId(n)
// given a nodeNum, return a db entry - creating if necessary
private fun getOrCreateNodeInfo(n: Int) = nodeDBbyNodeNum.getOrPut(n) {
val defaultUser = MeshUser(
private fun getOrCreateNodeInfo(n: Int) = nodeDBbyNodeNum[n] ?: NodeInfo(
n,
MeshUser(
id = DataPacket.nodeNumToDefaultId(n),
longName = getString(R.string.unknown_username),
shortName = getString(R.string.unknown_node_short_name),
hwModel = MeshProtos.HardwareModel.UNSET,
)
NodeInfo(n, defaultUser)
}
)
private val hexIdRegex = """\!([0-9A-Fa-f]+)""".toRegex()
private val rangeTestRegex = Regex("seq (\\d{1,10})")
@ -780,14 +751,7 @@ class MeshService : Service(), Logging {
/// Update our DB of users based on someone sending out a User subpacket
private fun handleReceivedUser(fromNum: Int, p: MeshProtos.User, channel: Int = 0) {
updateNodeInfo(fromNum) {
val oldId = it.user?.id.orEmpty()
it.user = MeshUser(
p.id.ifEmpty { oldId }, // If the new update doesn't contain an ID keep our old value
p.longName,
p.shortName,
p.hwModel,
p.isLicensed
)
it.user = MeshUser(p)
it.channel = channel
}
}
@ -1313,32 +1277,15 @@ class MeshService : Service(), Logging {
radioConfigRepository.setStatusMessage("Channels (${ch.index + 1} / $maxChannels)")
}
/**
* Convert a protobuf NodeInfo into our model objects and update our node DB
*/
private fun installNodeInfo(info: MeshProtos.NodeInfo) {
// Just replace/add any entry
updateNodeInfo(info.num) {
if (info.hasUser()) {
it.user = MeshUser(info.user.copy { if (info.viaMqtt) longName = "$longName (MQTT)" })
}
if (info.hasPosition()) {
// For the local node, it might not be able to update its times because it doesn't have a valid GPS reading yet
// so if the info is for _our_ node we always assume time is current
it.position = Position(info.position)
}
it.lastHeard = info.lastHeard
if (info.hasDeviceMetrics()) {
it.deviceMetrics = DeviceMetrics(info.deviceMetrics)
}
it.channel = info.channel
it.hopsAway = info.hopsAway
}
}
private fun MeshProtos.NodeInfo.toEntity() = NodeInfo(
num = num,
user = MeshUser(user.copy { if (viaMqtt) longName = "$longName (MQTT)" }),
position = Position(position),
lastHeard = lastHeard,
deviceMetrics = DeviceMetrics(deviceMetrics),
channel = channel,
hopsAway = hopsAway,
)
private fun handleNodeInfo(info: MeshProtos.NodeInfo) {
debug("Received nodeinfo num=${info.num}, hasUser=${info.hasUser()}, hasPosition=${info.hasPosition()}, hasDeviceMetrics=${info.hasDeviceMetrics()}")
@ -1497,7 +1444,7 @@ class MeshService : Service(), Logging {
reportConnection()
}
private fun handleConfigComplete(configCompleteId: Int) {
private fun handleConfigComplete(configCompleteId: Int) = serviceScope.handledLaunch {
if (configCompleteId == configNonce) {
val packetToSave = MeshLog(
@ -1512,19 +1459,12 @@ class MeshService : Service(), Logging {
if (newMyNodeInfo == null || newNodes.isEmpty()) {
errormsg("Did not receive a valid config")
} else {
discardNodeDB()
debug("Installing new node DB")
myNodeInfo = newMyNodeInfo
newNodes.forEach(::installNodeInfo)
radioConfigRepository.installNodeDB(newMyNodeInfo!!, newNodes.map { it.toEntity() })
newNodes.clear() // Just to save RAM ;-)
haveNodeDB = true // we now have nodes from real hardware
serviceScope.handledLaunch {
radioConfigRepository.installNodeDB(myNodeInfo!!, nodeDBbyID.values.toList())
}
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket {
setTimeOnly = currentSecond()
})