mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(logging): Replace Timber with Kermit for multiplatform logging (#4083)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
a927481e4d
commit
0776e029f3
92 changed files with 727 additions and 957 deletions
|
|
@ -46,7 +46,7 @@ dependencies {
|
|||
implementation(libs.androidx.compose.runtime)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
implementation(libs.androidx.navigation.runtime)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kermit)
|
||||
|
||||
googleApi(libs.dd.sdk.android.compose)
|
||||
googleApi(libs.dd.sdk.android.logs)
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@ package org.meshtastic.core.analytics.platform
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavHostController
|
||||
import co.touchlab.kermit.Logger
|
||||
import co.touchlab.kermit.Severity
|
||||
import org.meshtastic.core.analytics.BuildConfig
|
||||
import org.meshtastic.core.analytics.DataPair
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
|
@ -34,16 +35,17 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics {
|
|||
// In debug builds we attach a DebugTree for convenient local logging, but
|
||||
// release builds rely on system logging only.
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
Timber.i("F-Droid platform no-op analytics initialized (DebugTree planted).")
|
||||
Logger.setMinSeverity(Severity.Debug)
|
||||
Logger.i { "F-Droid platform no-op analytics initialized (Debug mode }." }
|
||||
} else {
|
||||
Timber.i("F-Droid platform no-op analytics initialized.")
|
||||
Logger.setMinSeverity(Severity.Info)
|
||||
Logger.i { "F-Droid platform no-op analytics initialized." }
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDeviceAttributes(firmwareVersion: String, model: String) {
|
||||
// No-op for F-Droid
|
||||
Timber.d("Set device attributes called: firmwareVersion=$firmwareVersion, deviceHardware=$model")
|
||||
Logger.d { "Set device attributes called: firmwareVersion=$firmwareVersion, deviceHardware=$model" }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -51,7 +53,7 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics {
|
|||
// No-op for F-Droid, but we can log navigation if needed for debugging
|
||||
if (BuildConfig.DEBUG) {
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
Timber.d("Navigation changed to: ${destination.route}")
|
||||
Logger.d { "Navigation changed to: ${destination.route}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +62,6 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics {
|
|||
get() = false
|
||||
|
||||
override fun track(event: String, vararg properties: DataPair) {
|
||||
Timber.d("Track called: event=$event, properties=${properties.toList()}")
|
||||
Logger.d { "Track called: event=$event, properties=${properties.toList()}" }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavHostController
|
||||
import co.touchlab.kermit.LogWriter
|
||||
import co.touchlab.kermit.Severity
|
||||
import com.datadog.android.Datadog
|
||||
import com.datadog.android.DatadogSite
|
||||
import com.datadog.android.compose.ExperimentalTrackingApi
|
||||
|
|
@ -43,7 +45,6 @@ import com.datadog.android.rum.tracking.AcceptAllNavDestinations
|
|||
import com.datadog.android.sessionreplay.SessionReplay
|
||||
import com.datadog.android.sessionreplay.SessionReplayConfiguration
|
||||
import com.datadog.android.sessionreplay.compose.ComposeExtensionSupport
|
||||
import com.datadog.android.timber.DatadogTree
|
||||
import com.datadog.android.trace.Trace
|
||||
import com.datadog.android.trace.TraceConfiguration
|
||||
import com.datadog.android.trace.opentelemetry.DatadogOpenTelemetry
|
||||
|
|
@ -61,10 +62,8 @@ import kotlinx.coroutines.flow.onEach
|
|||
import org.meshtastic.core.analytics.BuildConfig
|
||||
import org.meshtastic.core.analytics.DataPair
|
||||
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
|
||||
import timber.log.Timber
|
||||
import timber.log.Timber.DebugTree
|
||||
import timber.log.Timber.Tree
|
||||
import javax.inject.Inject
|
||||
import co.touchlab.kermit.Logger as KermitLogger
|
||||
|
||||
/**
|
||||
* Google Play Services specific implementation of [PlatformAnalytics]. This helper initializes and manages Firebase and
|
||||
|
|
@ -102,14 +101,15 @@ constructor(
|
|||
.setBundleWithTraceEnabled(true)
|
||||
.setBundleWithRumEnabled(true)
|
||||
.build()
|
||||
buildList {
|
||||
add(DatadogTree(datadogLogger))
|
||||
add(CrashlyticsTree())
|
||||
val writers = buildList {
|
||||
add(DatadogLogWriter(datadogLogger))
|
||||
add(CrashlyticsLogWriter())
|
||||
if (BuildConfig.DEBUG) {
|
||||
add(DebugTree())
|
||||
add(co.touchlab.kermit.LogcatWriter())
|
||||
}
|
||||
}
|
||||
.forEach(Timber::plant)
|
||||
KermitLogger.setLogWriters(writers)
|
||||
KermitLogger.setMinSeverity(if (BuildConfig.DEBUG) Severity.Debug else Severity.Info)
|
||||
|
||||
// Initial consent state
|
||||
updateAnalyticsConsent(analyticsPrefs.analyticsAllowed)
|
||||
|
|
@ -177,10 +177,10 @@ constructor(
|
|||
*/
|
||||
fun updateAnalyticsConsent(allowed: Boolean) {
|
||||
if (!isPlatformServicesAvailable || isInTestLab) {
|
||||
Timber.i("Analytics not available or in test lab, consent update skipped.")
|
||||
KermitLogger.i { "Analytics not available or in test lab, consent update skipped." }
|
||||
return
|
||||
}
|
||||
Timber.i(if (allowed) "Analytics enabled" else "Analytics disabled")
|
||||
KermitLogger.i { if (allowed) "Analytics enabled" else "Analytics disabled" }
|
||||
|
||||
Datadog.setTrackingConsent(if (allowed) TrackingConsent.GRANTED else TrackingConsent.NOT_GRANTED)
|
||||
Firebase.crashlytics.isCrashlyticsCollectionEnabled = allowed
|
||||
|
|
@ -221,30 +221,45 @@ constructor(
|
|||
override val isPlatformServicesAvailable: Boolean
|
||||
get() = isGooglePlayAvailable && isDatadogAvailable
|
||||
|
||||
private class CrashlyticsTree : Tree() {
|
||||
private class CrashlyticsLogWriter : LogWriter() {
|
||||
companion object {
|
||||
private const val KEY_PRIORITY = "priority"
|
||||
private const val KEY_TAG = "tag"
|
||||
private const val KEY_MESSAGE = "message"
|
||||
}
|
||||
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) {
|
||||
if (!Firebase.crashlytics.isCrashlyticsCollectionEnabled) return
|
||||
|
||||
Firebase.crashlytics.setCustomKeys {
|
||||
key(KEY_PRIORITY, priority)
|
||||
key(KEY_TAG, tag ?: "No Tag")
|
||||
key(KEY_PRIORITY, severity.ordinal)
|
||||
key(KEY_TAG, tag)
|
||||
key(KEY_MESSAGE, message)
|
||||
}
|
||||
|
||||
if (t == null) {
|
||||
if (throwable == null) {
|
||||
Firebase.crashlytics.recordException(Exception(message))
|
||||
} else {
|
||||
Firebase.crashlytics.recordException(t)
|
||||
Firebase.crashlytics.recordException(throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DatadogLogWriter(private val datadogLogger: Logger) : LogWriter() {
|
||||
override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) {
|
||||
val datadogPriority =
|
||||
when (severity) {
|
||||
Severity.Verbose -> android.util.Log.VERBOSE
|
||||
Severity.Debug -> android.util.Log.DEBUG
|
||||
Severity.Info -> android.util.Log.INFO
|
||||
Severity.Warn -> android.util.Log.WARN
|
||||
Severity.Error -> android.util.Log.ERROR
|
||||
Severity.Assert -> android.util.Log.ASSERT
|
||||
}
|
||||
datadogLogger.log(datadogPriority, message, throwable, mapOf("tag" to tag))
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.extractSemanticVersion(): String {
|
||||
val regex = "^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?".toRegex()
|
||||
val matchResult = regex.find(this)
|
||||
|
|
@ -263,7 +278,7 @@ constructor(
|
|||
is String -> bundle.putString(it.name, it.value as String?) // Explicitly handle String
|
||||
else -> bundle.putString(it.name, it.value.toString()) // Fallback for other types
|
||||
}
|
||||
Timber.tag(TAG).d("Analytics: track $event (${it.name} : ${it.value})")
|
||||
KermitLogger.withTag(TAG).d { "Analytics: track $event (${it.name} : ${it.value})" }
|
||||
}
|
||||
Firebase.analytics.logEvent(event, bundle)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,5 +61,5 @@ dependencies {
|
|||
implementation(libs.androidx.core.location.altitude)
|
||||
implementation(libs.androidx.paging.common)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kermit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@
|
|||
<ID>MagicNumber:LocationRepository.kt$LocationRepository$30</ID>
|
||||
<ID>MagicNumber:LocationRepository.kt$LocationRepository$31</ID>
|
||||
<ID>MagicNumber:PacketRepository.kt$PacketRepository$500</ID>
|
||||
<ID>MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=${quirk.requiresBootloaderUpgradeForOta}, infoUrl=${quirk.infoUrl}"</ID>
|
||||
<ID>MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: cache ${if (staleEntity == null) "empty" else "incomplete"} for hwModel=$hwModel, falling back to bundled JSON asset"</ID>
|
||||
<ID>MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=$hwModel"</ID>
|
||||
<ID>MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: lookup after JSON load for hwModel=$hwModel ${if (base != null) "succeeded" else "returned null"}"</ID>
|
||||
<ID>MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: lookup after remote fetch for hwModel=$hwModel ${if (fromDb != null) "succeeded" else "returned null"}"</ID>
|
||||
<ID>TooGenericExceptionCaught:LocationRepository.kt$LocationRepository$e: Exception</ID>
|
||||
<ID>TooManyFunctions:PacketRepository.kt$PacketRepository</ID>
|
||||
</CurrentIssues>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
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
|
||||
|
|
@ -26,10 +27,8 @@ import kotlinx.serialization.json.Json
|
|||
import org.meshtastic.core.data.model.CustomTileProviderConfig
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.prefs.map.MapTileProviderPrefs
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.plus
|
||||
|
||||
interface CustomTileProviderRepository {
|
||||
fun getCustomTileProviders(): Flow<List<CustomTileProviderConfig>>
|
||||
|
|
@ -88,7 +87,7 @@ constructor(
|
|||
try {
|
||||
customTileProvidersStateFlow.value = json.decodeFromString<List<CustomTileProviderConfig>>(jsonString)
|
||||
} catch (e: SerializationException) {
|
||||
Timber.e(e, "Error deserializing tile providers")
|
||||
Logger.e(e) { "Error deserializing tile providers" }
|
||||
customTileProvidersStateFlow.value = emptyList()
|
||||
}
|
||||
} else {
|
||||
|
|
@ -102,7 +101,7 @@ constructor(
|
|||
val jsonString = json.encodeToString(providers)
|
||||
mapTileProviderPrefs.customTileProviders = jsonString
|
||||
} catch (e: SerializationException) {
|
||||
Timber.e(e, "Error serializing tile providers")
|
||||
Logger.e(e) { "Error serializing tile providers" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
package org.meshtastic.core.data.datasource
|
||||
|
||||
import android.app.Application
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import org.meshtastic.core.model.BootloaderOtaQuirk
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class BootloaderOtaQuirksJsonDataSource @Inject constructor(private val application: Application) {
|
||||
|
|
@ -32,7 +32,7 @@ class BootloaderOtaQuirksJsonDataSource @Inject constructor(private val applicat
|
|||
val inputStream = application.assets.open("device_bootloader_ota_quirks.json")
|
||||
inputStream.use { Json.decodeFromStream<ListWrapper>(it).devices }
|
||||
}
|
||||
.onFailure { e -> Timber.w(e, "Failed to load device_bootloader_ota_quirks.json") }
|
||||
.onFailure { e -> Logger.w(e) { "Failed to load device_bootloader_ota_quirks.json" } }
|
||||
.getOrDefault(emptyList())
|
||||
|
||||
@Serializable private data class ListWrapper(val devices: List<BootloaderOtaQuirk> = emptyList())
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
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
|
||||
|
|
@ -27,7 +28,6 @@ import org.meshtastic.core.database.entity.asExternalModel
|
|||
import org.meshtastic.core.model.BootloaderOtaQuirk
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.network.DeviceHardwareRemoteDataSource
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -59,45 +59,40 @@ constructor(
|
|||
@Suppress("LongMethod")
|
||||
suspend fun getDeviceHardwareByModel(hwModel: Int, forceRefresh: Boolean = false): Result<DeviceHardware?> =
|
||||
withContext(Dispatchers.IO) {
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=%d, forceRefresh=%b)",
|
||||
hwModel,
|
||||
forceRefresh,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=$hwModel, forceRefresh=$forceRefresh)"
|
||||
}
|
||||
|
||||
val quirks = loadQuirks()
|
||||
|
||||
if (forceRefresh) {
|
||||
Timber.d("DeviceHardwareRepository: forceRefresh=true, clearing local device hardware cache")
|
||||
Logger.d { "DeviceHardwareRepository: forceRefresh=true, clearing local device hardware cache" }
|
||||
localDataSource.deleteAllDeviceHardware()
|
||||
} else {
|
||||
// 1. Attempt to retrieve from cache first
|
||||
val cachedEntity = localDataSource.getByHwModel(hwModel)
|
||||
if (cachedEntity != null && !cachedEntity.isStale()) {
|
||||
Timber.d("DeviceHardwareRepository: using fresh cached device hardware for hwModel=%d", hwModel)
|
||||
Logger.d { "DeviceHardwareRepository: using fresh cached device hardware for hwModel=$hwModel" }
|
||||
return@withContext Result.success(
|
||||
applyBootloaderQuirk(hwModel, cachedEntity.asExternalModel(), quirks),
|
||||
)
|
||||
}
|
||||
Timber.d("DeviceHardwareRepository: no fresh cache for hwModel=%d, attempting remote fetch", hwModel)
|
||||
Logger.d { "DeviceHardwareRepository: no fresh cache for hwModel=$hwModel, attempting remote fetch" }
|
||||
}
|
||||
|
||||
// 2. Fetch from remote API
|
||||
runCatching {
|
||||
Timber.d("DeviceHardwareRepository: fetching device hardware from remote API")
|
||||
Logger.d { "DeviceHardwareRepository: fetching device hardware from remote API" }
|
||||
val remoteHardware = remoteDataSource.getAllDeviceHardware()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: remote API returned %d device hardware entries",
|
||||
remoteHardware.size,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: remote API returned ${remoteHardware.size} device hardware entries"
|
||||
}
|
||||
|
||||
localDataSource.insertAllDeviceHardware(remoteHardware)
|
||||
val fromDb = localDataSource.getByHwModel(hwModel)?.asExternalModel()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: lookup after remote fetch for hwModel=%d %s",
|
||||
hwModel,
|
||||
if (fromDb != null) "succeeded" else "returned null",
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: lookup after remote fetch for hwModel=$hwModel ${if (fromDb != null) "succeeded" else "returned null"}"
|
||||
}
|
||||
fromDb
|
||||
}
|
||||
.onSuccess {
|
||||
|
|
@ -105,57 +100,48 @@ constructor(
|
|||
return@withContext Result.success(applyBootloaderQuirk(hwModel, it, quirks))
|
||||
}
|
||||
.onFailure { e ->
|
||||
Timber.w(
|
||||
e,
|
||||
"DeviceHardwareRepository: failed to fetch device hardware from server for hwModel=%d",
|
||||
hwModel,
|
||||
)
|
||||
Logger.w(e) {
|
||||
"DeviceHardwareRepository: failed to fetch device hardware from server for hwModel=$hwModel"
|
||||
}
|
||||
|
||||
// 3. Attempt to use stale cache as a fallback, but only if it looks complete.
|
||||
val staleEntity = localDataSource.getByHwModel(hwModel)
|
||||
if (staleEntity != null && !staleEntity.isIncomplete()) {
|
||||
Timber.d("DeviceHardwareRepository: using stale cached device hardware for hwModel=%d", hwModel)
|
||||
Logger.d { "DeviceHardwareRepository: using stale cached device hardware for hwModel=$hwModel" }
|
||||
return@withContext Result.success(
|
||||
applyBootloaderQuirk(hwModel, staleEntity.asExternalModel(), quirks),
|
||||
)
|
||||
}
|
||||
|
||||
// 4. Fallback to bundled JSON if cache is empty or incomplete
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: cache %s for hwModel=%d, falling back to bundled JSON asset",
|
||||
if (staleEntity == null) "empty" else "incomplete",
|
||||
hwModel,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: cache ${if (staleEntity == null) "empty" else "incomplete"} for hwModel=$hwModel, falling back to bundled JSON asset"
|
||||
}
|
||||
return@withContext loadFromBundledJson(hwModel, quirks)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadFromBundledJson(hwModel: Int, quirks: List<BootloaderOtaQuirk>): Result<DeviceHardware?> =
|
||||
runCatching {
|
||||
Timber.d("DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=%d", hwModel)
|
||||
Logger.d { "DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=$hwModel" }
|
||||
val jsonHardware = jsonDataSource.loadDeviceHardwareFromJsonAsset()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: bundled JSON returned %d device hardware entries",
|
||||
jsonHardware.size,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: bundled JSON returned ${jsonHardware.size} device hardware entries"
|
||||
}
|
||||
|
||||
localDataSource.insertAllDeviceHardware(jsonHardware)
|
||||
val base = localDataSource.getByHwModel(hwModel)?.asExternalModel()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: lookup after JSON load for hwModel=%d %s",
|
||||
hwModel,
|
||||
if (base != null) "succeeded" else "returned null",
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: lookup after JSON load for hwModel=$hwModel ${if (base != null) "succeeded" else "returned null"}"
|
||||
}
|
||||
|
||||
applyBootloaderQuirk(hwModel, base, quirks)
|
||||
}
|
||||
.also { result ->
|
||||
result.exceptionOrNull()?.let { e ->
|
||||
Timber.e(
|
||||
e,
|
||||
"DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=%d",
|
||||
hwModel,
|
||||
)
|
||||
Logger.e(e) {
|
||||
"DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=$hwModel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +160,7 @@ constructor(
|
|||
|
||||
private fun loadQuirks(): List<BootloaderOtaQuirk> {
|
||||
val quirks = bootloaderOtaQuirksJsonDataSource.loadBootloaderOtaQuirksFromJsonAsset()
|
||||
Timber.d("DeviceHardwareRepository: loaded %d bootloader quirks", quirks.size)
|
||||
Logger.d { "DeviceHardwareRepository: loaded ${quirks.size} bootloader quirks" }
|
||||
return quirks
|
||||
}
|
||||
|
||||
|
|
@ -186,17 +172,11 @@ constructor(
|
|||
if (base == null) return null
|
||||
|
||||
val quirk = quirks.firstOrNull { it.hwModel == hwModel }
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: applyBootloaderQuirk for hwModel=%d, quirk found=%b",
|
||||
hwModel,
|
||||
quirk != null,
|
||||
)
|
||||
Logger.d { "DeviceHardwareRepository: applyBootloaderQuirk for hwModel=$hwModel, quirk found=${quirk != null}" }
|
||||
return if (quirk != null) {
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=%b, infoUrl=%s",
|
||||
quirk.requiresBootloaderUpgradeForOta,
|
||||
quirk.infoUrl,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=${quirk.requiresBootloaderUpgradeForOta}, infoUrl=${quirk.infoUrl}"
|
||||
}
|
||||
base.copy(
|
||||
requiresBootloaderUpgradeForOta = quirk.requiresBootloaderUpgradeForOta,
|
||||
bootloaderInfoUrl = quirk.infoUrl,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.meshtastic.core.data.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.meshtastic.core.data.datasource.FirmwareReleaseJsonDataSource
|
||||
|
|
@ -26,7 +27,6 @@ import org.meshtastic.core.database.entity.FirmwareReleaseEntity
|
|||
import org.meshtastic.core.database.entity.FirmwareReleaseType
|
||||
import org.meshtastic.core.database.entity.asExternalModel
|
||||
import org.meshtastic.core.network.FirmwareReleaseRemoteDataSource
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -68,7 +68,7 @@ constructor(
|
|||
// This gives the UI something to show immediately.
|
||||
val cachedRelease = localDataSource.getLatestRelease(releaseType)
|
||||
cachedRelease?.let {
|
||||
Timber.d("Emitting cached firmware for $releaseType (isStale=${it.isStale()})")
|
||||
Logger.d { "Emitting cached firmware for $releaseType (isStale=${it.isStale()})" }
|
||||
emit(it.asExternalModel())
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ constructor(
|
|||
// The `distinctUntilChanged()` operator on the collector side will prevent
|
||||
// re-emitting the same data if the cache wasn't actually updated.
|
||||
val finalRelease = localDataSource.getLatestRelease(releaseType)
|
||||
Timber.d("Emitting final firmware for $releaseType from cache.")
|
||||
Logger.d { "Emitting final firmware for $releaseType from cache." }
|
||||
emit(finalRelease?.asExternalModel())
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ constructor(
|
|||
private suspend fun updateCacheFromSources() {
|
||||
val remoteFetchSuccess =
|
||||
runCatching {
|
||||
Timber.d("Fetching fresh firmware releases from remote API.")
|
||||
Logger.d { "Fetching fresh firmware releases from remote API." }
|
||||
val networkReleases = remoteDataSource.getFirmwareReleases()
|
||||
|
||||
// The API fetches all release types, so we cache them all at once.
|
||||
|
|
@ -109,13 +109,13 @@ constructor(
|
|||
|
||||
// If remote fetch failed, try the JSON fallback as a last resort.
|
||||
if (!remoteFetchSuccess) {
|
||||
Timber.w("Remote fetch failed, attempting to cache from bundled JSON.")
|
||||
Logger.w { "Remote fetch failed, attempting to cache from bundled JSON." }
|
||||
runCatching {
|
||||
val jsonReleases = jsonDataSource.loadFirmwareReleaseFromJsonAsset()
|
||||
localDataSource.insertFirmwareReleases(jsonReleases.releases.stable, FirmwareReleaseType.STABLE)
|
||||
localDataSource.insertFirmwareReleases(jsonReleases.releases.alpha, FirmwareReleaseType.ALPHA)
|
||||
}
|
||||
.onFailure { Timber.w("Failed to cache from JSON: ${it.message}") }
|
||||
.onFailure { Logger.w { "Failed to cache from JSON: ${it.message}" } }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import androidx.core.location.LocationListenerCompat
|
|||
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
|
||||
|
|
@ -34,7 +35,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ constructor(
|
|||
try {
|
||||
AltitudeConverterCompat.addMslAltitudeToLocation(context, location)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "addMslAltitudeToLocation() failed")
|
||||
Logger.e(e) { "addMslAltitudeToLocation() failed" }
|
||||
}
|
||||
}
|
||||
// info("New location: $location")
|
||||
|
|
@ -85,9 +85,9 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
Timber.i(
|
||||
"Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m",
|
||||
)
|
||||
Logger.i {
|
||||
"Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m"
|
||||
}
|
||||
_receivingLocationUpdates.value = true
|
||||
analytics.track("location_start") // Figure out how many users needed to use the phone GPS
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ constructor(
|
|||
}
|
||||
|
||||
awaitClose {
|
||||
Timber.i("Stopping location requests")
|
||||
Logger.i { "Stopping location requests" }
|
||||
_receivingLocationUpdates.value = false
|
||||
analytics.track("location_stop")
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ dependencies {
|
|||
|
||||
implementation(libs.androidx.room.paging)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kermit)
|
||||
|
||||
ksp(libs.androidx.room.compiler)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package org.meshtastic.core.database
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import kotlinx.serialization.json.Json
|
||||
|
|
@ -25,7 +26,6 @@ import org.meshtastic.core.model.DataPacket
|
|||
import org.meshtastic.proto.MeshProtos
|
||||
import org.meshtastic.proto.PaxcountProtos
|
||||
import org.meshtastic.proto.TelemetryProtos
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class Converters {
|
||||
|
|
@ -45,7 +45,7 @@ class Converters {
|
|||
fun bytesToFromRadio(bytes: ByteArray): MeshProtos.FromRadio = try {
|
||||
MeshProtos.FromRadio.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
Timber.e(ex, "bytesToFromRadio TypeConverter error")
|
||||
Logger.e(ex) { "bytesToFromRadio TypeConverter error" }
|
||||
MeshProtos.FromRadio.getDefaultInstance()
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ class Converters {
|
|||
fun bytesToUser(bytes: ByteArray): MeshProtos.User = try {
|
||||
MeshProtos.User.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
Timber.e(ex, "bytesToUser TypeConverter error")
|
||||
Logger.e(ex) { "bytesToUser TypeConverter error" }
|
||||
MeshProtos.User.getDefaultInstance()
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ class Converters {
|
|||
fun bytesToPosition(bytes: ByteArray): MeshProtos.Position = try {
|
||||
MeshProtos.Position.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
Timber.e(ex, "bytesToPosition TypeConverter error")
|
||||
Logger.e(ex) { "bytesToPosition TypeConverter error" }
|
||||
MeshProtos.Position.getDefaultInstance()
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ class Converters {
|
|||
fun bytesToTelemetry(bytes: ByteArray): TelemetryProtos.Telemetry = try {
|
||||
TelemetryProtos.Telemetry.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
Timber.e(ex, "bytesToTelemetry TypeConverter error")
|
||||
Logger.e(ex) { "bytesToTelemetry TypeConverter error" }
|
||||
TelemetryProtos.Telemetry.newBuilder().build() // Return an empty Telemetry object
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ class Converters {
|
|||
fun bytesToPaxcounter(bytes: ByteArray): PaxcountProtos.Paxcount = try {
|
||||
PaxcountProtos.Paxcount.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
Timber.e(ex, "bytesToPaxcounter TypeConverter error")
|
||||
Logger.e(ex) { "bytesToPaxcounter TypeConverter error" }
|
||||
PaxcountProtos.Paxcount.getDefaultInstance()
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ class Converters {
|
|||
fun bytesToMetadata(bytes: ByteArray): MeshProtos.DeviceMetadata = try {
|
||||
MeshProtos.DeviceMetadata.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
Timber.e(ex, "bytesToMetadata TypeConverter error")
|
||||
Logger.e(ex) { "bytesToMetadata TypeConverter error" }
|
||||
MeshProtos.DeviceMetadata.getDefaultInstance()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import android.app.Application
|
|||
import android.content.Context
|
||||
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
|
||||
|
|
@ -33,7 +34,6 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import javax.inject.Inject
|
||||
|
|
@ -106,7 +106,7 @@ class DatabaseManager @Inject constructor(private val app: Application) {
|
|||
// One-time cleanup: remove legacy DB if present and not active
|
||||
managerScope.launch(Dispatchers.IO) { cleanupLegacyDbIfNeeded(activeDbName = dbName) }
|
||||
|
||||
Timber.i("Switched active DB to ${anonymizeDbName(dbName)} for address ${anonymizeAddress(address)}")
|
||||
Logger.i { "Switched active DB to ${anonymizeDbName(dbName)} for address ${anonymizeAddress(address)}" }
|
||||
}
|
||||
|
||||
/** Execute [block] with the current DB instance. */
|
||||
|
|
@ -138,26 +138,28 @@ class DatabaseManager @Inject constructor(private val app: Application) {
|
|||
// Only enforce the limit over device-specific DBs; exclude legacy and default DBs
|
||||
val deviceDbs =
|
||||
all.filterNot { it == DatabaseConstants.LEGACY_DB_NAME || it == DatabaseConstants.DEFAULT_DB_NAME }
|
||||
Timber.d(
|
||||
"LRU check: limit=%d, active=%s, deviceDbs=%s",
|
||||
limit,
|
||||
anonymizeDbName(activeDbName),
|
||||
deviceDbs.joinToString(", ") { anonymizeDbName(it) },
|
||||
)
|
||||
Logger.d {
|
||||
"LRU check: limit=$limit, active=${anonymizeDbName(
|
||||
activeDbName,
|
||||
)}, deviceDbs=${deviceDbs.joinToString(", ") {
|
||||
anonymizeDbName(it)
|
||||
}}"
|
||||
}
|
||||
if (deviceDbs.size <= limit) return
|
||||
val usageSnapshot = deviceDbs.associateWith { lastUsed(it) }
|
||||
Timber.d(
|
||||
"LRU lastUsed(ms): %s",
|
||||
usageSnapshot.entries.joinToString(", ") { (name, ts) -> "${anonymizeDbName(name)}=$ts" },
|
||||
)
|
||||
Logger.d {
|
||||
"LRU lastUsed(ms): ${usageSnapshot.entries.joinToString(", ") { (name, ts) ->
|
||||
"${anonymizeDbName(name)}=$ts"
|
||||
}}"
|
||||
}
|
||||
val victims = selectEvictionVictims(deviceDbs, activeDbName, limit, usageSnapshot)
|
||||
Timber.i("LRU victims: %s", victims.joinToString(", ") { anonymizeDbName(it) })
|
||||
Logger.i { "LRU victims: ${victims.joinToString(", ") { anonymizeDbName(it) }}" }
|
||||
victims.forEach { name ->
|
||||
runCatching { dbCache.remove(name)?.close() }
|
||||
.onFailure { Timber.w(it, "Failed to close database %s", name) }
|
||||
.onFailure { Logger.w(it) { "Failed to close database $name" } }
|
||||
app.deleteDatabase(name)
|
||||
prefs.edit().remove(lastUsedKey(name)).apply()
|
||||
Timber.i("Evicted cached DB ${anonymizeDbName(name)}")
|
||||
Logger.i { "Evicted cached DB ${anonymizeDbName(name)}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,12 +188,12 @@ class DatabaseManager @Inject constructor(private val app: Application) {
|
|||
val legacyFile = getDbFile(app, legacy)
|
||||
if (legacyFile != null) {
|
||||
runCatching { dbCache.remove(legacy)?.close() }
|
||||
.onFailure { Timber.w(it, "Failed to close legacy database %s before deletion", legacy) }
|
||||
.onFailure { Logger.w(it) { "Failed to close legacy database $legacy before deletion" } }
|
||||
val deleted = app.deleteDatabase(legacy)
|
||||
if (deleted) {
|
||||
Timber.i("Deleted legacy DB ${anonymizeDbName(legacy)}")
|
||||
Logger.i { "Deleted legacy DB ${anonymizeDbName(legacy)}" }
|
||||
} else {
|
||||
Timber.w("Attempted to delete legacy DB %s but deleteDatabase returned false", legacy)
|
||||
Logger.w { "Attempted to delete legacy DB $legacy but deleteDatabase returned false" }
|
||||
}
|
||||
}
|
||||
prefs.edit().putBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, true).apply()
|
||||
|
|
|
|||
|
|
@ -48,5 +48,5 @@ dependencies {
|
|||
implementation(libs.androidx.datastore)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kermit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ import androidx.datastore.core.DataStore
|
|||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -43,9 +43,9 @@ class BootloaderWarningDataSource @Inject constructor(private val dataStore: Dat
|
|||
runCatching { Json.decodeFromString<List<String>>(jsonString).toSet() }
|
||||
.onFailure { e ->
|
||||
if (e is IllegalArgumentException || e is SerializationException) {
|
||||
Timber.w(e, "Failed to parse dismissed bootloader warning addresses, resetting preference")
|
||||
Logger.w(e) { "Failed to parse dismissed bootloader warning addresses, resetting preference" }
|
||||
} else {
|
||||
Timber.w(e, "Unexpected error while parsing dismissed bootloader warning addresses")
|
||||
Logger.w(e) { "Unexpected error while parsing dismissed bootloader warning addresses" }
|
||||
}
|
||||
}
|
||||
.getOrDefault(emptySet())
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@
|
|||
package org.meshtastic.core.datastore
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import org.meshtastic.proto.AppOnlyProtos.ChannelSet
|
||||
import org.meshtastic.proto.ChannelProtos.Channel
|
||||
import org.meshtastic.proto.ChannelProtos.ChannelSettings
|
||||
import org.meshtastic.proto.ConfigProtos
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -36,7 +36,7 @@ class ChannelSetDataSource @Inject constructor(private val channelSetStore: Data
|
|||
channelSetStore.data.catch { exception ->
|
||||
// dataStore.data throws an IOException when an error is encountered when reading data
|
||||
if (exception is IOException) {
|
||||
Timber.e("Error reading DeviceConfig settings: ${exception.message}")
|
||||
Logger.e { "Error reading DeviceConfig settings: ${exception.message}" }
|
||||
emit(ChannelSet.getDefaultInstance())
|
||||
} else {
|
||||
throw exception
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@
|
|||
package org.meshtastic.core.datastore
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import org.meshtastic.proto.ConfigProtos.Config
|
||||
import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -34,7 +34,7 @@ class LocalConfigDataSource @Inject constructor(private val localConfigStore: Da
|
|||
localConfigStore.data.catch { exception ->
|
||||
// dataStore.data throws an IOException when an error is encountered when reading data
|
||||
if (exception is IOException) {
|
||||
Timber.e("Error reading LocalConfig settings: ${exception.message}")
|
||||
Logger.e { "Error reading LocalConfig settings: ${exception.message}" }
|
||||
emit(LocalConfig.getDefaultInstance())
|
||||
} else {
|
||||
throw exception
|
||||
|
|
@ -53,7 +53,7 @@ class LocalConfigDataSource @Inject constructor(private val localConfigStore: Da
|
|||
if (localField != null) {
|
||||
builder.setField(localField, value)
|
||||
} else {
|
||||
Timber.e("Error writing LocalConfig settings: ${config.payloadVariantCase}")
|
||||
Logger.e { "Error writing LocalConfig settings: ${config.payloadVariantCase}" }
|
||||
}
|
||||
}
|
||||
builder.build()
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@
|
|||
package org.meshtastic.core.datastore
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import org.meshtastic.proto.LocalOnlyProtos.LocalModuleConfig
|
||||
import org.meshtastic.proto.ModuleConfigProtos.ModuleConfig
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -34,7 +34,7 @@ class ModuleConfigDataSource @Inject constructor(private val moduleConfigStore:
|
|||
moduleConfigStore.data.catch { exception ->
|
||||
// dataStore.data throws an IOException when an error is encountered when reading data
|
||||
if (exception is IOException) {
|
||||
Timber.e("Error reading LocalModuleConfig settings: ${exception.message}")
|
||||
Logger.e { "Error reading LocalModuleConfig settings: ${exception.message}" }
|
||||
emit(LocalModuleConfig.getDefaultInstance())
|
||||
} else {
|
||||
throw exception
|
||||
|
|
@ -53,7 +53,7 @@ class ModuleConfigDataSource @Inject constructor(private val moduleConfigStore:
|
|||
if (localField != null) {
|
||||
builder.setField(localField, value)
|
||||
} else {
|
||||
Timber.e("Error writing LocalModuleConfig settings: ${config.payloadVariantCase}")
|
||||
Logger.e { "Error writing LocalModuleConfig settings: ${config.payloadVariantCase}" }
|
||||
}
|
||||
}
|
||||
builder.build()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.datastore.core.DataStore
|
|||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -29,7 +30,6 @@ import kotlinx.serialization.json.Json
|
|||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.meshtastic.core.datastore.model.RecentAddress
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -46,11 +46,11 @@ class RecentAddressesDataSource @Inject constructor(private val dataStore: DataS
|
|||
try {
|
||||
Json.decodeFromString<List<RecentAddress>>(jsonString)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.w("Could not parse recent addresses, falling back to legacy parsing: ${e.message}")
|
||||
Logger.w { "Could not parse recent addresses, falling back to legacy parsing: ${e.message}" }
|
||||
// Fallback to legacy parsing
|
||||
parseLegacyRecentAddresses(jsonString)
|
||||
} catch (e: SerializationException) {
|
||||
Timber.w("Could not parse recent addresses, falling back to legacy parsing: ${e.message}")
|
||||
Logger.w { "Could not parse recent addresses, falling back to legacy parsing: ${e.message}" }
|
||||
// Fallback to legacy parsing
|
||||
parseLegacyRecentAddresses(jsonString)
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ class RecentAddressesDataSource @Inject constructor(private val dataStore: DataS
|
|||
}
|
||||
else -> {
|
||||
// Unknown format, log or handle as an error if necessary
|
||||
Timber.w("Unknown item type in recent IP addresses: $item")
|
||||
Logger.w { "Unknown item type in recent IP addresses: $item" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ dependencies {
|
|||
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kermit)
|
||||
implementation(libs.zxing.android.embedded) { isTransitive = false }
|
||||
implementation(libs.zxing.core)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,30 +2,6 @@
|
|||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MagicNumber:Channel.kt$0xff</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.03125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.0625f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.203125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.40625f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.8125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1.6250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1000f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1600</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$200</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$3.25f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$31</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$400</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$5</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$62</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$800</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_MODERATE$.125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_SLOW$.125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_SLOW$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.SHORT_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.SHORT_SLOW$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.VERY_LONG_SLOW$.0625f</ID>
|
||||
<ID>MagicNumber:ChannelSet.kt$40</ID>
|
||||
<ID>MagicNumber:ChannelSet.kt$960</ID>
|
||||
<ID>SwallowedException:ChannelSet.kt$ex: Throwable</ID>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import timber.log.Timber
|
||||
import co.touchlab.kermit.Logger
|
||||
|
||||
/** Provide structured access to parse and compare device version strings */
|
||||
data class DeviceVersion(val asString: String) : Comparable<DeviceVersion> {
|
||||
|
|
@ -28,7 +28,7 @@ data class DeviceVersion(val asString: String) : Comparable<DeviceVersion> {
|
|||
try {
|
||||
verStringToInt(asString)
|
||||
} catch (e: Exception) {
|
||||
Timber.w("Exception while parsing version '$asString', assuming version 0")
|
||||
Logger.w { "Exception while parsing version '$asString', assuming version 0" }
|
||||
0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,14 +20,13 @@ package org.meshtastic.core.model.util
|
|||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.MultiFormatWriter
|
||||
import com.journeyapps.barcodescanner.BarcodeEncoder
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.proto.AppOnlyProtos.ChannelSet
|
||||
import timber.log.Timber
|
||||
import java.net.MalformedURLException
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
private const val MESHTASTIC_HOST = "meshtastic.org"
|
||||
private const val CHANNEL_PATH = "/e/"
|
||||
|
|
@ -86,6 +85,6 @@ fun ChannelSet.qrCode(shouldAdd: Boolean): Bitmap? = try {
|
|||
val barcodeEncoder = BarcodeEncoder()
|
||||
barcodeEncoder.createBitmap(bitMatrix)
|
||||
} catch (ex: Throwable) {
|
||||
Timber.e("URL was too complex to render as barcode")
|
||||
Logger.e { "URL was too complex to render as barcode" }
|
||||
null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,5 +46,5 @@ dependencies {
|
|||
implementation(projects.core.proto)
|
||||
implementation(libs.javax.inject)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kermit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.meshtastic.core.service
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -25,7 +26,6 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
import org.meshtastic.proto.MeshProtos.MeshPacket
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ class ServiceRepository @Inject constructor() {
|
|||
get() = _clientNotification
|
||||
|
||||
fun setClientNotification(notification: MeshProtos.ClientNotification?) {
|
||||
Timber.e(notification?.message.orEmpty())
|
||||
Logger.e { notification?.message.orEmpty() }
|
||||
|
||||
_clientNotification.value = notification
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ class ServiceRepository @Inject constructor() {
|
|||
get() = _errorMessage
|
||||
|
||||
fun setErrorMessage(text: String) {
|
||||
Timber.e(text)
|
||||
Logger.e { text }
|
||||
_errorMessage.value = text
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ dependencies {
|
|||
implementation(libs.guava)
|
||||
implementation(libs.zxing.core)
|
||||
implementation(libs.zxing.android.embedded)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kermit)
|
||||
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,6 @@
|
|||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>ComposableParamOrder:AlertDialogs.kt$SimpleAlertDialog</ID>
|
||||
<ID>ComposableParamOrder:EditBase64Preference.kt$EditBase64Preference</ID>
|
||||
<ID>ComposableParamOrder:EditTextPreference.kt$EditTextPreference</ID>
|
||||
<ID>ComposableParamOrder:MainAppBar.kt$MainAppBar</ID>
|
||||
<ID>ComposableParamOrder:MaterialBatteryInfo.kt$MaterialBatteryInfo</ID>
|
||||
<ID>ComposableParamOrder:NodeChip.kt$NodeChip</ID>
|
||||
<ID>ComposableParamOrder:NodeKeyStatusIcon.kt$NodeKeyStatusIcon</ID>
|
||||
<ID>ComposableParamOrder:SignalInfo.kt$SignalInfo</ID>
|
||||
<ID>ComposableParamOrder:SwitchPreference.kt$SwitchPreference</ID>
|
||||
<ID>ContentSlotReused:AdaptiveTwoPane.kt$second</ID>
|
||||
<ID>LambdaParameterEventTrailing:ContactSharing.kt$onSharedContactRequested</ID>
|
||||
<ID>LambdaParameterEventTrailing:MainAppBar.kt$onClickChip</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$0xff</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$16</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$24</ID>
|
||||
|
|
@ -22,47 +10,5 @@
|
|||
<ID>MagicNumber:EditListPreference.kt$12345</ID>
|
||||
<ID>MagicNumber:EditListPreference.kt$67890</ID>
|
||||
<ID>MagicNumber:LazyColumnDragAndDropDemo.kt$50</ID>
|
||||
<ID>ModifierMissing:AdaptiveTwoPane.kt$AdaptiveTwoPane</ID>
|
||||
<ID>ModifierMissing:ChannelItem.kt$ChannelItem</ID>
|
||||
<ID>ModifierMissing:ChannelSelection.kt$ChannelSelection</ID>
|
||||
<ID>ModifierMissing:ContactSharing.kt$SharedContactDialog</ID>
|
||||
<ID>ModifierMissing:EmojiPicker.kt$EmojiPicker</ID>
|
||||
<ID>ModifierMissing:EmojiPicker.kt$EmojiPickerDialog</ID>
|
||||
<ID>ModifierMissing:IndoorAirQuality.kt$IndoorAirQuality</ID>
|
||||
<ID>ModifierMissing:LoraSignalIndicator.kt$LoraSignalIndicator</ID>
|
||||
<ID>ModifierMissing:LoraSignalIndicator.kt$Rssi</ID>
|
||||
<ID>ModifierMissing:LoraSignalIndicator.kt$Snr</ID>
|
||||
<ID>ModifierMissing:LoraSignalIndicator.kt$SnrAndRssi</ID>
|
||||
<ID>ModifierMissing:PreferenceDivider.kt$PreferenceDivider</ID>
|
||||
<ID>ModifierMissing:SecurityIcon.kt$SecurityIcon</ID>
|
||||
<ID>ModifierMissing:SharedContactDialog.kt$SharedContactDialog</ID>
|
||||
<ID>ModifierMissing:SimpleAlertDialog.kt$SimpleAlertDialog</ID>
|
||||
<ID>ModifierMissing:SlidingSelector.kt$OptionLabel</ID>
|
||||
<ID>ModifierNotUsedAtRoot:TextDividerPreference.kt$modifier = modifier.fillMaxWidth().padding(all = 16.dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:TextDividerPreference.kt$modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End)</ID>
|
||||
<ID>ModifierReused:PreferenceCategory.kt$Card(modifier = modifier.padding(bottom = 8.dp)) { Column( modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { ProvideTextStyle(MaterialTheme.typography.bodyLarge) { content() } } }</ID>
|
||||
<ID>ModifierReused:PreferenceCategory.kt$Text( text, modifier = modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp, end = 16.dp), style = MaterialTheme.typography.titleLarge, )</ID>
|
||||
<ID>ModifierReused:TextDividerPreference.kt$Card(modifier = modifier.fillMaxWidth()) { Row(modifier = modifier.fillMaxWidth().padding(all = 16.dp), verticalAlignment = Alignment.CenterVertically) { Text( text = title, style = MaterialTheme.typography.bodyLarge, color = if (!enabled) { MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) } else { Color.Unspecified }, ) if (trailingIcon != null) { Icon(trailingIcon, "trailingIcon", modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End)) } } }</ID>
|
||||
<ID>ModifierReused:TextDividerPreference.kt$Icon(trailingIcon, "trailingIcon", modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End))</ID>
|
||||
<ID>ModifierReused:TextDividerPreference.kt$Row(modifier = modifier.fillMaxWidth().padding(all = 16.dp), verticalAlignment = Alignment.CenterVertically) { Text( text = title, style = MaterialTheme.typography.bodyLarge, color = if (!enabled) { MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) } else { Color.Unspecified }, ) if (trailingIcon != null) { Icon(trailingIcon, "trailingIcon", modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End)) } }</ID>
|
||||
<ID>MultipleEmitters:PreferenceCategory.kt$PreferenceCategory</ID>
|
||||
<ID>ParameterNaming:BitwisePreference.kt$onItemSelected</ID>
|
||||
<ID>ParameterNaming:ChannelSelection.kt$onSelected</ID>
|
||||
<ID>ParameterNaming:ContactSharing.kt$onSharedContactRequested</ID>
|
||||
<ID>ParameterNaming:DropDownPreference.kt$onItemSelected</ID>
|
||||
<ID>ParameterNaming:EditIPv4Preference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:EditListPreference.kt$onValuesChanged</ID>
|
||||
<ID>ParameterNaming:EditPasswordPreference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:EditTextPreference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:PositionPrecisionPreference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onNegativeClicked</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onPositiveClicked</ID>
|
||||
<ID>ParameterNaming:SlidingSelector.kt$onOptionSelected</ID>
|
||||
<ID>PreviewPublic:IndoorAirQuality.kt$IAQScalePreview</ID>
|
||||
<ID>PreviewPublic:LazyColumnDragAndDropDemo.kt$LazyColumnDragAndDropDemo</ID>
|
||||
<ID>PreviewPublic:MaterialBatteryInfo.kt$MaterialBatteryInfoPreview</ID>
|
||||
<ID>PreviewPublic:SignalInfo.kt$SignalInfoPreview</ID>
|
||||
<ID>PreviewPublic:SignalInfo.kt$SignalInfoSelfPreview</ID>
|
||||
<ID>PreviewPublic:SignalInfo.kt$SignalInfoSimplePreview</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
|
@ -65,7 +66,6 @@ import org.meshtastic.core.ui.R
|
|||
import org.meshtastic.core.ui.share.SharedContactDialog
|
||||
import org.meshtastic.proto.AdminProtos
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
import timber.log.Timber
|
||||
import java.net.MalformedURLException
|
||||
|
||||
/**
|
||||
|
|
@ -90,7 +90,7 @@ fun AddContactFAB(
|
|||
try {
|
||||
uri.toSharedContact()
|
||||
} catch (ex: MalformedURLException) {
|
||||
Timber.e("URL was malformed: ${ex.message}")
|
||||
Logger.e { "URL was malformed: ${ex.message}" }
|
||||
null
|
||||
}
|
||||
if (sharedContact != null) {
|
||||
|
|
@ -102,7 +102,7 @@ fun AddContactFAB(
|
|||
sharedContact?.let { SharedContactDialog(sharedContact = it, onDismiss = { onSharedContactRequested(null) }) }
|
||||
|
||||
fun zxingScan() {
|
||||
Timber.d("Starting zxing QR code scanner")
|
||||
Logger.d { "Starting zxing QR code scanner" }
|
||||
val zxingScan = ScanOptions()
|
||||
zxingScan.setCameraId(CAMERA_ID)
|
||||
zxingScan.setPrompt("")
|
||||
|
|
@ -115,9 +115,9 @@ fun AddContactFAB(
|
|||
|
||||
LaunchedEffect(cameraPermissionState.status) {
|
||||
if (cameraPermissionState.status.isGranted) {
|
||||
Timber.d("Camera permission granted")
|
||||
Logger.d { "Camera permission granted" }
|
||||
} else {
|
||||
Timber.d("Camera permission denied")
|
||||
Logger.d { "Camera permission denied" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ val Uri.qrCode: Bitmap?
|
|||
val barcodeEncoder = BarcodeEncoder()
|
||||
barcodeEncoder.createBitmap(bitMatrix)
|
||||
} catch (ex: WriterException) {
|
||||
Timber.e("URL was too complex to render as barcode: ${ex.message}")
|
||||
Logger.e { "URL was too complex to render as barcode: ${ex.message}" }
|
||||
null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package org.meshtastic.core.ui.qr
|
|||
import android.os.RemoteException
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
|
|
@ -32,7 +33,6 @@ import org.meshtastic.proto.ConfigProtos.Config
|
|||
import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
|
||||
import org.meshtastic.proto.channelSet
|
||||
import org.meshtastic.proto.config
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -61,7 +61,7 @@ constructor(
|
|||
try {
|
||||
serviceRepository.meshService?.setChannel(channel.toByteArray())
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e(ex, "Set channel error")
|
||||
Logger.e(ex) { "Set channel error" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ constructor(
|
|||
try {
|
||||
serviceRepository.meshService?.setConfig(config.toByteArray())
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e(ex, "Set config error")
|
||||
Logger.e(ex) { "Set config error" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue