mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-31 20:21:36 -04:00
refactor(deps): inject CoroutineDispatchers (#4170)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
@@ -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 com.geeksville.mesh
|
||||
|
||||
import android.content.Context
|
||||
@@ -31,6 +30,7 @@ import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.startService
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.service.IMeshService
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import javax.inject.Inject
|
||||
@@ -75,10 +75,12 @@ constructor(
|
||||
super.onStart(owner)
|
||||
Logger.d { "Lifecycle: ON_START" }
|
||||
|
||||
try {
|
||||
bindMeshService()
|
||||
} catch (ex: BindFailedException) {
|
||||
Logger.e { "Bind of MeshService failed: ${ex.message}" }
|
||||
owner.lifecycleScope.launch {
|
||||
try {
|
||||
bindMeshService()
|
||||
} catch (ex: BindFailedException) {
|
||||
Logger.e { "Bind of MeshService failed: ${ex.message}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +95,7 @@ constructor(
|
||||
// endregion
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
private fun bindMeshService() {
|
||||
private suspend fun bindMeshService() {
|
||||
Logger.d { "Binding to mesh service!" }
|
||||
try {
|
||||
MeshService.startService(context)
|
||||
|
||||
@@ -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 com.geeksville.mesh.android
|
||||
|
||||
import android.content.ComponentName
|
||||
@@ -25,6 +24,7 @@ import android.os.IBinder
|
||||
import android.os.IInterface
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import kotlinx.coroutines.delay
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
@@ -64,7 +64,7 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
|
||||
}
|
||||
}
|
||||
|
||||
fun connect(c: Context, intent: Intent, flags: Int) {
|
||||
suspend fun connect(c: Context, intent: Intent, flags: Int) {
|
||||
context = c
|
||||
if (isClosed) {
|
||||
isClosed = false
|
||||
@@ -73,7 +73,7 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
|
||||
// Try
|
||||
// a short sleep to see if that helps
|
||||
Logger.e { "Needed to use the second bind attempt hack" }
|
||||
Thread.sleep(500) // was 200ms, but received an autobug from a Galaxy Note4, android 6.0.1
|
||||
delay(500) // was 200ms, but received an autobug from a Galaxy Note4, android 6.0.1
|
||||
if (!c.bindService(intent, connection, flags)) {
|
||||
throw BindFailedException()
|
||||
}
|
||||
|
||||
@@ -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 com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.app.Application
|
||||
@@ -31,7 +30,6 @@ import com.geeksville.mesh.repository.network.NetworkRepository
|
||||
import com.geeksville.mesh.util.ignoreException
|
||||
import com.geeksville.mesh.util.toRemoteExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
@@ -99,7 +97,7 @@ constructor(
|
||||
val mockInterfaceAddress: String by lazy { toInterfaceAddress(InterfaceId.MOCK, "") }
|
||||
|
||||
/** We recreate this scope each time we stop an interface */
|
||||
var serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
var serviceScope = CoroutineScope(dispatchers.io + SupervisorJob())
|
||||
|
||||
private var radioIf: IRadioInterface = NopInterface("")
|
||||
|
||||
@@ -292,7 +290,7 @@ constructor(
|
||||
|
||||
// cancel any old jobs and get ready for the new ones
|
||||
serviceScope.cancel("stopping interface")
|
||||
serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
serviceScope = CoroutineScope(dispatchers.io + SupervisorJob())
|
||||
|
||||
if (logSends) {
|
||||
sentPacketsLog.close()
|
||||
|
||||
@@ -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 com.geeksville.mesh.repository.radio
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
@@ -23,9 +22,9 @@ import com.geeksville.mesh.repository.network.NetworkRepository
|
||||
import com.geeksville.mesh.util.Exceptions
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.IOException
|
||||
@@ -34,8 +33,13 @@ import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import java.net.SocketTimeoutException
|
||||
|
||||
class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @Assisted private val address: String) :
|
||||
StreamInterface(service) {
|
||||
class TCPInterface
|
||||
@AssistedInject
|
||||
constructor(
|
||||
service: RadioInterfaceService,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
@Assisted private val address: String,
|
||||
) : StreamInterface(service) {
|
||||
|
||||
companion object {
|
||||
const val MAX_RETRIES_ALLOWED = Int.MAX_VALUE
|
||||
@@ -143,7 +147,7 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
|
||||
}
|
||||
|
||||
// Create a socket to make the connection with the server
|
||||
private suspend fun startConnect() = withContext(Dispatchers.IO) {
|
||||
private suspend fun startConnect() = withContext(dispatchers.io) {
|
||||
val attemptStart = System.currentTimeMillis()
|
||||
Logger.i { "[$address] TCP connection attempt starting..." }
|
||||
|
||||
|
||||
@@ -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 com.geeksville.mesh.ui.contact
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -30,7 +29,6 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
@@ -163,8 +161,8 @@ constructor(
|
||||
longName = longName,
|
||||
lastMessageTime = getShortDate(data.time),
|
||||
lastMessageText = if (fromLocal) data.text else "$shortName: ${data.text}",
|
||||
unreadCount = runBlocking(Dispatchers.IO) { packetRepository.getUnreadCount(contactKey) },
|
||||
messageCount = runBlocking(Dispatchers.IO) { packetRepository.getMessageCount(contactKey) },
|
||||
unreadCount = packetRepository.getUnreadCount(contactKey),
|
||||
messageCount = packetRepository.getMessageCount(contactKey),
|
||||
isMuted = settings[contactKey]?.isMuted == true,
|
||||
isUnmessageable = user.isUnmessagable,
|
||||
nodeColors =
|
||||
|
||||
@@ -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,27 +14,31 @@
|
||||
* 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 dagger.Lazy
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.database.dao.DeviceHardwareDao
|
||||
import org.meshtastic.core.database.entity.DeviceHardwareEntity
|
||||
import org.meshtastic.core.database.entity.asEntity
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import javax.inject.Inject
|
||||
|
||||
class DeviceHardwareLocalDataSource @Inject constructor(private val deviceHardwareDaoLazy: Lazy<DeviceHardwareDao>) {
|
||||
class DeviceHardwareLocalDataSource
|
||||
@Inject
|
||||
constructor(
|
||||
private val deviceHardwareDaoLazy: Lazy<DeviceHardwareDao>,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
private val deviceHardwareDao by lazy { deviceHardwareDaoLazy.get() }
|
||||
|
||||
suspend fun insertAllDeviceHardware(deviceHardware: List<NetworkDeviceHardware>) = withContext(Dispatchers.IO) {
|
||||
suspend fun insertAllDeviceHardware(deviceHardware: List<NetworkDeviceHardware>) = withContext(dispatchers.io) {
|
||||
deviceHardware.forEach { deviceHardware -> deviceHardwareDao.insert(deviceHardware.asEntity()) }
|
||||
}
|
||||
|
||||
suspend fun deleteAllDeviceHardware() = withContext(Dispatchers.IO) { deviceHardwareDao.deleteAll() }
|
||||
suspend fun deleteAllDeviceHardware() = withContext(dispatchers.io) { deviceHardwareDao.deleteAll() }
|
||||
|
||||
suspend fun getByHwModel(hwModel: Int): DeviceHardwareEntity? =
|
||||
withContext(Dispatchers.IO) { deviceHardwareDao.getByHwModel(hwModel) }
|
||||
withContext(dispatchers.io) { deviceHardwareDao.getByHwModel(hwModel) }
|
||||
}
|
||||
|
||||
@@ -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,36 +14,40 @@
|
||||
* 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 dagger.Lazy
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.database.dao.FirmwareReleaseDao
|
||||
import org.meshtastic.core.database.entity.FirmwareReleaseEntity
|
||||
import org.meshtastic.core.database.entity.FirmwareReleaseType
|
||||
import org.meshtastic.core.database.entity.asDeviceVersion
|
||||
import org.meshtastic.core.database.entity.asEntity
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.NetworkFirmwareRelease
|
||||
import javax.inject.Inject
|
||||
|
||||
class FirmwareReleaseLocalDataSource @Inject constructor(private val firmwareReleaseDaoLazy: Lazy<FirmwareReleaseDao>) {
|
||||
class FirmwareReleaseLocalDataSource
|
||||
@Inject
|
||||
constructor(
|
||||
private val firmwareReleaseDaoLazy: Lazy<FirmwareReleaseDao>,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
private val firmwareReleaseDao by lazy { firmwareReleaseDaoLazy.get() }
|
||||
|
||||
suspend fun insertFirmwareReleases(
|
||||
firmwareReleases: List<NetworkFirmwareRelease>,
|
||||
releaseType: FirmwareReleaseType,
|
||||
) = withContext(Dispatchers.IO) {
|
||||
) = withContext(dispatchers.io) {
|
||||
firmwareReleases.forEach { firmwareRelease ->
|
||||
firmwareReleaseDao.insert(firmwareRelease.asEntity(releaseType))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteAllFirmwareReleases() = withContext(Dispatchers.IO) { firmwareReleaseDao.deleteAll() }
|
||||
suspend fun deleteAllFirmwareReleases() = withContext(dispatchers.io) { firmwareReleaseDao.deleteAll() }
|
||||
|
||||
suspend fun getLatestRelease(releaseType: FirmwareReleaseType): FirmwareReleaseEntity? =
|
||||
withContext(Dispatchers.IO) {
|
||||
withContext(dispatchers.io) {
|
||||
val releases = firmwareReleaseDao.getReleasesByType(releaseType)
|
||||
if (releases.isEmpty()) {
|
||||
return@withContext null
|
||||
|
||||
@@ -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,17 +14,16 @@
|
||||
* 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.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.data.datasource.BootloaderOtaQuirksJsonDataSource
|
||||
import org.meshtastic.core.data.datasource.DeviceHardwareJsonDataSource
|
||||
import org.meshtastic.core.data.datasource.DeviceHardwareLocalDataSource
|
||||
import org.meshtastic.core.database.entity.DeviceHardwareEntity
|
||||
import org.meshtastic.core.database.entity.asExternalModel
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.BootloaderOtaQuirk
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.network.DeviceHardwareRemoteDataSource
|
||||
@@ -41,6 +40,7 @@ constructor(
|
||||
private val localDataSource: DeviceHardwareLocalDataSource,
|
||||
private val jsonDataSource: DeviceHardwareJsonDataSource,
|
||||
private val bootloaderOtaQuirksJsonDataSource: BootloaderOtaQuirksJsonDataSource,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
|
||||
/**
|
||||
@@ -58,7 +58,7 @@ constructor(
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
suspend fun getDeviceHardwareByModel(hwModel: Int, forceRefresh: Boolean = false): Result<DeviceHardware?> =
|
||||
withContext(Dispatchers.IO) {
|
||||
withContext(dispatchers.io) {
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=$hwModel, forceRefresh=$forceRefresh)"
|
||||
}
|
||||
|
||||
@@ -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 android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
@@ -28,13 +27,13 @@ import androidx.core.location.LocationManagerCompat
|
||||
import androidx.core.location.LocationRequestCompat
|
||||
import androidx.core.location.altitude.AltitudeConverterCompat
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
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 javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -45,6 +44,7 @@ constructor(
|
||||
private val context: Application,
|
||||
private val locationManager: dagger.Lazy<LocationManager>,
|
||||
private val analytics: PlatformAnalytics,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
|
||||
/** Status of whether the app is actively subscribed to location changes. */
|
||||
@@ -97,7 +97,7 @@ constructor(
|
||||
this@requestLocationUpdates,
|
||||
provider,
|
||||
locationRequest,
|
||||
Dispatchers.IO.asExecutor(),
|
||||
dispatchers.io.asExecutor(),
|
||||
locationListener,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ configure<LibraryExtension> {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.di)
|
||||
implementation(projects.core.model)
|
||||
implementation(projects.core.proto)
|
||||
implementation(projects.core.strings)
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -33,6 +32,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import javax.inject.Inject
|
||||
@@ -40,9 +40,9 @@ import javax.inject.Singleton
|
||||
|
||||
/** Manages per-device Room database instances for node data, with LRU eviction. */
|
||||
@Singleton
|
||||
class DatabaseManager @Inject constructor(private val app: Application) {
|
||||
class DatabaseManager @Inject constructor(private val app: Application, private val dispatchers: CoroutineDispatchers) {
|
||||
val prefs: SharedPreferences = app.getSharedPreferences("db-manager-prefs", Context.MODE_PRIVATE)
|
||||
private val managerScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
private val managerScope = CoroutineScope(SupervisorJob() + dispatchers.default)
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
@@ -72,25 +72,27 @@ class DatabaseManager @Inject constructor(private val app: Application) {
|
||||
private val dbCache = mutableMapOf<String, MeshtasticDatabase>() // key = dbName
|
||||
|
||||
/** Initialize the active database for [address]. */
|
||||
suspend fun init(address: String?) = switchActiveDatabase(address)
|
||||
suspend fun init(address: String?) {
|
||||
switchActiveDatabase(address)
|
||||
}
|
||||
|
||||
/** Switch active database to the one associated with [address]. Serialized via mutex. */
|
||||
suspend fun switchActiveDatabase(address: String?) = mutex.withLock {
|
||||
val dbName = buildDbName(address)
|
||||
|
||||
// Remember the previously active DB name (if any) so we can record its last-used time as well.
|
||||
// Remember the previously active DB name (any) so we can record its last-used time as well.
|
||||
val previousDbName = _currentDb.value?.openHelper?.databaseName
|
||||
|
||||
// Fast path: no-op if already on this address
|
||||
if (_currentAddress.value == address && _currentDb.value != null) {
|
||||
markLastUsed(dbName)
|
||||
return
|
||||
return@withLock
|
||||
}
|
||||
|
||||
// Build/open Room DB off the main thread
|
||||
val db =
|
||||
dbCache[dbName]
|
||||
?: withContext(Dispatchers.IO) { buildRoomDb(app, dbName) }.also { dbCache[dbName] = it }
|
||||
?: withContext(dispatchers.io) { buildRoomDb(app, dbName) }.also { dbCache[dbName] = it }
|
||||
|
||||
_currentDb.value = db
|
||||
_currentAddress.value = address
|
||||
@@ -100,10 +102,10 @@ class DatabaseManager @Inject constructor(private val app: Application) {
|
||||
previousDbName?.let { markLastUsed(it) }
|
||||
|
||||
// Defer LRU eviction so switch is not blocked by filesystem work
|
||||
managerScope.launch(Dispatchers.IO) { enforceCacheLimit(activeDbName = dbName) }
|
||||
managerScope.launch(dispatchers.io) { enforceCacheLimit(activeDbName = dbName) }
|
||||
|
||||
// One-time cleanup: remove legacy DB if present and not active
|
||||
managerScope.launch(Dispatchers.IO) { cleanupLegacyDbIfNeeded(activeDbName = dbName) }
|
||||
managerScope.launch(dispatchers.io) { cleanupLegacyDbIfNeeded(activeDbName = dbName) }
|
||||
|
||||
Logger.i { "Switched active DB to ${anonymizeDbName(dbName)} for address ${anonymizeAddress(address)}" }
|
||||
}
|
||||
@@ -144,7 +146,7 @@ class DatabaseManager @Inject constructor(private val app: Application) {
|
||||
anonymizeDbName(it)
|
||||
}}"
|
||||
}
|
||||
if (deviceDbs.size <= limit) return
|
||||
if (deviceDbs.size <= limit) return@withLock
|
||||
val usageSnapshot = deviceDbs.associateWith { lastUsed(it) }
|
||||
Logger.d {
|
||||
"LRU lastUsed(ms): ${usageSnapshot.entries.joinToString(", ") { (name, ts) ->
|
||||
@@ -173,16 +175,16 @@ class DatabaseManager @Inject constructor(private val app: Application) {
|
||||
_cacheLimit.value = clamped
|
||||
// Enforce asynchronously with current active DB protected
|
||||
val active = _currentDb.value?.openHelper?.databaseName ?: defaultDbName()
|
||||
managerScope.launch(Dispatchers.IO) { enforceCacheLimit(activeDbName = active) }
|
||||
managerScope.launch(dispatchers.io) { enforceCacheLimit(activeDbName = active) }
|
||||
}
|
||||
|
||||
private suspend fun cleanupLegacyDbIfNeeded(activeDbName: String) = mutex.withLock {
|
||||
if (prefs.getBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, false)) return
|
||||
if (prefs.getBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, false)) return@withLock
|
||||
val legacy = DatabaseConstants.LEGACY_DB_NAME
|
||||
if (legacy == activeDbName) {
|
||||
// Never delete the active DB; mark as cleaned to avoid repeated checks
|
||||
prefs.edit().putBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, true).apply()
|
||||
return
|
||||
return@withLock
|
||||
}
|
||||
val legacyFile = getDbFile(app, legacy)
|
||||
if (legacyFile != null) {
|
||||
|
||||
@@ -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
|
||||
@@ -48,6 +48,7 @@ configure<LibraryExtension> {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.di)
|
||||
implementation(projects.core.model)
|
||||
|
||||
implementation(libs.coil.network.core)
|
||||
|
||||
@@ -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,16 +14,20 @@
|
||||
* 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.network
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.network.service.ApiService
|
||||
import javax.inject.Inject
|
||||
|
||||
class DeviceHardwareRemoteDataSource @Inject constructor(private val apiService: ApiService) {
|
||||
class DeviceHardwareRemoteDataSource
|
||||
@Inject
|
||||
constructor(
|
||||
private val apiService: ApiService,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
suspend fun getAllDeviceHardware(): List<NetworkDeviceHardware> =
|
||||
withContext(Dispatchers.IO) { apiService.getDeviceHardware() }
|
||||
withContext(dispatchers.io) { apiService.getDeviceHardware() }
|
||||
}
|
||||
|
||||
@@ -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,16 +14,20 @@
|
||||
* 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.network
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.NetworkFirmwareReleases
|
||||
import org.meshtastic.core.network.service.ApiService
|
||||
import javax.inject.Inject
|
||||
|
||||
class FirmwareReleaseRemoteDataSource @Inject constructor(private val apiService: ApiService) {
|
||||
class FirmwareReleaseRemoteDataSource
|
||||
@Inject
|
||||
constructor(
|
||||
private val apiService: ApiService,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
suspend fun getFirmwareReleases(): NetworkFirmwareReleases =
|
||||
withContext(Dispatchers.IO) { apiService.getFirmwareReleases() }
|
||||
withContext(dispatchers.io) { apiService.getFirmwareReleases() }
|
||||
}
|
||||
|
||||
@@ -1086,5 +1086,4 @@
|
||||
<string name="compass_uncertainty">Estimated area: \u00b1%1$s (\u00b1%2$s)</string>
|
||||
<string name="compass_uncertainty_unknown">Estimated area: unknown accuracy</string>
|
||||
<string name="mark_as_read">Mark as read</string>
|
||||
<string name="now">now</string>
|
||||
</resources>
|
||||
|
||||
@@ -17,24 +17,16 @@
|
||||
package org.meshtastic.core.ui.util
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import com.meshtastic.core.strings.getString
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.now
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun formatAgo(lastSeenUnix: Int, currentTimeMillis: Long = System.currentTimeMillis()): String {
|
||||
val timeInMillis = lastSeenUnix * 1000L
|
||||
val diff = currentTimeMillis - timeInMillis
|
||||
|
||||
return if (diff < 60_000L) {
|
||||
getString(Res.string.now)
|
||||
} else {
|
||||
DateUtils.getRelativeTimeSpanString(
|
||||
timeInMillis,
|
||||
currentTimeMillis,
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE,
|
||||
)
|
||||
.toString()
|
||||
}
|
||||
return DateUtils.getRelativeTimeSpanString(
|
||||
timeInMillis,
|
||||
currentTimeMillis,
|
||||
DateUtils.SECOND_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE,
|
||||
)
|
||||
.toString()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user