refactor: eliminate NodeInfoReadDataSource, use NodeRepository directly

MeshLogRepositoryImpl was the sole external consumer of
NodeInfoReadDataSource — it only needed myNodeNum to identify the
local node in logs. Replace with NodeRepository.myNodeInfo (already
SDK-backed via SdkNodeRepositoryImpl).

PacketRepositoryImpl referenced NodeInfoDao.MAX_BIND_PARAMS — inlined
as a private constant since it's just the SQLite bind-param limit.

With zero external consumers remaining, delete:
- NodeInfoReadDataSource interface
- SwitchingNodeInfoReadDataSource implementation

The Room NodeInfoDao/NodeEntity/MyNodeEntity remain in the database
module for now (internal tests + migration history) but have no
external dependents — ready for a future Room migration 40 to DROP.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich
2026-05-05 10:23:07 -05:00
parent a0ee9e8399
commit 1dd3637b50
6 changed files with 30 additions and 122 deletions
+10 -8
View File
@@ -7,14 +7,14 @@
## Summary
**Completed:** ~92% of the Clean Break migration. AIDL dropped, SDK is sole radio path,
**Completed:** ~93% of the Clean Break migration. AIDL dropped, SDK is sole radio path,
transport layer fully deleted, Desktop uses shared SDK bridge, dead infrastructure gone,
POC ViewModels removed, stale broadcast constants removed.
POC ViewModels removed, NodeInfoReadDataSource eliminated.
**Remaining:** Room table cleanup, optional VM parameter slimming, and test coverage for
new bridge code.
**Net change:** 154 files changed, +3,655 / -15,301 lines (net -11,646 LOC removed)
**Net change:** 159 files changed, +3,684 / -15,460 lines (net -11,776 LOC removed)
---
@@ -97,13 +97,15 @@ new bridge code.
## What Remains
### 1. Room Table Cleanup (low priority)
### 1. Room Table Cleanup (medium priority — unblocked)
- Migration 39→40: DROP legacy `nodes`, `my_node` tables
- Remove old `NodeEntity`, `MyNodeEntity` Room entities + DAOs
- Remove old `NodeEntity`, `MyNodeEntity` Room entities + `NodeInfoDao`
- SDK SqlDelight is already source of truth; Room tables are redundant
- **Blocked by:** `NodeInfoReadDataSource` (used by `MeshLogRepositoryImpl` for node
name resolution), `PacketRepositoryImpl` (uses `NodeInfoDao.MAX_BIND_PARAMS`)
- Requires migrating node-name lookup to SDK APIs before tables can be dropped
- **No longer blocked:** `NodeInfoReadDataSource` eliminated, `PacketRepositoryImpl`
no longer depends on `NodeInfoDao`
- Remaining internal consumers: `MeshtasticDatabase.nodeInfoDao()` abstract method,
`CommonNodeInfoDaoTest`, `CommonPacketDaoTest`, `MigrationTest`
- Requires: Room schema migration file, entity deletion, DAO deletion, test updates
### 2. VM Parameter Slimming (optional, quality-of-life)
VMs currently inject SDK-backed adapters (RadioController, NodeRepository, etc.)
@@ -1,40 +0,0 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.data.datasource
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.database.entity.NodeEntity
import org.meshtastic.core.database.entity.NodeWithRelations
interface NodeInfoReadDataSource {
fun myNodeInfoFlow(): Flow<MyNodeEntity?>
fun nodeDBbyNumFlow(): Flow<Map<Int, NodeWithRelations>>
fun getNodesFlow(
sort: String,
filter: String,
includeUnknown: Boolean,
hopsAwayMax: Int,
lastHeardMin: Int,
): Flow<List<NodeWithRelations>>
suspend fun getNodesOlderThan(lastHeard: Int): List<NodeEntity>
suspend fun getUnknownNodes(): List<NodeEntity>
}
@@ -1,58 +0,0 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.data.datasource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import org.koin.core.annotation.Single
import org.meshtastic.core.database.DatabaseProvider
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.database.entity.NodeEntity
import org.meshtastic.core.database.entity.NodeWithRelations
@Single
class SwitchingNodeInfoReadDataSource(private val dbManager: DatabaseProvider) : NodeInfoReadDataSource {
override fun myNodeInfoFlow(): Flow<MyNodeEntity?> =
dbManager.currentDb.flatMapLatest { db -> db.nodeInfoDao().getMyNodeInfo() }
override fun nodeDBbyNumFlow(): Flow<Map<Int, NodeWithRelations>> =
dbManager.currentDb.flatMapLatest { db -> db.nodeInfoDao().nodeDBbyNum() }
override fun getNodesFlow(
sort: String,
filter: String,
includeUnknown: Boolean,
hopsAwayMax: Int,
lastHeardMin: Int,
): Flow<List<NodeWithRelations>> = dbManager.currentDb.flatMapLatest { db ->
db.nodeInfoDao()
.getNodes(
sort = sort,
filter = filter,
includeUnknown = includeUnknown,
hopsAwayMax = hopsAwayMax,
lastHeardMin = lastHeardMin,
)
}
override suspend fun getNodesOlderThan(lastHeard: Int): List<NodeEntity> =
dbManager.withDb { it.nodeInfoDao().getNodesOlderThan(lastHeard) } ?: emptyList()
override suspend fun getUnknownNodes(): List<NodeEntity> =
dbManager.withDb { it.nodeInfoDao().getUnknownNodes() } ?: emptyList()
}
@@ -27,7 +27,6 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.withContext
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.data.datasource.NodeInfoReadDataSource
import org.meshtastic.core.database.DatabaseProvider
import org.meshtastic.core.database.entity.asEntity
import org.meshtastic.core.database.entity.asExternalModel
@@ -36,6 +35,7 @@ import org.meshtastic.core.model.MeshLog
import org.meshtastic.core.repository.MeshLogPrefs
import org.meshtastic.core.repository.MeshLogRepository
import org.meshtastic.core.repository.MeshLogRepository.Companion.DEFAULT_MAX_LOGS
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.MyNodeInfo
import org.meshtastic.proto.PortNum
@@ -53,7 +53,7 @@ open class MeshLogRepositoryImpl(
private val dbManager: DatabaseProvider,
private val dispatchers: CoroutineDispatchers,
private val meshLogPrefs: MeshLogPrefs,
private val nodeInfoReadDataSource: NodeInfoReadDataSource,
private val nodeRepository: NodeRepository,
) : MeshLogRepository {
/** Retrieves all [MeshLog]s in the database, up to [maxItem]. */
@@ -142,8 +142,8 @@ open class MeshLogRepositoryImpl(
.getOrNull()
/** Returns a flow that maps a [nodeNum] to [MeshLog.NODE_NUM_LOCAL] if it is the locally connected node. */
private fun effectiveLogId(nodeNum: Int): Flow<Int> = nodeInfoReadDataSource
.myNodeInfoFlow()
private fun effectiveLogId(nodeNum: Int): Flow<Int> = nodeRepository
.myNodeInfo
.map { info -> if (nodeNum == info?.myNodeNum) MeshLog.NODE_NUM_LOCAL else nodeNum }
.distinctUntilChanged()
@@ -169,7 +169,7 @@ open class MeshLogRepositoryImpl(
/** Deletes all logs associated with a specific [nodeNum] and [portNum]. */
override suspend fun deleteLogs(nodeNum: Int, portNum: Int) = withContext(dispatchers.io) {
val myNodeNum = nodeInfoReadDataSource.myNodeInfoFlow().firstOrNull()?.myNodeNum
val myNodeNum = nodeRepository.myNodeInfo.value?.myNodeNum
val logId = if (nodeNum == myNodeNum) MeshLog.NODE_NUM_LOCAL else nodeNum
dbManager.currentDb.value.meshLogDao().deleteLogs(logId, portNum)
}
@@ -28,7 +28,6 @@ import kotlinx.coroutines.withContext
import okio.ByteString.Companion.toByteString
import org.koin.core.annotation.Single
import org.meshtastic.core.database.DatabaseProvider
import org.meshtastic.core.database.dao.NodeInfoDao
import org.meshtastic.core.database.entity.PacketEntity
import org.meshtastic.core.database.entity.toReaction
import org.meshtastic.core.di.CoroutineDispatchers
@@ -245,7 +244,7 @@ class PacketRepositoryImpl(private val dbManager: DatabaseProvider, private val
} else {
withContext(dispatchers.io) {
val dao = dbManager.currentDb.value.packetDao()
ids.chunked(NodeInfoDao.MAX_BIND_PARAMS)
ids.chunked(MAX_SQLITE_BIND_PARAMS)
.flatMap { dao.getPacketsByPacketIds(it) }
.associateBy { it.packet.packetId }
}
@@ -515,5 +514,6 @@ class PacketRepositoryImpl(private val dbManager: DatabaseProvider, private val
private const val MESSAGES_PAGE_SIZE = 50
private const val DELETE_CHUNK_SIZE = 500
private const val MILLISECONDS_IN_SECOND = 1000L
private const val MAX_SQLITE_BIND_PARAMS = 999
}
}
@@ -25,10 +25,10 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import okio.ByteString.Companion.toByteString
import org.meshtastic.core.data.datasource.NodeInfoReadDataSource
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.MeshLog
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.testing.FakeDatabaseProvider
import org.meshtastic.core.testing.FakeMeshLogPrefs
import org.meshtastic.proto.Data
@@ -47,7 +47,7 @@ abstract class CommonMeshLogRepositoryTest {
protected lateinit var dbProvider: FakeDatabaseProvider
protected lateinit var meshLogPrefs: FakeMeshLogPrefs
protected lateinit var nodeInfoReadDataSource: NodeInfoReadDataSource
protected lateinit var nodeRepository: NodeRepository
private val testDispatcher = UnconfinedTestDispatcher()
private val dispatchers = CoroutineDispatchers(main = testDispatcher, io = testDispatcher, default = testDispatcher)
@@ -59,11 +59,11 @@ abstract class CommonMeshLogRepositoryTest {
dbProvider = FakeDatabaseProvider()
meshLogPrefs = FakeMeshLogPrefs()
meshLogPrefs.setLoggingEnabled(true)
nodeInfoReadDataSource = mock(MockMode.autofill)
nodeRepository = mock(MockMode.autofill)
every { nodeInfoReadDataSource.myNodeInfoFlow() } returns MutableStateFlow(null)
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)
repository = MeshLogRepositoryImpl(dbProvider, dispatchers, meshLogPrefs, nodeInfoReadDataSource)
repository = MeshLogRepositoryImpl(dbProvider, dispatchers, meshLogPrefs, nodeRepository)
}
@AfterTest
@@ -105,9 +105,10 @@ abstract class CommonMeshLogRepositoryTest {
fun `deleteLogs redirects local node number to NODE_NUM_LOCAL`() = runTest(testDispatcher) {
val localNodeNum = 999
val port = PortNum.TEXT_MESSAGE_APP.value
val myNodeEntity =
MyNodeEntity(
val myNodeInfo =
MyNodeInfo(
myNodeNum = localNodeNum,
hasGPS = false,
model = "model",
firmwareVersion = "1.0",
couldUpdate = false,
@@ -117,8 +118,11 @@ abstract class CommonMeshLogRepositoryTest {
minAppVersion = 0,
maxChannels = 0,
hasWifi = false,
channelUtilization = 0f,
airUtilTx = 0f,
deviceId = null,
)
every { nodeInfoReadDataSource.myNodeInfoFlow() } returns MutableStateFlow(myNodeEntity)
every { nodeRepository.myNodeInfo } returns MutableStateFlow(myNodeInfo)
val log =
MeshLog(