mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2025-12-24 00:07:48 -05:00
fix: fdroid device hardware fallback using bundled JSON for incomplete cache entries (#3844)
This commit is contained in:
@@ -30,10 +30,14 @@ import javax.inject.Inject
|
||||
*/
|
||||
class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics {
|
||||
init {
|
||||
// For F-Droid builds we don't initialize external analytics services.
|
||||
// In debug builds we attach a DebugTree for convenient local logging.
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
Timber.i("F-Droid platform no-op analytics initialized (DebugTree planted).")
|
||||
} else {
|
||||
Timber.i("F-Droid platform no-op analytics initialized.")
|
||||
}
|
||||
Timber.i("F-Droid platform no-op analytics initialized.")
|
||||
}
|
||||
|
||||
override fun setDeviceAttributes(firmwareVersion: String, model: String) {
|
||||
|
||||
@@ -55,54 +55,98 @@ constructor(
|
||||
*/
|
||||
suspend fun getDeviceHardwareByModel(hwModel: Int, forceRefresh: Boolean = false): Result<DeviceHardware?> =
|
||||
withContext(Dispatchers.IO) {
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=%d, forceRefresh=%b)",
|
||||
hwModel,
|
||||
forceRefresh,
|
||||
)
|
||||
|
||||
if (forceRefresh) {
|
||||
Timber.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("Using fresh cached device hardware for model $hwModel")
|
||||
Timber.d("DeviceHardwareRepository: using fresh cached device hardware for hwModel=%d", hwModel)
|
||||
return@withContext Result.success(cachedEntity.asExternalModel())
|
||||
}
|
||||
Timber.d("DeviceHardwareRepository: no fresh cache for hwModel=%d, attempting remote fetch", hwModel)
|
||||
}
|
||||
|
||||
// 2. Fetch from remote API
|
||||
runCatching {
|
||||
Timber.d("Fetching device hardware from remote API.")
|
||||
Timber.d("DeviceHardwareRepository: fetching device hardware from remote API")
|
||||
val remoteHardware = remoteDataSource.getAllDeviceHardware()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: remote API returned %d device hardware entries",
|
||||
remoteHardware.size,
|
||||
)
|
||||
|
||||
localDataSource.insertAllDeviceHardware(remoteHardware)
|
||||
localDataSource.getByHwModel(hwModel)?.asExternalModel()
|
||||
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",
|
||||
)
|
||||
fromDb
|
||||
}
|
||||
.onSuccess {
|
||||
// Successfully fetched and found the model
|
||||
return@withContext Result.success(it)
|
||||
}
|
||||
.onFailure { e ->
|
||||
Timber.w("Failed to fetch device hardware from server: ${e.message}")
|
||||
Timber.w(
|
||||
e,
|
||||
"DeviceHardwareRepository: failed to fetch device hardware from server for hwModel=%d",
|
||||
hwModel,
|
||||
)
|
||||
|
||||
// 3. Attempt to use stale cache as a fallback
|
||||
// 3. Attempt to use stale cache as a fallback, but only if it looks complete.
|
||||
val staleEntity = localDataSource.getByHwModel(hwModel)
|
||||
if (staleEntity != null) {
|
||||
Timber.d("Using stale cached device hardware for model $hwModel")
|
||||
if (staleEntity != null && !staleEntity.isIncomplete()) {
|
||||
Timber.d("DeviceHardwareRepository: using stale cached device hardware for hwModel=%d", hwModel)
|
||||
return@withContext Result.success(staleEntity.asExternalModel())
|
||||
}
|
||||
|
||||
// 4. Fallback to bundled JSON if cache is empty
|
||||
Timber.d("Cache is empty, falling back to bundled JSON asset.")
|
||||
// 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,
|
||||
)
|
||||
return@withContext loadFromBundledJson(hwModel)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadFromBundledJson(hwModel: Int): Result<DeviceHardware?> = runCatching {
|
||||
Timber.d("DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=%d", hwModel)
|
||||
val jsonHardware = jsonDataSource.loadDeviceHardwareFromJsonAsset()
|
||||
Timber.d("DeviceHardwareRepository: bundled JSON returned %d device hardware entries", jsonHardware.size)
|
||||
|
||||
localDataSource.insertAllDeviceHardware(jsonHardware)
|
||||
localDataSource.getByHwModel(hwModel)?.asExternalModel()
|
||||
val fromDb = localDataSource.getByHwModel(hwModel)?.asExternalModel()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: lookup after JSON load for hwModel=%d %s",
|
||||
hwModel,
|
||||
if (fromDb != null) "succeeded" else "returned null",
|
||||
)
|
||||
fromDb
|
||||
}
|
||||
|
||||
/** Extension function to check if the cached entity is stale. */
|
||||
/** Returns true if the cached entity is missing important fields and should be refreshed. */
|
||||
private fun DeviceHardwareEntity.isIncomplete(): Boolean =
|
||||
displayName.isBlank() || platformioTarget.isBlank() || images.isNullOrEmpty()
|
||||
|
||||
/**
|
||||
* Extension function to check if the cached entity is stale.
|
||||
*
|
||||
* We treat entries with missing critical fields (e.g., no images or target) as stale so that they can be
|
||||
* automatically healed from newer JSON snapshots even if their timestamp is recent.
|
||||
*/
|
||||
private fun DeviceHardwareEntity.isStale(): Boolean =
|
||||
(System.currentTimeMillis() - this.lastUpdated) > CACHE_EXPIRATION_TIME_MS
|
||||
isIncomplete() || (System.currentTimeMillis() - this.lastUpdated) > CACHE_EXPIRATION_TIME_MS
|
||||
|
||||
companion object {
|
||||
private val CACHE_EXPIRATION_TIME_MS = TimeUnit.DAYS.toMillis(1)
|
||||
|
||||
Reference in New Issue
Block a user