From 717c932ae29c85a0b29c8cbb15c2cc3234736a03 Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 26 Sep 2024 05:34:37 -0300 Subject: [PATCH] refactor(MeshLog): add fields for query optimization - Adds `from_num` and `port_num` fields as indices to the `MeshLog` entity to improve query performance. - Sets default values for new columns for auto-migration and backward compatibility. --- .../12.json | 696 ++++++++++++++++++ .../geeksville/mesh/database/Converters.kt | 16 + .../mesh/database/MeshLogRepository.kt | 16 +- .../mesh/database/MeshtasticDatabase.kt | 3 +- .../mesh/database/dao/MeshLogDao.kt | 15 +- .../mesh/database/entity/MeshLog.kt | 24 +- .../geeksville/mesh/service/MeshService.kt | 85 ++- 7 files changed, 800 insertions(+), 55 deletions(-) create mode 100644 app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/12.json diff --git a/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/12.json b/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/12.json new file mode 100644 index 000000000..efde78e6f --- /dev/null +++ b/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/12.json @@ -0,0 +1,696 @@ +{ + "formatVersion": 1, + "database": { + "version": 12, + "identityHash": "293c8875bddb0da5cdeb7e1903dc9c38", + "entities": [ + { + "tableName": "MyNodeInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`myNodeNum` INTEGER NOT NULL, `hasGPS` INTEGER NOT NULL, `model` TEXT, `firmwareVersion` TEXT, `couldUpdate` INTEGER NOT NULL, `shouldUpdate` INTEGER NOT NULL, `currentPacketId` INTEGER NOT NULL, `messageTimeoutMsec` INTEGER NOT NULL, `minAppVersion` INTEGER NOT NULL, `maxChannels` INTEGER NOT NULL, `hasWifi` INTEGER NOT NULL, `channelUtilization` REAL NOT NULL, `airUtilTx` REAL NOT NULL, PRIMARY KEY(`myNodeNum`))", + "fields": [ + { + "fieldPath": "myNodeNum", + "columnName": "myNodeNum", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasGPS", + "columnName": "hasGPS", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "model", + "columnName": "model", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firmwareVersion", + "columnName": "firmwareVersion", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "couldUpdate", + "columnName": "couldUpdate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shouldUpdate", + "columnName": "shouldUpdate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currentPacketId", + "columnName": "currentPacketId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageTimeoutMsec", + "columnName": "messageTimeoutMsec", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minAppVersion", + "columnName": "minAppVersion", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "maxChannels", + "columnName": "maxChannels", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasWifi", + "columnName": "hasWifi", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "channelUtilization", + "columnName": "channelUtilization", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "airUtilTx", + "columnName": "airUtilTx", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "myNodeNum" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "NodeInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`num` INTEGER NOT NULL, `snr` REAL NOT NULL, `rssi` INTEGER NOT NULL, `lastHeard` INTEGER NOT NULL, `channel` INTEGER NOT NULL, `hopsAway` INTEGER NOT NULL DEFAULT 0, `user_id` TEXT, `user_longName` TEXT, `user_shortName` TEXT, `user_hwModel` TEXT, `user_isLicensed` INTEGER, `user_role` INTEGER DEFAULT 0, `position_latitude` REAL, `position_longitude` REAL, `position_altitude` INTEGER, `position_time` INTEGER, `position_satellitesInView` INTEGER, `position_groundSpeed` INTEGER, `position_groundTrack` INTEGER, `position_precisionBits` INTEGER, `devMetrics_time` INTEGER, `devMetrics_batteryLevel` INTEGER, `devMetrics_voltage` REAL, `devMetrics_channelUtilization` REAL, `devMetrics_airUtilTx` REAL, `devMetrics_uptimeSeconds` INTEGER, `envMetrics_time` INTEGER, `envMetrics_temperature` REAL, `envMetrics_relativeHumidity` REAL, `envMetrics_barometricPressure` REAL, `envMetrics_gasResistance` REAL, `envMetrics_voltage` REAL, `envMetrics_current` REAL, `envMetrics_iaq` INTEGER, PRIMARY KEY(`num`))", + "fields": [ + { + "fieldPath": "num", + "columnName": "num", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snr", + "columnName": "snr", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "rssi", + "columnName": "rssi", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastHeard", + "columnName": "lastHeard", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "channel", + "columnName": "channel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hopsAway", + "columnName": "hopsAway", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "user.id", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.longName", + "columnName": "user_longName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.shortName", + "columnName": "user_shortName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.hwModel", + "columnName": "user_hwModel", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.isLicensed", + "columnName": "user_isLicensed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "user.role", + "columnName": "user_role", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "0" + }, + { + "fieldPath": "position.latitude", + "columnName": "position_latitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "position.longitude", + "columnName": "position_longitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "position.altitude", + "columnName": "position_altitude", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "position.time", + "columnName": "position_time", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "position.satellitesInView", + "columnName": "position_satellitesInView", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "position.groundSpeed", + "columnName": "position_groundSpeed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "position.groundTrack", + "columnName": "position_groundTrack", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "position.precisionBits", + "columnName": "position_precisionBits", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "deviceMetrics.time", + "columnName": "devMetrics_time", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "deviceMetrics.batteryLevel", + "columnName": "devMetrics_batteryLevel", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "deviceMetrics.voltage", + "columnName": "devMetrics_voltage", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "deviceMetrics.channelUtilization", + "columnName": "devMetrics_channelUtilization", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "deviceMetrics.airUtilTx", + "columnName": "devMetrics_airUtilTx", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "deviceMetrics.uptimeSeconds", + "columnName": "devMetrics_uptimeSeconds", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "environmentMetrics.time", + "columnName": "envMetrics_time", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "environmentMetrics.temperature", + "columnName": "envMetrics_temperature", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "environmentMetrics.relativeHumidity", + "columnName": "envMetrics_relativeHumidity", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "environmentMetrics.barometricPressure", + "columnName": "envMetrics_barometricPressure", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "environmentMetrics.gasResistance", + "columnName": "envMetrics_gasResistance", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "environmentMetrics.voltage", + "columnName": "envMetrics_voltage", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "environmentMetrics.current", + "columnName": "envMetrics_current", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "environmentMetrics.iaq", + "columnName": "envMetrics_iaq", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "num" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "nodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`num` INTEGER NOT NULL, `user` BLOB NOT NULL, `long_name` TEXT, `short_name` TEXT, `position` BLOB NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `snr` REAL NOT NULL, `rssi` INTEGER NOT NULL, `last_heard` INTEGER NOT NULL, `device_metrics` BLOB NOT NULL, `channel` INTEGER NOT NULL, `via_mqtt` INTEGER NOT NULL, `hops_away` INTEGER NOT NULL, `is_favorite` INTEGER NOT NULL, `environment_metrics` BLOB NOT NULL, `power_metrics` BLOB NOT NULL, `paxcounter` BLOB NOT NULL, PRIMARY KEY(`num`))", + "fields": [ + { + "fieldPath": "num", + "columnName": "num", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "user", + "columnName": "user", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "longName", + "columnName": "long_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "latitude", + "columnName": "latitude", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "longitude", + "columnName": "longitude", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "snr", + "columnName": "snr", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "rssi", + "columnName": "rssi", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastHeard", + "columnName": "last_heard", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceTelemetry", + "columnName": "device_metrics", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "channel", + "columnName": "channel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viaMqtt", + "columnName": "via_mqtt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hopsAway", + "columnName": "hops_away", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFavorite", + "columnName": "is_favorite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "environmentTelemetry", + "columnName": "environment_metrics", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "powerTelemetry", + "columnName": "power_metrics", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "paxcounter", + "columnName": "paxcounter", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "num" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "packet", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `myNodeNum` INTEGER NOT NULL DEFAULT 0, `port_num` INTEGER NOT NULL, `contact_key` TEXT NOT NULL, `received_time` INTEGER NOT NULL, `read` INTEGER NOT NULL DEFAULT 1, `data` TEXT NOT NULL, `packet_id` INTEGER NOT NULL DEFAULT 0, `routing_error` INTEGER NOT NULL DEFAULT -1)", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "myNodeNum", + "columnName": "myNodeNum", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "port_num", + "columnName": "port_num", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contact_key", + "columnName": "contact_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "received_time", + "columnName": "received_time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "read", + "columnName": "read", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "packetId", + "columnName": "packet_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "routingError", + "columnName": "routing_error", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_packet_myNodeNum", + "unique": false, + "columnNames": [ + "myNodeNum" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_packet_myNodeNum` ON `${TABLE_NAME}` (`myNodeNum`)" + }, + { + "name": "index_packet_port_num", + "unique": false, + "columnNames": [ + "port_num" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_packet_port_num` ON `${TABLE_NAME}` (`port_num`)" + }, + { + "name": "index_packet_contact_key", + "unique": false, + "columnNames": [ + "contact_key" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_packet_contact_key` ON `${TABLE_NAME}` (`contact_key`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "contact_settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`contact_key` TEXT NOT NULL, `muteUntil` INTEGER NOT NULL, PRIMARY KEY(`contact_key`))", + "fields": [ + { + "fieldPath": "contact_key", + "columnName": "contact_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "muteUntil", + "columnName": "muteUntil", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "contact_key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "log", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `type` TEXT NOT NULL, `received_date` INTEGER NOT NULL, `message` TEXT NOT NULL, `from_num` INTEGER NOT NULL DEFAULT 0, `port_num` INTEGER NOT NULL DEFAULT 0, `from_radio` BLOB NOT NULL DEFAULT x'', PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message_type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "received_date", + "columnName": "received_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "raw_message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fromNum", + "columnName": "from_num", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "portNum", + "columnName": "port_num", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "fromRadio", + "columnName": "from_radio", + "affinity": "BLOB", + "notNull": true, + "defaultValue": "x''" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_log_from_num", + "unique": false, + "columnNames": [ + "from_num" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_log_from_num` ON `${TABLE_NAME}` (`from_num`)" + }, + { + "name": "index_log_port_num", + "unique": false, + "columnNames": [ + "port_num" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_log_port_num` ON `${TABLE_NAME}` (`port_num`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "quick_chat", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `message` TEXT NOT NULL, `mode` TEXT NOT NULL, `position` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mode", + "columnName": "mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uuid" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '293c8875bddb0da5cdeb7e1903dc9c38')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/Converters.kt b/app/src/main/java/com/geeksville/mesh/database/Converters.kt index fd9fff474..6cc44b9c4 100644 --- a/app/src/main/java/com/geeksville/mesh/database/Converters.kt +++ b/app/src/main/java/com/geeksville/mesh/database/Converters.kt @@ -9,6 +9,7 @@ import com.geeksville.mesh.android.Logging import com.google.protobuf.InvalidProtocolBufferException import kotlinx.serialization.json.Json +@Suppress("TooManyFunctions") class Converters : Logging { @TypeConverter fun dataFromString(value: String): DataPacket { @@ -22,6 +23,21 @@ class Converters : Logging { return json.encodeToString(DataPacket.serializer(), value) } + @TypeConverter + fun bytesToFromRadio(bytes: ByteArray): MeshProtos.FromRadio { + return try { + MeshProtos.FromRadio.parseFrom(bytes) + } catch (ex: InvalidProtocolBufferException) { + errormsg("bytesToFromRadio TypeConverter error:", ex) + MeshProtos.FromRadio.getDefaultInstance() + } + } + + @TypeConverter + fun fromRadioToBytes(value: MeshProtos.FromRadio): ByteArray? { + return value.toByteArray() + } + @TypeConverter fun bytesToUser(bytes: ByteArray): MeshProtos.User { return try { diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshLogRepository.kt b/app/src/main/java/com/geeksville/mesh/database/MeshLogRepository.kt index f9973956f..b0cb1c6ac 100644 --- a/app/src/main/java/com/geeksville/mesh/database/MeshLogRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/database/MeshLogRepository.kt @@ -26,17 +26,15 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L meshLogDao.getAllLogsInReceiveOrder(maxItems) } - @OptIn(ExperimentalCoroutinesApi::class) - fun getMeshPacketsFrom(nodeNum: Int) = meshLogDao.getAllLogs(MAX_MESH_PACKETS) - .mapLatest { list -> list.mapNotNull { it.meshPacket }.filter { it.from == nodeNum } } - .distinctUntilChanged() - .flowOn(Dispatchers.IO) + private fun parseTelemetryLog(log: MeshLog): Telemetry? = + runCatching { Telemetry.parseFrom(log.fromRadio.packet.decoded.payload) }.getOrNull() @OptIn(ExperimentalCoroutinesApi::class) - fun getTelemetryFrom(nodeNum: Int) = getMeshPacketsFrom(nodeNum).mapLatest { list -> - list.filter { it.hasDecoded() && it.decoded.portnum == Portnums.PortNum.TELEMETRY_APP } - .mapNotNull { runCatching { Telemetry.parseFrom(it.decoded.payload) }.getOrNull() } - }.flowOn(Dispatchers.IO) + fun getTelemetryFrom(nodeNum: Int): Flow> = + meshLogDao.getLogsFrom(nodeNum, Portnums.PortNum.TELEMETRY_APP_VALUE, MAX_MESH_PACKETS) + .distinctUntilChanged() + .mapLatest { list -> list.mapNotNull(::parseTelemetryLog) } + .flowOn(Dispatchers.IO) suspend fun insert(log: MeshLog) = withContext(Dispatchers.IO) { meshLogDao.insert(log) diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt index b4bf85855..cb44d2289 100644 --- a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt +++ b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt @@ -37,8 +37,9 @@ import com.geeksville.mesh.database.entity.QuickChatAction AutoMigration (from = 8, to = 9), AutoMigration (from = 9, to = 10), AutoMigration (from = 10, to = 11), + AutoMigration (from = 11, to = 12), ], - version = 11, + version = 12, exportSchema = true, ) @TypeConverters(Converters::class) diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/MeshLogDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/MeshLogDao.kt index 72918b0c7..ad82aca40 100644 --- a/app/src/main/java/com/geeksville/mesh/database/dao/MeshLogDao.kt +++ b/app/src/main/java/com/geeksville/mesh/database/dao/MeshLogDao.kt @@ -9,16 +9,25 @@ import kotlinx.coroutines.flow.Flow @Dao interface MeshLogDao { - @Query("Select * from log order by received_date desc limit 0,:maxItem") + @Query("SELECT * FROM log ORDER BY received_date DESC LIMIT 0,:maxItem") fun getAllLogs(maxItem: Int): Flow> - @Query("Select * from log order by received_date asc limit 0,:maxItem") + @Query("SELECT * FROM log ORDER BY received_date ASC LIMIT 0,:maxItem") fun getAllLogsInReceiveOrder(maxItem: Int): Flow> + @Query( + """ + SELECT * FROM log + WHERE from_num = :fromNum AND port_num = :portNum + ORDER BY received_date DESC LIMIT 0,:maxItem + """ + ) + fun getLogsFrom(fromNum: Int, portNum: Int, maxItem: Int): Flow> + @Insert fun insert(log: MeshLog) - @Query("DELETE from log") + @Query("DELETE FROM log") fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/MeshLog.kt b/app/src/main/java/com/geeksville/mesh/database/entity/MeshLog.kt index 0697376a3..73c92d3ee 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/MeshLog.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/MeshLog.kt @@ -2,18 +2,30 @@ package com.geeksville.mesh.database.entity import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Index import androidx.room.PrimaryKey import com.geeksville.mesh.MeshProtos +import com.geeksville.mesh.MeshProtos.FromRadio import com.geeksville.mesh.Portnums import com.google.protobuf.TextFormat import java.io.IOException - -@Entity(tableName = "log") -data class MeshLog(@PrimaryKey val uuid: String, - @ColumnInfo(name = "type") val message_type: String, - @ColumnInfo(name = "received_date") val received_date: Long, - @ColumnInfo(name = "message") val raw_message: String +@Entity( + tableName = "log", + indices = [ + Index(value = ["from_num"]), + Index(value = ["port_num"]), + ], +) +data class MeshLog( + @PrimaryKey val uuid: String, + @ColumnInfo(name = "type") val message_type: String, + @ColumnInfo(name = "received_date") val received_date: Long, + @ColumnInfo(name = "message") val raw_message: String, + @ColumnInfo(name = "from_num", defaultValue = "0") val fromNum: Int = 0, + @ColumnInfo(name = "port_num", defaultValue = "0") val portNum: Int = 0, + @ColumnInfo(name = "from_radio", typeAffinity = ColumnInfo.BLOB, defaultValue = "x''") + val fromRadio: FromRadio = FromRadio.getDefaultInstance(), ) { val meshPacket: MeshProtos.MeshPacket? diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 868eb9d68..58b0b561a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -216,10 +216,13 @@ class MeshService : Service(), Logging { if (p.packet.hasDecoded()) { val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "Packet", - System.currentTimeMillis(), - p.packet.toString() + uuid = UUID.randomUUID().toString(), + message_type = "Packet", + received_date = System.currentTimeMillis(), + raw_message = p.packet.toString(), + fromNum = p.packet.from, + portNum = p.packet.decoded.portnumValue, + fromRadio = fromRadio { packet = p.packet }, ) insertMeshLog(packetToSave) } @@ -1061,10 +1064,13 @@ class MeshService : Service(), Logging { // debug("Recieved: $packet") if (packet.hasDecoded()) { val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "Packet", - System.currentTimeMillis(), - packet.toString() + uuid = UUID.randomUUID().toString(), + message_type = "Packet", + received_date = System.currentTimeMillis(), + raw_message = packet.toString(), + fromNum = packet.from, + portNum = packet.decoded.portnumValue, + fromRadio = fromRadio { this.packet = packet }, ) insertMeshLog(packetToSave) @@ -1320,10 +1326,11 @@ class MeshService : Service(), Logging { private fun handleDeviceConfig(config: ConfigProtos.Config) { debug("Received config ${config.toOneLineString()}") val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "Config ${config.payloadVariantCase}", - System.currentTimeMillis(), - config.toString() + uuid = UUID.randomUUID().toString(), + message_type = "Config ${config.payloadVariantCase}", + received_date = System.currentTimeMillis(), + raw_message = config.toString(), + fromRadio = fromRadio { this.config = config }, ) insertMeshLog(packetToSave) setLocalConfig(config) @@ -1334,10 +1341,11 @@ class MeshService : Service(), Logging { private fun handleModuleConfig(config: ModuleConfigProtos.ModuleConfig) { debug("Received moduleConfig ${config.toOneLineString()}") val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "ModuleConfig ${config.payloadVariantCase}", - System.currentTimeMillis(), - config.toString() + uuid = UUID.randomUUID().toString(), + message_type = "ModuleConfig ${config.payloadVariantCase}", + received_date = System.currentTimeMillis(), + raw_message = config.toString(), + fromRadio = fromRadio { moduleConfig = config }, ) insertMeshLog(packetToSave) setLocalModuleConfig(config) @@ -1358,10 +1366,11 @@ class MeshService : Service(), Logging { private fun handleChannel(ch: ChannelProtos.Channel) { debug("Received channel ${ch.index}") val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "Channel", - System.currentTimeMillis(), - ch.toString() + uuid = UUID.randomUUID().toString(), + message_type = "Channel", + received_date = System.currentTimeMillis(), + raw_message = ch.toString(), + fromRadio = fromRadio { channel = ch }, ) insertMeshLog(packetToSave) if (ch.role != ChannelProtos.Channel.Role.DISABLED) updateChannelSettings(ch) @@ -1404,10 +1413,11 @@ class MeshService : Service(), Logging { debug("Received nodeinfo num=${info.num}, hasUser=${info.hasUser()}, hasPosition=${info.hasPosition()}, hasDeviceMetrics=${info.hasDeviceMetrics()}") val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "NodeInfo", - System.currentTimeMillis(), - info.toString() + uuid = UUID.randomUUID().toString(), + message_type = "NodeInfo", + received_date = System.currentTimeMillis(), + raw_message = info.toString(), + fromRadio = fromRadio { nodeInfo = info }, ) insertMeshLog(packetToSave) @@ -1465,10 +1475,11 @@ class MeshService : Service(), Logging { */ private fun handleMyInfo(myInfo: MeshProtos.MyNodeInfo) { val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "MyNodeInfo", - System.currentTimeMillis(), - myInfo.toString() + uuid = UUID.randomUUID().toString(), + message_type = "MyNodeInfo", + received_date = System.currentTimeMillis(), + raw_message = myInfo.toString(), + fromRadio = fromRadio { this.myInfo = myInfo }, ) insertMeshLog(packetToSave) @@ -1488,10 +1499,11 @@ class MeshService : Service(), Logging { private fun handleMetadata(metadata: MeshProtos.DeviceMetadata) { debug("Received deviceMetadata ${metadata.toOneLineString()}") val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "DeviceMetadata", - System.currentTimeMillis(), - metadata.toString() + uuid = UUID.randomUUID().toString(), + message_type = "DeviceMetadata", + received_date = System.currentTimeMillis(), + raw_message = metadata.toString(), + fromRadio = fromRadio { this.metadata = metadata }, ) insertMeshLog(packetToSave) @@ -1561,10 +1573,11 @@ class MeshService : Service(), Logging { if (configCompleteId == configNonce) { val packetToSave = MeshLog( - UUID.randomUUID().toString(), - "ConfigComplete", - System.currentTimeMillis(), - configCompleteId.toString() + uuid = UUID.randomUUID().toString(), + message_type = "ConfigComplete", + received_date = System.currentTimeMillis(), + raw_message = configCompleteId.toString(), + fromRadio = fromRadio { this.configCompleteId = configCompleteId }, ) insertMeshLog(packetToSave)