mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: migrate core modules to Kotlin Multiplatform and consolidat… (#4735)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
f3775a601c
commit
cffbd08806
265 changed files with 1383 additions and 1340 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-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
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* 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 android.app.Application
|
||||
|
|
@ -26,9 +25,10 @@ import kotlinx.serialization.json.decodeFromStream
|
|||
import org.meshtastic.core.model.BootloaderOtaQuirk
|
||||
import javax.inject.Inject
|
||||
|
||||
class BootloaderOtaQuirksJsonDataSource @Inject constructor(private val application: Application) {
|
||||
class BootloaderOtaQuirksJsonDataSourceImpl @Inject constructor(private val application: Application) :
|
||||
BootloaderOtaQuirksJsonDataSource {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
fun loadBootloaderOtaQuirksFromJsonAsset(): List<BootloaderOtaQuirk> = runCatching {
|
||||
override fun loadBootloaderOtaQuirksFromJsonAsset(): List<BootloaderOtaQuirk> = runCatching {
|
||||
val inputStream = application.assets.open("device_bootloader_ota_quirks.json")
|
||||
inputStream.use { Json.decodeFromStream<ListWrapper>(it).devices }
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-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
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* 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 android.app.Application
|
||||
|
|
@ -24,7 +23,8 @@ import kotlinx.serialization.json.decodeFromStream
|
|||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import javax.inject.Inject
|
||||
|
||||
class DeviceHardwareJsonDataSource @Inject constructor(private val application: Application) {
|
||||
class DeviceHardwareJsonDataSourceImpl @Inject constructor(private val application: Application) :
|
||||
DeviceHardwareJsonDataSource {
|
||||
|
||||
// Use a tolerant JSON parser so that additional fields in the bundled asset
|
||||
// (e.g., "key") do not break deserialization on older app versions.
|
||||
|
|
@ -35,7 +35,7 @@ class DeviceHardwareJsonDataSource @Inject constructor(private val application:
|
|||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
fun loadDeviceHardwareFromJsonAsset(): List<NetworkDeviceHardware> =
|
||||
override fun loadDeviceHardwareFromJsonAsset(): List<NetworkDeviceHardware> =
|
||||
application.assets.open("device_hardware.json").use { inputStream ->
|
||||
json.decodeFromStream<List<NetworkDeviceHardware>>(inputStream)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-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
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* 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 android.app.Application
|
||||
|
|
@ -24,7 +23,8 @@ import kotlinx.serialization.json.decodeFromStream
|
|||
import org.meshtastic.core.model.NetworkFirmwareReleases
|
||||
import javax.inject.Inject
|
||||
|
||||
class FirmwareReleaseJsonDataSource @Inject constructor(private val application: Application) {
|
||||
class FirmwareReleaseJsonDataSourceImpl @Inject constructor(private val application: Application) :
|
||||
FirmwareReleaseJsonDataSource {
|
||||
|
||||
// Match the network client behavior: be tolerant of unknown fields so that
|
||||
// older app versions can read newer snapshots of firmware_releases.json.
|
||||
|
|
@ -35,7 +35,7 @@ class FirmwareReleaseJsonDataSource @Inject constructor(private val application:
|
|||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
fun loadFirmwareReleaseFromJsonAsset(): NetworkFirmwareReleases =
|
||||
override fun loadFirmwareReleaseFromJsonAsset(): NetworkFirmwareReleases =
|
||||
application.assets.open("firmware_releases.json").use { inputStream ->
|
||||
json.decodeFromStream<NetworkFirmwareReleases>(inputStream)
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
|||
import android.Manifest.permission.ACCESS_FINE_LOCATION
|
||||
import android.app.Application
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.core.location.LocationCompat
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
|
|
@ -29,55 +30,61 @@ import androidx.core.location.altitude.AltitudeConverterCompat
|
|||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.repository.Location
|
||||
import org.meshtastic.core.repository.LocationRepository
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LocationRepository
|
||||
class LocationRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
private val context: Application,
|
||||
private val locationManager: dagger.Lazy<LocationManager>,
|
||||
private val analytics: PlatformAnalytics,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
) : LocationRepository {
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_INTERVAL_MS = 30_000L
|
||||
private const val MIN_DISTANCE_METERS = 0f
|
||||
private const val API_LEVEL_31 = 31
|
||||
}
|
||||
|
||||
/** Status of whether the app is actively subscribed to location changes. */
|
||||
private val _receivingLocationUpdates: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
val receivingLocationUpdates: StateFlow<Boolean>
|
||||
override val receivingLocationUpdates: StateFlow<Boolean>
|
||||
get() = _receivingLocationUpdates
|
||||
|
||||
@RequiresPermission(anyOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
|
||||
private fun LocationManager.requestLocationUpdates() = callbackFlow {
|
||||
val intervalMs = 30 * 1000L // 30 seconds
|
||||
val minDistanceM = 0f
|
||||
|
||||
private fun LocationManager.requestLocationUpdates(): Flow<Location> = callbackFlow {
|
||||
val locationRequest =
|
||||
LocationRequestCompat.Builder(intervalMs)
|
||||
.setMinUpdateDistanceMeters(minDistanceM)
|
||||
LocationRequestCompat.Builder(DEFAULT_INTERVAL_MS)
|
||||
.setMinUpdateDistanceMeters(MIN_DISTANCE_METERS)
|
||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||
.build()
|
||||
|
||||
val locationListener = LocationListenerCompat { location ->
|
||||
if (location.hasAltitude() && !LocationCompat.hasMslAltitude(location)) {
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
try {
|
||||
AltitudeConverterCompat.addMslAltitudeToLocation(context, location)
|
||||
} catch (e: Exception) {
|
||||
Logger.e(e) { "addMslAltitudeToLocation() failed" }
|
||||
}
|
||||
}
|
||||
// info("New location: $location")
|
||||
trySend(location)
|
||||
}
|
||||
|
||||
val providerList = buildList {
|
||||
val providers = allProviders
|
||||
if (android.os.Build.VERSION.SDK_INT >= 31 && LocationManager.FUSED_PROVIDER in providers) {
|
||||
if (Build.VERSION.SDK_INT >= API_LEVEL_31 && LocationManager.FUSED_PROVIDER in providers) {
|
||||
add(LocationManager.FUSED_PROVIDER)
|
||||
} else {
|
||||
if (LocationManager.GPS_PROVIDER in providers) add(LocationManager.GPS_PROVIDER)
|
||||
|
|
@ -86,11 +93,13 @@ constructor(
|
|||
}
|
||||
|
||||
Logger.i {
|
||||
"Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m"
|
||||
"Starting location updates with $providerList intervalMs=$DEFAULT_INTERVAL_MS " +
|
||||
"and minDistanceM=$MIN_DISTANCE_METERS"
|
||||
}
|
||||
_receivingLocationUpdates.value = true
|
||||
analytics.track("location_start") // Figure out how many users needed to use the phone GPS
|
||||
analytics.track("location_start")
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
try {
|
||||
providerList.forEach { provider ->
|
||||
LocationManagerCompat.requestLocationUpdates(
|
||||
|
|
@ -102,7 +111,7 @@ constructor(
|
|||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
close(e) // in case of exception, close the Flow
|
||||
close(e)
|
||||
}
|
||||
|
||||
awaitClose {
|
||||
|
|
@ -116,5 +125,5 @@ constructor(
|
|||
|
||||
/** Observable flow for location updates */
|
||||
@RequiresPermission(anyOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
|
||||
fun getLocations() = locationManager.get().requestLocationUpdates()
|
||||
override fun getLocations(): Flow<Location> = locationManager.get().requestLocationUpdates()
|
||||
}
|
||||
|
|
@ -14,18 +14,10 @@
|
|||
* 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.model
|
||||
package org.meshtastic.core.data.datasource
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.uuid.Uuid
|
||||
import org.meshtastic.core.model.BootloaderOtaQuirk
|
||||
|
||||
@Serializable
|
||||
data class CustomTileProviderConfig(
|
||||
val id: String = Uuid.random().toString(),
|
||||
val name: String,
|
||||
val urlTemplate: String,
|
||||
val localUri: String? = null,
|
||||
) {
|
||||
val isLocal: Boolean
|
||||
get() = localUri != null
|
||||
interface BootloaderOtaQuirksJsonDataSource {
|
||||
fun loadBootloaderOtaQuirksFromJsonAsset(): List<BootloaderOtaQuirk>
|
||||
}
|
||||
|
|
@ -14,21 +14,10 @@
|
|||
* 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.di
|
||||
package org.meshtastic.core.data.datasource
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface DatabaseModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindDatabaseManager(
|
||||
impl: org.meshtastic.core.database.DatabaseManager,
|
||||
): org.meshtastic.core.common.database.DatabaseManager
|
||||
interface DeviceHardwareJsonDataSource {
|
||||
fun loadDeviceHardwareFromJsonAsset(): List<NetworkDeviceHardware>
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-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
|
||||
|
|
@ -14,24 +14,10 @@
|
|||
* 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
|
||||
|
||||
package org.meshtastic.core.data.di
|
||||
import org.meshtastic.core.model.NetworkFirmwareReleases
|
||||
|
||||
import android.content.Context
|
||||
import android.location.LocationManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideLocationManager(@ApplicationContext context: Context): LocationManager =
|
||||
context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
interface FirmwareReleaseJsonDataSource {
|
||||
fun loadFirmwareReleaseFromJsonAsset(): NetworkFirmwareReleases
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-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
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* 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
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package org.meshtastic.core.data.manager
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
|
@ -45,12 +46,10 @@ import org.meshtastic.proto.Neighbor
|
|||
import org.meshtastic.proto.NeighborInfo
|
||||
import org.meshtastic.proto.PortNum
|
||||
import org.meshtastic.proto.Telemetry
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
@Suppress("TooManyFunctions", "CyclomaticComplexMethod")
|
||||
|
|
@ -63,10 +62,10 @@ constructor(
|
|||
private val radioConfigRepository: RadioConfigRepository,
|
||||
) : CommandSender {
|
||||
private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val currentPacketId = AtomicLong(java.util.Random(nowMillis).nextLong().absoluteValue)
|
||||
private val sessionPasskey = AtomicReference(ByteString.EMPTY)
|
||||
override val tracerouteStartTimes = ConcurrentHashMap<Int, Long>()
|
||||
override val neighborInfoStartTimes = ConcurrentHashMap<Int, Long>()
|
||||
private val currentPacketId = atomic(Random(nowMillis).nextLong().absoluteValue)
|
||||
private val sessionPasskey = atomic(ByteString.EMPTY)
|
||||
override val tracerouteStartTimes = mutableMapOf<Int, Long>()
|
||||
override val neighborInfoStartTimes = mutableMapOf<Int, Long>()
|
||||
|
||||
private val localConfig = MutableStateFlow(LocalConfig())
|
||||
private val channelSet = MutableStateFlow(ChannelSet())
|
||||
|
|
@ -87,7 +86,7 @@ constructor(
|
|||
|
||||
override fun getCachedChannelSet(): ChannelSet = channelSet.value
|
||||
|
||||
override fun getCurrentPacketId(): Long = currentPacketId.get()
|
||||
override fun getCurrentPacketId(): Long = currentPacketId.value
|
||||
|
||||
override fun generatePacketId(): Int {
|
||||
val numPacketIds = ((1L shl PACKET_ID_SHIFT_BITS) - 1)
|
||||
|
|
@ -96,7 +95,7 @@ constructor(
|
|||
}
|
||||
|
||||
override fun setSessionPasskey(key: ByteString) {
|
||||
sessionPasskey.set(key)
|
||||
sessionPasskey.value = key
|
||||
}
|
||||
|
||||
private fun computeHopLimit(): Int = (localConfig.value.lora?.hop_limit ?: 0).takeIf { it > 0 } ?: DEFAULT_HOP_LIMIT
|
||||
|
|
@ -167,7 +166,7 @@ constructor(
|
|||
}
|
||||
|
||||
override fun sendAdmin(destNum: Int, requestId: Int, wantResponse: Boolean, initFn: () -> AdminMessage) {
|
||||
val adminMsg = initFn().copy(session_passkey = sessionPasskey.get())
|
||||
val adminMsg = initFn().copy(session_passkey = sessionPasskey.value)
|
||||
val packet =
|
||||
buildAdminPacket(to = destNum, id = requestId, wantResponse = wantResponse, adminMessage = adminMsg)
|
||||
packetHandler.sendToRadio(packet)
|
||||
|
|
@ -21,8 +21,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.meshtastic.core.analytics.DataPair
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import org.meshtastic.core.common.database.DatabaseManager
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.ignoreException
|
||||
|
|
@ -34,6 +32,7 @@ import org.meshtastic.core.model.Position
|
|||
import org.meshtastic.core.model.Reaction
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.DataPair
|
||||
import org.meshtastic.core.repository.MeshActionHandler
|
||||
import org.meshtastic.core.repository.MeshDataHandler
|
||||
import org.meshtastic.core.repository.MeshMessageProcessor
|
||||
|
|
@ -41,6 +40,7 @@ import org.meshtastic.core.repository.MeshPrefs
|
|||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.ServiceBroadcasts
|
||||
import org.meshtastic.proto.AdminMessage
|
||||
import org.meshtastic.proto.Channel
|
||||
|
|
@ -22,7 +22,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import okio.IOException
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
|
|
@ -31,6 +31,7 @@ import org.meshtastic.core.repository.MeshConnectionManager
|
|||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceBroadcasts
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
|
|
@ -39,7 +40,6 @@ import org.meshtastic.proto.HardwareModel
|
|||
import org.meshtastic.proto.Heartbeat
|
||||
import org.meshtastic.proto.NodeInfo
|
||||
import org.meshtastic.proto.ToRadio
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import org.meshtastic.core.model.MyNodeInfo as SharedMyNodeInfo
|
||||
|
|
@ -28,8 +28,6 @@ import kotlinx.coroutines.flow.first
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.analytics.DataPair
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
|
|
@ -37,6 +35,7 @@ import org.meshtastic.core.model.ConnectionState
|
|||
import org.meshtastic.core.model.TelemetryType
|
||||
import org.meshtastic.core.repository.AppWidgetUpdater
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.DataPair
|
||||
import org.meshtastic.core.repository.HistoryManager
|
||||
import org.meshtastic.core.repository.MeshConnectionManager
|
||||
import org.meshtastic.core.repository.MeshLocationManager
|
||||
|
|
@ -47,6 +46,7 @@ import org.meshtastic.core.repository.NodeManager
|
|||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.ServiceBroadcasts
|
||||
|
|
@ -24,9 +24,11 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.meshtastic.core.analytics.DataPair
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import okio.IOException
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
|
|
@ -39,6 +41,7 @@ import org.meshtastic.core.model.util.SfppHasher
|
|||
import org.meshtastic.core.model.util.decodeOrNull
|
||||
import org.meshtastic.core.model.util.toOneLiner
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.DataPair
|
||||
import org.meshtastic.core.repository.HistoryManager
|
||||
import org.meshtastic.core.repository.MeshConfigFlowManager
|
||||
import org.meshtastic.core.repository.MeshConfigHandler
|
||||
|
|
@ -50,6 +53,7 @@ import org.meshtastic.core.repository.NeighborInfoHandler
|
|||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceBroadcasts
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
|
|
@ -72,8 +76,6 @@ import org.meshtastic.proto.StoreForwardPlusPlus
|
|||
import org.meshtastic.proto.Telemetry
|
||||
import org.meshtastic.proto.User
|
||||
import org.meshtastic.proto.Waypoint
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
|
@ -355,11 +357,11 @@ constructor(
|
|||
u.session_passkey.let { commandSender.setSessionPasskey(it) }
|
||||
|
||||
val fromNum = packet.from
|
||||
u.get_module_config_response?.let { config ->
|
||||
u.get_module_config_response?.let {
|
||||
if (fromNum == myNodeNum) {
|
||||
configHandler.get().handleModuleConfig(config)
|
||||
configHandler.get().handleModuleConfig(it)
|
||||
} else {
|
||||
config.statusmessage?.node_status?.let { nodeManager.updateNodeStatus(fromNum, it) }
|
||||
it.statusmessage?.node_status?.let { nodeManager.updateNodeStatus(fromNum, it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -368,11 +370,11 @@ constructor(
|
|||
u.get_channel_response?.let { configHandler.get().handleChannel(it) }
|
||||
}
|
||||
|
||||
u.get_device_metadata_response?.let { metadata ->
|
||||
u.get_device_metadata_response?.let {
|
||||
if (fromNum == myNodeNum) {
|
||||
configFlowManager.get().handleLocalMetadata(metadata)
|
||||
configFlowManager.get().handleLocalMetadata(it)
|
||||
} else {
|
||||
nodeManager.insertMetadata(fromNum, metadata)
|
||||
nodeManager.insertMetadata(fromNum, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -429,14 +431,20 @@ constructor(
|
|||
(metrics.voltage ?: 0f) > BATTERY_PERCENT_UNSUPPORTED &&
|
||||
(metrics.battery_level ?: 0) <= BATTERY_PERCENT_LOW_THRESHOLD
|
||||
) {
|
||||
if (shouldBatteryNotificationShow(fromNum, t, myNodeNum)) {
|
||||
serviceNotifications.showOrUpdateLowBatteryNotification(nextNode, isRemote)
|
||||
scope.launch {
|
||||
if (shouldBatteryNotificationShow(fromNum, t, myNodeNum)) {
|
||||
serviceNotifications.showOrUpdateLowBatteryNotification(nextNode, isRemote)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (batteryPercentCooldowns.containsKey(fromNum)) {
|
||||
batteryPercentCooldowns.remove(fromNum)
|
||||
scope.launch {
|
||||
batteryMutex.withLock {
|
||||
if (batteryPercentCooldowns.containsKey(fromNum)) {
|
||||
batteryPercentCooldowns.remove(fromNum)
|
||||
}
|
||||
}
|
||||
serviceNotifications.cancelLowBatteryNotification(nextNode)
|
||||
}
|
||||
serviceNotifications.cancelLowBatteryNotification(nextNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -451,7 +459,7 @@ constructor(
|
|||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
private fun shouldBatteryNotificationShow(fromNum: Int, t: Telemetry, myNodeNum: Int): Boolean {
|
||||
private suspend fun shouldBatteryNotificationShow(fromNum: Int, t: Telemetry, myNodeNum: Int): Boolean {
|
||||
val isRemote = (fromNum != myNodeNum)
|
||||
var shouldDisplay = false
|
||||
var forceDisplay = false
|
||||
|
|
@ -470,10 +478,12 @@ constructor(
|
|||
}
|
||||
if (shouldDisplay) {
|
||||
val now = nowSeconds
|
||||
if (!batteryPercentCooldowns.containsKey(fromNum)) batteryPercentCooldowns[fromNum] = 0L
|
||||
if ((now - batteryPercentCooldowns[fromNum]!!) >= BATTERY_PERCENT_COOLDOWN_SECONDS || forceDisplay) {
|
||||
batteryPercentCooldowns[fromNum] = now
|
||||
return true
|
||||
batteryMutex.withLock {
|
||||
if (!batteryPercentCooldowns.containsKey(fromNum)) batteryPercentCooldowns[fromNum] = 0L
|
||||
if ((now - batteryPercentCooldowns[fromNum]!!) >= BATTERY_PERCENT_COOLDOWN_SECONDS || forceDisplay) {
|
||||
batteryPercentCooldowns[fromNum] = now
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
@ -775,6 +785,7 @@ constructor(
|
|||
private const val BATTERY_PERCENT_LOW_DIVISOR = 5
|
||||
private const val BATTERY_PERCENT_CRITICAL_THRESHOLD = 5
|
||||
private const val BATTERY_PERCENT_COOLDOWN_SECONDS = 1500
|
||||
private val batteryPercentCooldowns = ConcurrentHashMap<Int, Long>()
|
||||
private val batteryMutex = Mutex()
|
||||
private val batteryPercentCooldowns = mutableMapOf<Int, Long>()
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,9 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
|
|
@ -40,9 +43,6 @@ import org.meshtastic.proto.FromRadio
|
|||
import org.meshtastic.proto.LogRecord
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
import org.meshtastic.proto.PortNum
|
||||
import java.util.ArrayDeque
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.uuid.Uuid
|
||||
|
|
@ -60,14 +60,17 @@ constructor(
|
|||
private val fromRadioDispatcher: FromRadioPacketHandler,
|
||||
) : MeshMessageProcessor {
|
||||
private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val logUuidByPacketId = ConcurrentHashMap<Int, String>()
|
||||
private val logInsertJobByPacketId = ConcurrentHashMap<Int, Job>()
|
||||
|
||||
private val earlyReceivedPackets = ArrayDeque<MeshPacket>()
|
||||
private val mapsMutex = Mutex()
|
||||
private val logUuidByPacketId = mutableMapOf<Int, String>()
|
||||
private val logInsertJobByPacketId = mutableMapOf<Int, Job>()
|
||||
|
||||
private val earlyMutex = Mutex()
|
||||
private val earlyReceivedPackets = kotlin.collections.ArrayDeque<MeshPacket>()
|
||||
private val maxEarlyPacketBuffer = 10240
|
||||
|
||||
override fun clearEarlyPackets() {
|
||||
synchronized(earlyReceivedPackets) { earlyReceivedPackets.clear() }
|
||||
scope.launch { earlyMutex.withLock { earlyReceivedPackets.clear() } }
|
||||
}
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
|
|
@ -91,8 +94,7 @@ constructor(
|
|||
}
|
||||
.onFailure { _ ->
|
||||
Logger.e(primaryException) {
|
||||
"Failed to parse radio packet (len=${bytes.size} contents=${bytes.toHexString()}). " +
|
||||
"Not a valid FromRadio or LogRecord."
|
||||
"Failed to parse radio packet (len=${bytes.size}). " + "Not a valid FromRadio or LogRecord."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -150,27 +152,33 @@ constructor(
|
|||
if (nodeManager.isNodeDbReady.value) {
|
||||
processReceivedMeshPacket(preparedPacket, myNodeNum)
|
||||
} else {
|
||||
synchronized(earlyReceivedPackets) {
|
||||
val queueSize = earlyReceivedPackets.size
|
||||
if (queueSize >= maxEarlyPacketBuffer) {
|
||||
earlyReceivedPackets.removeFirst()
|
||||
scope.launch {
|
||||
earlyMutex.withLock {
|
||||
val queueSize = earlyReceivedPackets.size
|
||||
if (queueSize >= maxEarlyPacketBuffer) {
|
||||
earlyReceivedPackets.removeFirstOrNull()
|
||||
}
|
||||
earlyReceivedPackets.addLast(preparedPacket)
|
||||
}
|
||||
earlyReceivedPackets.addLast(preparedPacket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun flushEarlyReceivedPackets(reason: String) {
|
||||
val packets =
|
||||
synchronized(earlyReceivedPackets) {
|
||||
if (earlyReceivedPackets.isEmpty()) return
|
||||
val list = earlyReceivedPackets.toList()
|
||||
earlyReceivedPackets.clear()
|
||||
list
|
||||
}
|
||||
Logger.d { "replayEarlyPackets reason=$reason count=${packets.size}" }
|
||||
val myNodeNum = nodeManager.myNodeNum
|
||||
packets.forEach { processReceivedMeshPacket(it, myNodeNum) }
|
||||
scope.launch {
|
||||
val packets =
|
||||
earlyMutex.withLock {
|
||||
if (earlyReceivedPackets.isEmpty()) return@withLock emptyList<MeshPacket>()
|
||||
val list = earlyReceivedPackets.toList()
|
||||
earlyReceivedPackets.clear()
|
||||
list
|
||||
}
|
||||
if (packets.isEmpty()) return@launch
|
||||
|
||||
Logger.d { "replayEarlyPackets reason=$reason count=${packets.size}" }
|
||||
val myNodeNum = nodeManager.myNodeNum
|
||||
packets.forEach { processReceivedMeshPacket(it, myNodeNum) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -187,8 +195,13 @@ constructor(
|
|||
fromRadio = FromRadio(packet = packet),
|
||||
)
|
||||
val logJob = insertMeshLog(log)
|
||||
logInsertJobByPacketId[packet.id] = logJob
|
||||
logUuidByPacketId[packet.id] = log.uuid
|
||||
|
||||
scope.launch {
|
||||
mapsMutex.withLock {
|
||||
logInsertJobByPacketId[packet.id] = logJob
|
||||
logUuidByPacketId[packet.id] = log.uuid
|
||||
}
|
||||
}
|
||||
|
||||
scope.handledLaunch { serviceRepository.emitMeshPacket(packet) }
|
||||
|
||||
|
|
@ -235,14 +248,15 @@ constructor(
|
|||
try {
|
||||
router.get().dataHandler.handleReceivedData(packet, myNum, log.uuid, logJob)
|
||||
} finally {
|
||||
logUuidByPacketId.remove(packet.id)
|
||||
logInsertJobByPacketId.remove(packet.id)
|
||||
scope.launch {
|
||||
mapsMutex.withLock {
|
||||
logUuidByPacketId.remove(packet.id)
|
||||
logInsertJobByPacketId.remove(packet.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertMeshLog(log: MeshLog): Job = scope.handledLaunch { meshLogRepository.get().insert(log) }
|
||||
|
||||
private fun ByteArray.toHexString(): String =
|
||||
this.joinToString(",") { byte -> String.format(Locale.US, "0x%02x", byte) }
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ package org.meshtastic.core.data.manager
|
|||
import co.touchlab.kermit.Logger
|
||||
import org.meshtastic.core.repository.FilterPrefs
|
||||
import org.meshtastic.core.repository.MessageFilter
|
||||
import java.util.regex.PatternSyntaxException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -49,7 +48,7 @@ class MessageFilterImpl @Inject constructor(private val filterPrefs: FilterPrefs
|
|||
} else {
|
||||
Regex("\\b${Regex.escape(word)}\\b", RegexOption.IGNORE_CASE)
|
||||
}
|
||||
} catch (e: PatternSyntaxException) {
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Logger.w { "Invalid filter pattern: $word - ${e.message}" }
|
||||
null
|
||||
}
|
||||
|
|
@ -17,6 +17,9 @@
|
|||
package org.meshtastic.core.data.manager
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.atomicfu.update
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
|
@ -42,21 +45,12 @@ import org.meshtastic.proto.Paxcount
|
|||
import org.meshtastic.proto.StatusMessage
|
||||
import org.meshtastic.proto.Telemetry
|
||||
import org.meshtastic.proto.User
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import org.meshtastic.proto.NodeInfo as ProtoNodeInfo
|
||||
import org.meshtastic.proto.Position as ProtoPosition
|
||||
|
||||
/**
|
||||
* Implementation of [NodeManager] that maintains an in-memory database of the mesh.
|
||||
*
|
||||
* This component acts as the "brain" for node-related data during a connection session. It manages:
|
||||
* 1. In-memory maps for fast node lookup by number or ID.
|
||||
* 2. Synchronization of node data between the radio and the persistent database.
|
||||
* 3. Processing of incoming node-related packets (User, Position, Telemetry).
|
||||
* 4. Broadcasting changes to the rest of the application.
|
||||
*/
|
||||
/** Implementation of [NodeManager] that maintains an in-memory database of the mesh. */
|
||||
@Suppress("LongParameterList", "TooManyFunctions", "CyclomaticComplexMethod")
|
||||
@Singleton
|
||||
class NodeManagerImpl
|
||||
|
|
@ -68,8 +62,14 @@ constructor(
|
|||
) : NodeManager {
|
||||
private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
override val nodeDBbyNodeNum = ConcurrentHashMap<Int, Node>()
|
||||
override val nodeDBbyID = ConcurrentHashMap<String, Node>()
|
||||
private val _nodeDBbyNodeNum = atomic(persistentMapOf<Int, Node>())
|
||||
private val _nodeDBbyID = atomic(persistentMapOf<String, Node>())
|
||||
|
||||
override val nodeDBbyNodeNum: Map<Int, Node>
|
||||
get() = _nodeDBbyNodeNum.value
|
||||
|
||||
override val nodeDBbyID: Map<String, Node>
|
||||
get() = _nodeDBbyID.value
|
||||
|
||||
override val isNodeDbReady = MutableStateFlow(false)
|
||||
override val allowNodeDbWrites = MutableStateFlow(false)
|
||||
|
|
@ -95,15 +95,17 @@ constructor(
|
|||
override fun loadCachedNodeDB() {
|
||||
scope.handledLaunch {
|
||||
val nodes = nodeRepository.nodeDBbyNum.first()
|
||||
nodeDBbyNodeNum.putAll(nodes)
|
||||
nodes.values.forEach { nodeDBbyID[it.user.id] = it }
|
||||
_nodeDBbyNodeNum.value = persistentMapOf<Int, Node>().putAll(nodes)
|
||||
val byId = mutableMapOf<String, Node>()
|
||||
nodes.values.forEach { byId[it.user.id] = it }
|
||||
_nodeDBbyID.value = persistentMapOf<String, Node>().putAll(byId)
|
||||
myNodeNum = nodeRepository.myNodeInfo.value?.myNodeNum
|
||||
}
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
nodeDBbyNodeNum.clear()
|
||||
nodeDBbyID.clear()
|
||||
_nodeDBbyNodeNum.value = persistentMapOf()
|
||||
_nodeDBbyID.value = persistentMapOf()
|
||||
isNodeDbReady.value = false
|
||||
allowNodeDbWrites.value = false
|
||||
myNodeNum = null
|
||||
|
|
@ -111,7 +113,7 @@ constructor(
|
|||
|
||||
override fun getMyNodeInfo(): MyNodeInfo? {
|
||||
val mi = nodeRepository.myNodeInfo.value ?: return null
|
||||
val myNode = nodeDBbyNodeNum[mi.myNodeNum]
|
||||
val myNode = _nodeDBbyNodeNum.value[mi.myNodeNum]
|
||||
return MyNodeInfo(
|
||||
myNodeNum = mi.myNodeNum,
|
||||
hasGPS = (myNode?.position?.latitude_i ?: 0) != 0,
|
||||
|
|
@ -132,34 +134,41 @@ constructor(
|
|||
|
||||
override fun getMyId(): String {
|
||||
val num = myNodeNum ?: nodeRepository.myNodeInfo.value?.myNodeNum ?: return ""
|
||||
return nodeDBbyNodeNum[num]?.user?.id ?: ""
|
||||
return _nodeDBbyNodeNum.value[num]?.user?.id ?: ""
|
||||
}
|
||||
|
||||
override fun getNodes(): List<NodeInfo> = nodeDBbyNodeNum.values.map { it.toNodeInfo() }
|
||||
override fun getNodes(): List<NodeInfo> = _nodeDBbyNodeNum.value.values.map { it.toNodeInfo() }
|
||||
|
||||
override fun removeByNodenum(nodeNum: Int) {
|
||||
nodeDBbyNodeNum.remove(nodeNum)?.let { nodeDBbyID.remove(it.user.id) }
|
||||
val removed = atomic<Node?>(null)
|
||||
_nodeDBbyNodeNum.update { map ->
|
||||
val node = map[nodeNum]
|
||||
removed.value = node
|
||||
map.remove(nodeNum)
|
||||
}
|
||||
removed.value?.let { node -> _nodeDBbyID.update { it.remove(node.user.id) } }
|
||||
}
|
||||
|
||||
fun getOrCreateNode(n: Int, channel: Int = 0): Node = nodeDBbyNodeNum.getOrPut(n) {
|
||||
val userId = DataPacket.nodeNumToDefaultId(n)
|
||||
val defaultUser =
|
||||
User(
|
||||
id = userId,
|
||||
long_name = "Meshtastic ${userId.takeLast(n = 4)}",
|
||||
short_name = userId.takeLast(n = 4),
|
||||
hw_model = HardwareModel.UNSET,
|
||||
)
|
||||
internal fun getOrCreateNode(n: Int, channel: Int = 0): Node = _nodeDBbyNodeNum.value[n]
|
||||
?: run {
|
||||
val userId = DataPacket.nodeNumToDefaultId(n)
|
||||
val defaultUser =
|
||||
User(
|
||||
id = userId,
|
||||
long_name = "Meshtastic ${userId.takeLast(n = 4)}",
|
||||
short_name = userId.takeLast(n = 4),
|
||||
hw_model = HardwareModel.UNSET,
|
||||
)
|
||||
|
||||
Node(num = n, user = defaultUser, channel = channel)
|
||||
}
|
||||
Node(num = n, user = defaultUser, channel = channel)
|
||||
}
|
||||
|
||||
override fun updateNode(nodeNum: Int, withBroadcast: Boolean, channel: Int, transform: (Node) -> Node) {
|
||||
val current = nodeDBbyNodeNum[nodeNum] ?: getOrCreateNode(nodeNum, channel)
|
||||
val next = transform(current)
|
||||
nodeDBbyNodeNum[nodeNum] = next
|
||||
val next = transform(_nodeDBbyNodeNum.value[nodeNum] ?: getOrCreateNode(nodeNum, channel))
|
||||
|
||||
_nodeDBbyNodeNum.update { it.put(nodeNum, next) }
|
||||
if (next.user.id.isNotEmpty()) {
|
||||
nodeDBbyID[next.user.id] = next
|
||||
_nodeDBbyID.update { it.put(next.user.id, next) }
|
||||
}
|
||||
|
||||
if (next.user.id.isNotEmpty() && isNodeDbReady.value) {
|
||||
|
|
@ -252,7 +261,8 @@ constructor(
|
|||
if (shouldPreserveExistingUser(node.user, user)) {
|
||||
// keep existing names
|
||||
} else {
|
||||
var newUser = user.let { if (it.is_licensed) it.copy(public_key = ByteString.EMPTY) else it }
|
||||
var newUser =
|
||||
user.let { if (it.is_licensed == true) it.copy(public_key = ByteString.EMPTY) else it }
|
||||
if (info.via_mqtt) {
|
||||
newUser = newUser.copy(long_name = "${newUser.long_name} (MQTT)")
|
||||
}
|
||||
|
|
@ -292,7 +302,7 @@ constructor(
|
|||
override fun toNodeID(nodeNum: Int): String = if (nodeNum == DataPacket.NODENUM_BROADCAST) {
|
||||
DataPacket.ID_BROADCAST
|
||||
} else {
|
||||
nodeDBbyNodeNum[nodeNum]?.user?.id ?: DataPacket.nodeNumToDefaultId(nodeNum)
|
||||
_nodeDBbyNodeNum.value[nodeNum]?.user?.id ?: DataPacket.nodeNumToDefaultId(nodeNum)
|
||||
}
|
||||
|
||||
private fun Node.toNodeInfo(): NodeInfo = NodeInfo(
|
||||
|
|
@ -24,6 +24,9 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
|
|
@ -45,8 +48,6 @@ import org.meshtastic.proto.FromRadio
|
|||
import org.meshtastic.proto.MeshPacket
|
||||
import org.meshtastic.proto.QueueStatus
|
||||
import org.meshtastic.proto.ToRadio
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
|
@ -72,8 +73,11 @@ constructor(
|
|||
private var queueJob: Job? = null
|
||||
private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
private val queuedPackets = ConcurrentLinkedQueue<MeshPacket>()
|
||||
private val queueResponse = ConcurrentHashMap<Int, CompletableDeferred<Boolean>>()
|
||||
private val queueMutex = Mutex()
|
||||
private val queuedPackets = mutableListOf<MeshPacket>()
|
||||
|
||||
private val responseMutex = Mutex()
|
||||
private val queueResponse = mutableMapOf<Int, CompletableDeferred<Boolean>>()
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
|
|
@ -103,8 +107,10 @@ constructor(
|
|||
}
|
||||
|
||||
override fun sendToRadio(packet: MeshPacket) {
|
||||
queuedPackets.add(packet)
|
||||
startPacketQueue()
|
||||
scope.launch {
|
||||
queueMutex.withLock { queuedPackets.add(packet) }
|
||||
startPacketQueue()
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopPacketQueue() {
|
||||
|
|
@ -112,9 +118,13 @@ constructor(
|
|||
Logger.i { "Stopping packet queueJob" }
|
||||
queueJob?.cancel()
|
||||
queueJob = null
|
||||
queuedPackets.clear()
|
||||
queueResponse.entries.lastOrNull { !it.value.isCompleted }?.value?.complete(false)
|
||||
queueResponse.clear()
|
||||
scope.launch {
|
||||
queueMutex.withLock { queuedPackets.clear() }
|
||||
responseMutex.withLock {
|
||||
queueResponse.values.lastOrNull { !it.isCompleted }?.complete(false)
|
||||
queueResponse.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,15 +132,20 @@ constructor(
|
|||
Logger.d { "[queueStatus] ${queueStatus.toOneLineString()}" }
|
||||
val (success, isFull, requestId) = with(queueStatus) { Triple(res == 0, free == 0, mesh_packet_id) }
|
||||
if (success && isFull) return
|
||||
if (requestId != 0) {
|
||||
queueResponse.remove(requestId)?.complete(success)
|
||||
} else {
|
||||
queueResponse.values.firstOrNull { !it.isCompleted }?.complete(success)
|
||||
|
||||
scope.launch {
|
||||
responseMutex.withLock {
|
||||
if (requestId != 0) {
|
||||
queueResponse.remove(requestId)?.complete(success)
|
||||
} else {
|
||||
queueResponse.values.firstOrNull { !it.isCompleted }?.complete(success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeResponse(dataRequestId: Int, complete: Boolean) {
|
||||
queueResponse.remove(dataRequestId)?.complete(complete)
|
||||
scope.launch { responseMutex.withLock { queueResponse.remove(dataRequestId)?.complete(complete) } }
|
||||
}
|
||||
|
||||
private fun startPacketQueue() {
|
||||
|
|
@ -138,20 +153,27 @@ constructor(
|
|||
queueJob =
|
||||
scope.handledLaunch {
|
||||
Logger.d { "packet queueJob started" }
|
||||
while (serviceRepository.connectionState.value == ConnectionState.Connected) {
|
||||
val packet = queuedPackets.poll() ?: break
|
||||
@Suppress("TooGenericExceptionCaught", "SwallowedException")
|
||||
try {
|
||||
val response = sendPacket(packet)
|
||||
Logger.d { "queueJob packet id=${packet.id.toUInt()} waiting" }
|
||||
val success = withTimeout(TIMEOUT) { response.await() }
|
||||
Logger.d { "queueJob packet id=${packet.id.toUInt()} success $success" }
|
||||
} catch (e: TimeoutCancellationException) {
|
||||
Logger.d { "queueJob packet id=${packet.id.toUInt()} timeout" }
|
||||
} catch (e: Exception) {
|
||||
Logger.d { "queueJob packet id=${packet.id.toUInt()} failed" }
|
||||
} finally {
|
||||
queueResponse.remove(packet.id)
|
||||
try {
|
||||
while (serviceRepository.connectionState.value == ConnectionState.Connected) {
|
||||
val packet = queueMutex.withLock { queuedPackets.removeFirstOrNull() } ?: break
|
||||
@Suppress("TooGenericExceptionCaught", "SwallowedException")
|
||||
try {
|
||||
val response = sendPacket(packet)
|
||||
Logger.d { "queueJob packet id=${packet.id.toUInt()} waiting" }
|
||||
val success = withTimeout(TIMEOUT) { response.await() }
|
||||
Logger.d { "queueJob packet id=${packet.id.toUInt()} success $success" }
|
||||
} catch (e: TimeoutCancellationException) {
|
||||
Logger.d { "queueJob packet id=${packet.id.toUInt()} timeout" }
|
||||
} catch (e: Exception) {
|
||||
Logger.d { "queueJob packet id=${packet.id.toUInt()} failed" }
|
||||
} finally {
|
||||
responseMutex.withLock { queueResponse.remove(packet.id) }
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
queueJob = null
|
||||
if (queueMutex.withLock { queuedPackets.isNotEmpty() }) {
|
||||
startPacketQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -177,9 +199,9 @@ constructor(
|
|||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
private fun sendPacket(packet: MeshPacket): CompletableDeferred<Boolean> {
|
||||
private suspend fun sendPacket(packet: MeshPacket): CompletableDeferred<Boolean> {
|
||||
val deferred = CompletableDeferred<Boolean>()
|
||||
queueResponse[packet.id] = deferred
|
||||
responseMutex.withLock { queueResponse[packet.id] = deferred }
|
||||
try {
|
||||
if (serviceRepository.connectionState.value != ConnectionState.Connected) {
|
||||
throw RadioNotConnectedException()
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-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
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* 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.repository
|
||||
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
|
|
@ -31,7 +31,6 @@ import org.junit.After
|
|||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
|
|
@ -47,6 +46,7 @@ import org.meshtastic.core.repository.NodeManager
|
|||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.ServiceBroadcasts
|
||||
|
|
@ -28,7 +28,6 @@ import kotlinx.coroutines.test.runTest
|
|||
import okio.ByteString.Companion.toByteString
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.util.MeshDataMapper
|
||||
|
|
@ -43,6 +42,7 @@ import org.meshtastic.core.repository.NeighborInfoHandler
|
|||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceBroadcasts
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.meshtastic.core.data.repository.CustomTileProviderRepository
|
||||
import org.meshtastic.core.data.repository.CustomTileProviderRepositoryImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface GoogleDataModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCustomTileProviderRepository(impl: CustomTileProviderRepositoryImpl): CustomTileProviderRepository
|
||||
|
||||
companion object {
|
||||
@Provides @Singleton
|
||||
fun provideJson(): Json = Json { prettyPrint = false }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025-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.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.meshtastic.core.data.model.CustomTileProviderConfig
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.repository.MapTileProviderPrefs
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
interface CustomTileProviderRepository {
|
||||
fun getCustomTileProviders(): Flow<List<CustomTileProviderConfig>>
|
||||
|
||||
suspend fun addCustomTileProvider(config: CustomTileProviderConfig)
|
||||
|
||||
suspend fun updateCustomTileProvider(config: CustomTileProviderConfig)
|
||||
|
||||
suspend fun deleteCustomTileProvider(configId: String)
|
||||
|
||||
suspend fun getCustomTileProviderById(configId: String): CustomTileProviderConfig?
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class CustomTileProviderRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
private val json: Json,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val mapTileProviderPrefs: MapTileProviderPrefs,
|
||||
) : CustomTileProviderRepository {
|
||||
|
||||
private val customTileProvidersStateFlow = MutableStateFlow<List<CustomTileProviderConfig>>(emptyList())
|
||||
|
||||
init {
|
||||
loadDataFromPrefs()
|
||||
}
|
||||
|
||||
override fun getCustomTileProviders(): Flow<List<CustomTileProviderConfig>> =
|
||||
customTileProvidersStateFlow.asStateFlow()
|
||||
|
||||
override suspend fun addCustomTileProvider(config: CustomTileProviderConfig) {
|
||||
val newList = customTileProvidersStateFlow.value + config
|
||||
customTileProvidersStateFlow.value = newList
|
||||
saveDataToPrefs(newList)
|
||||
}
|
||||
|
||||
override suspend fun updateCustomTileProvider(config: CustomTileProviderConfig) {
|
||||
val newList = customTileProvidersStateFlow.value.map { if (it.id == config.id) config else it }
|
||||
customTileProvidersStateFlow.value = newList
|
||||
saveDataToPrefs(newList)
|
||||
}
|
||||
|
||||
override suspend fun deleteCustomTileProvider(configId: String) {
|
||||
val newList = customTileProvidersStateFlow.value.filterNot { it.id == configId }
|
||||
customTileProvidersStateFlow.value = newList
|
||||
saveDataToPrefs(newList)
|
||||
}
|
||||
|
||||
override suspend fun getCustomTileProviderById(configId: String): CustomTileProviderConfig? =
|
||||
customTileProvidersStateFlow.value.find { it.id == configId }
|
||||
|
||||
private fun loadDataFromPrefs() {
|
||||
val jsonString = mapTileProviderPrefs.customTileProviders.value
|
||||
if (jsonString != null) {
|
||||
try {
|
||||
customTileProvidersStateFlow.value = json.decodeFromString<List<CustomTileProviderConfig>>(jsonString)
|
||||
} catch (e: SerializationException) {
|
||||
Logger.e(e) { "Error deserializing tile providers" }
|
||||
customTileProvidersStateFlow.value = emptyList()
|
||||
}
|
||||
} else {
|
||||
customTileProvidersStateFlow.value = emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveDataToPrefs(providers: List<CustomTileProviderConfig>) {
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
val jsonString = json.encodeToString(providers)
|
||||
mapTileProviderPrefs.setCustomTileProviders(jsonString)
|
||||
} catch (e: SerializationException) {
|
||||
Logger.e(e) { "Error serializing tile providers" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025-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.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.core.okio.OkioStorage
|
||||
import androidx.datastore.dataStoreFile
|
||||
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import okio.FileSystem
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import org.meshtastic.core.datastore.KEY_APP_INTRO_COMPLETED
|
||||
import org.meshtastic.core.datastore.KEY_INCLUDE_UNKNOWN
|
||||
import org.meshtastic.core.datastore.KEY_NODE_SORT
|
||||
import org.meshtastic.core.datastore.KEY_ONLY_DIRECT
|
||||
import org.meshtastic.core.datastore.KEY_ONLY_ONLINE
|
||||
import org.meshtastic.core.datastore.KEY_SHOW_IGNORED
|
||||
import org.meshtastic.core.datastore.KEY_THEME
|
||||
import org.meshtastic.core.datastore.serializer.ChannelSetSerializer
|
||||
import org.meshtastic.core.datastore.serializer.LocalConfigSerializer
|
||||
import org.meshtastic.core.datastore.serializer.LocalStatsSerializer
|
||||
import org.meshtastic.core.datastore.serializer.ModuleConfigSerializer
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import org.meshtastic.proto.LocalModuleConfig
|
||||
import org.meshtastic.proto.LocalStats
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val USER_PREFERENCES_NAME = "user_preferences"
|
||||
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Qualifier
|
||||
annotation class DataStoreScope
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object DataStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@DataStoreScope
|
||||
fun provideDataStoreScope(): CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providePreferencesDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<Preferences> = PreferenceDataStoreFactory.create(
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }),
|
||||
migrations =
|
||||
listOf(
|
||||
SharedPreferencesMigration(context = appContext, sharedPreferencesName = USER_PREFERENCES_NAME),
|
||||
SharedPreferencesMigration(
|
||||
context = appContext,
|
||||
sharedPreferencesName = "ui-prefs",
|
||||
keysToMigrate =
|
||||
setOf(
|
||||
KEY_APP_INTRO_COMPLETED,
|
||||
KEY_THEME,
|
||||
KEY_NODE_SORT,
|
||||
KEY_INCLUDE_UNKNOWN,
|
||||
KEY_ONLY_ONLINE,
|
||||
KEY_ONLY_DIRECT,
|
||||
KEY_SHOW_IGNORED,
|
||||
),
|
||||
),
|
||||
),
|
||||
scope = scope,
|
||||
produceFile = { appContext.preferencesDataStoreFile(USER_PREFERENCES_NAME) },
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLocalConfigDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<LocalConfig> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
fileSystem = FileSystem.SYSTEM,
|
||||
serializer = LocalConfigSerializer,
|
||||
producePath = { appContext.dataStoreFile("local_config.pb").toOkioPath() },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { LocalConfig() }),
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideModuleConfigDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<LocalModuleConfig> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
fileSystem = FileSystem.SYSTEM,
|
||||
serializer = ModuleConfigSerializer,
|
||||
producePath = { appContext.dataStoreFile("module_config.pb").toOkioPath() },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { LocalModuleConfig() }),
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideChannelSetDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<ChannelSet> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
fileSystem = FileSystem.SYSTEM,
|
||||
serializer = ChannelSetSerializer,
|
||||
producePath = { appContext.dataStoreFile("channel_set.pb").toOkioPath() },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { ChannelSet() }),
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLocalStatsDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<LocalStats> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
fileSystem = FileSystem.SYSTEM,
|
||||
serializer = LocalStatsSerializer,
|
||||
producePath = { appContext.dataStoreFile("local_stats.pb").toOkioPath() },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { LocalStats() }),
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.data.datasource.NodeInfoReadDataSource
|
||||
import org.meshtastic.core.data.datasource.NodeInfoWriteDataSource
|
||||
import org.meshtastic.core.data.datasource.SwitchingNodeInfoReadDataSource
|
||||
import org.meshtastic.core.data.datasource.SwitchingNodeInfoWriteDataSource
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface NodeDataSourceModule {
|
||||
@Binds @Singleton
|
||||
fun bindNodeInfoReadDataSource(impl: SwitchingNodeInfoReadDataSource): NodeInfoReadDataSource
|
||||
|
||||
@Binds @Singleton
|
||||
fun bindNodeInfoWriteDataSource(impl: SwitchingNodeInfoWriteDataSource): NodeInfoWriteDataSource
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025-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.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.data.manager.CommandSenderImpl
|
||||
import org.meshtastic.core.data.manager.FromRadioPacketHandlerImpl
|
||||
import org.meshtastic.core.data.manager.HistoryManagerImpl
|
||||
import org.meshtastic.core.data.manager.MeshActionHandlerImpl
|
||||
import org.meshtastic.core.data.manager.MeshConfigFlowManagerImpl
|
||||
import org.meshtastic.core.data.manager.MeshConfigHandlerImpl
|
||||
import org.meshtastic.core.data.manager.MeshConnectionManagerImpl
|
||||
import org.meshtastic.core.data.manager.MeshDataHandlerImpl
|
||||
import org.meshtastic.core.data.manager.MeshMessageProcessorImpl
|
||||
import org.meshtastic.core.data.manager.MeshRouterImpl
|
||||
import org.meshtastic.core.data.manager.MessageFilterImpl
|
||||
import org.meshtastic.core.data.manager.MqttManagerImpl
|
||||
import org.meshtastic.core.data.manager.NeighborInfoHandlerImpl
|
||||
import org.meshtastic.core.data.manager.NodeManagerImpl
|
||||
import org.meshtastic.core.data.manager.PacketHandlerImpl
|
||||
import org.meshtastic.core.data.manager.TracerouteHandlerImpl
|
||||
import org.meshtastic.core.data.repository.DeviceHardwareRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.MeshLogRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.NodeRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.PacketRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepositoryImpl
|
||||
import org.meshtastic.core.model.util.MeshDataMapper
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.DeviceHardwareRepository
|
||||
import org.meshtastic.core.repository.FromRadioPacketHandler
|
||||
import org.meshtastic.core.repository.HistoryManager
|
||||
import org.meshtastic.core.repository.MeshActionHandler
|
||||
import org.meshtastic.core.repository.MeshConfigFlowManager
|
||||
import org.meshtastic.core.repository.MeshConfigHandler
|
||||
import org.meshtastic.core.repository.MeshConnectionManager
|
||||
import org.meshtastic.core.repository.MeshDataHandler
|
||||
import org.meshtastic.core.repository.MeshLogRepository
|
||||
import org.meshtastic.core.repository.MeshMessageProcessor
|
||||
import org.meshtastic.core.repository.MeshRouter
|
||||
import org.meshtastic.core.repository.MessageFilter
|
||||
import org.meshtastic.core.repository.MqttManager
|
||||
import org.meshtastic.core.repository.NeighborInfoHandler
|
||||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.TracerouteHandler
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class RepositoryModule {
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindNodeRepository(nodeRepositoryImpl: NodeRepositoryImpl): NodeRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindRadioConfigRepository(radioConfigRepositoryImpl: RadioConfigRepositoryImpl): RadioConfigRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindDeviceHardwareRepository(
|
||||
deviceHardwareRepositoryImpl: DeviceHardwareRepositoryImpl,
|
||||
): DeviceHardwareRepository
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindPacketRepository(packetRepositoryImpl: PacketRepositoryImpl): PacketRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshLogRepository(meshLogRepositoryImpl: MeshLogRepositoryImpl): MeshLogRepository
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindNodeManager(nodeManagerImpl: NodeManagerImpl): NodeManager
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindCommandSender(commandSenderImpl: CommandSenderImpl): CommandSender
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindHistoryManager(historyManagerImpl: HistoryManagerImpl): HistoryManager
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindTracerouteHandler(tracerouteHandlerImpl: TracerouteHandlerImpl): TracerouteHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindNeighborInfoHandler(neighborInfoHandlerImpl: NeighborInfoHandlerImpl): NeighborInfoHandler
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindMqttManager(mqttManagerImpl: MqttManagerImpl): MqttManager
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindPacketHandler(packetHandlerImpl: PacketHandlerImpl): PacketHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshConnectionManager(meshConnectionManagerImpl: MeshConnectionManagerImpl): MeshConnectionManager
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindMeshDataHandler(meshDataHandlerImpl: MeshDataHandlerImpl): MeshDataHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshActionHandler(meshActionHandlerImpl: MeshActionHandlerImpl): MeshActionHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshMessageProcessor(meshMessageProcessorImpl: MeshMessageProcessorImpl): MeshMessageProcessor
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindMeshRouter(meshRouterImpl: MeshRouterImpl): MeshRouter
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindFromRadioPacketHandler(
|
||||
fromRadioPacketHandlerImpl: FromRadioPacketHandlerImpl,
|
||||
): FromRadioPacketHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshConfigHandler(meshConfigHandlerImpl: MeshConfigHandlerImpl): MeshConfigHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshConfigFlowManager(meshConfigFlowManagerImpl: MeshConfigFlowManagerImpl): MeshConfigFlowManager
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindMessageFilter(messageFilterImpl: MessageFilterImpl): MessageFilter
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMeshDataMapper(nodeManager: NodeManager): MeshDataMapper = MeshDataMapper(nodeManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025-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.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.HomoglyphPrefs
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.usecase.SendMessageUseCase
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object UseCaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendMessageUseCase(
|
||||
nodeRepository: NodeRepository,
|
||||
packetRepository: PacketRepository,
|
||||
radioController: RadioController,
|
||||
homoglyphEncodingPrefs: HomoglyphPrefs,
|
||||
messageQueue: MessageQueue,
|
||||
): SendMessageUseCase =
|
||||
SendMessageUseCase(nodeRepository, packetRepository, radioController, homoglyphEncodingPrefs, messageQueue)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue