@@ -19,10 +19,12 @@ package org.meshtastic.core.data.repository
1919
2020import kotlinx.coroutines.Dispatchers
2121import kotlinx.coroutines.withContext
22+ import org.meshtastic.core.data.datasource.BootloaderOtaQuirksJsonDataSource
2223import org.meshtastic.core.data.datasource.DeviceHardwareJsonDataSource
2324import org.meshtastic.core.data.datasource.DeviceHardwareLocalDataSource
2425import org.meshtastic.core.database.entity.DeviceHardwareEntity
2526import org.meshtastic.core.database.entity.asExternalModel
27+ import org.meshtastic.core.model.BootloaderOtaQuirk
2628import org.meshtastic.core.model.DeviceHardware
2729import org.meshtastic.core.network.DeviceHardwareRemoteDataSource
2830import timber.log.Timber
@@ -38,6 +40,7 @@ constructor(
3840 private val remoteDataSource: DeviceHardwareRemoteDataSource ,
3941 private val localDataSource: DeviceHardwareLocalDataSource ,
4042 private val jsonDataSource: DeviceHardwareJsonDataSource ,
43+ private val bootloaderOtaQuirksJsonDataSource: BootloaderOtaQuirksJsonDataSource ,
4144) {
4245
4346 /* *
@@ -53,6 +56,7 @@ constructor(
5356 * @param forceRefresh If true, the local cache will be invalidated and data will be fetched remotely.
5457 * @return A [Result] containing the [DeviceHardware] on success (or null if not found), or an exception on failure.
5558 */
59+ @Suppress(" LongMethod" )
5660 suspend fun getDeviceHardwareByModel (hwModel : Int , forceRefresh : Boolean = false): Result <DeviceHardware ?> =
5761 withContext(Dispatchers .IO ) {
5862 Timber .d(
@@ -61,6 +65,8 @@ constructor(
6165 forceRefresh,
6266 )
6367
68+ val quirks = loadQuirks()
69+
6470 if (forceRefresh) {
6571 Timber .d(" DeviceHardwareRepository: forceRefresh=true, clearing local device hardware cache" )
6672 localDataSource.deleteAllDeviceHardware()
@@ -69,7 +75,9 @@ constructor(
6975 val cachedEntity = localDataSource.getByHwModel(hwModel)
7076 if (cachedEntity != null && ! cachedEntity.isStale()) {
7177 Timber .d(" DeviceHardwareRepository: using fresh cached device hardware for hwModel=%d" , hwModel)
72- return @withContext Result .success(cachedEntity.asExternalModel())
78+ return @withContext Result .success(
79+ applyBootloaderQuirk(hwModel, cachedEntity.asExternalModel(), quirks),
80+ )
7381 }
7482 Timber .d(" DeviceHardwareRepository: no fresh cache for hwModel=%d, attempting remote fetch" , hwModel)
7583 }
@@ -94,7 +102,7 @@ constructor(
94102 }
95103 .onSuccess {
96104 // Successfully fetched and found the model
97- return @withContext Result .success(it )
105+ return @withContext Result .success(applyBootloaderQuirk(hwModel, it, quirks) )
98106 }
99107 .onFailure { e ->
100108 Timber .w(
@@ -107,7 +115,9 @@ constructor(
107115 val staleEntity = localDataSource.getByHwModel(hwModel)
108116 if (staleEntity != null && ! staleEntity.isIncomplete()) {
109117 Timber .d(" DeviceHardwareRepository: using stale cached device hardware for hwModel=%d" , hwModel)
110- return @withContext Result .success(staleEntity.asExternalModel())
118+ return @withContext Result .success(
119+ applyBootloaderQuirk(hwModel, staleEntity.asExternalModel(), quirks),
120+ )
111121 }
112122
113123 // 4. Fallback to bundled JSON if cache is empty or incomplete
@@ -116,36 +126,38 @@ constructor(
116126 if (staleEntity == null ) " empty" else " incomplete" ,
117127 hwModel,
118128 )
119- return @withContext loadFromBundledJson(hwModel)
129+ return @withContext loadFromBundledJson(hwModel, quirks )
120130 }
121131 }
122132
123- private suspend fun loadFromBundledJson (hwModel : Int ): Result <DeviceHardware ?> = runCatching {
124- Timber .d(" DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=%d" , hwModel)
125- val jsonHardware = jsonDataSource.loadDeviceHardwareFromJsonAsset()
126- Timber .d(
127- " DeviceHardwareRepository: bundled JSON returned %d device hardware entries" ,
128- jsonHardware.size,
129- )
133+ private suspend fun loadFromBundledJson (hwModel : Int , quirks : List <BootloaderOtaQuirk >): Result <DeviceHardware ?> =
134+ runCatching {
135+ Timber .d(" DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=%d" , hwModel)
136+ val jsonHardware = jsonDataSource.loadDeviceHardwareFromJsonAsset()
137+ Timber .d(
138+ " DeviceHardwareRepository: bundled JSON returned %d device hardware entries" ,
139+ jsonHardware.size,
140+ )
130141
131- localDataSource.insertAllDeviceHardware(jsonHardware)
132- val fromDb = localDataSource.getByHwModel(hwModel)?.asExternalModel()
133- Timber .d(
134- " DeviceHardwareRepository: lookup after JSON load for hwModel=%d %s" ,
135- hwModel,
136- if (fromDb != null ) " succeeded" else " returned null" ,
137- )
138- fromDb
139- }
140- .also { result ->
141- result.exceptionOrNull()?.let { e ->
142- Timber .e(
143- e,
144- " DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=%d" ,
145- hwModel,
146- )
147- }
142+ localDataSource.insertAllDeviceHardware(jsonHardware)
143+ val base = localDataSource.getByHwModel(hwModel)?.asExternalModel()
144+ Timber .d(
145+ " DeviceHardwareRepository: lookup after JSON load for hwModel=%d %s" ,
146+ hwModel,
147+ if (base != null ) " succeeded" else " returned null" ,
148+ )
149+
150+ applyBootloaderQuirk(hwModel, base, quirks)
148151 }
152+ .also { result ->
153+ result.exceptionOrNull()?.let { e ->
154+ Timber .e(
155+ e,
156+ " DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=%d" ,
157+ hwModel,
158+ )
159+ }
160+ }
149161
150162 /* * Returns true if the cached entity is missing important fields and should be refreshed. */
151163 private fun DeviceHardwareEntity.isIncomplete (): Boolean =
@@ -160,6 +172,40 @@ constructor(
160172 private fun DeviceHardwareEntity.isStale (): Boolean =
161173 isIncomplete() || (System .currentTimeMillis() - this .lastUpdated) > CACHE_EXPIRATION_TIME_MS
162174
175+ private fun loadQuirks (): List <BootloaderOtaQuirk > {
176+ val quirks = bootloaderOtaQuirksJsonDataSource.loadBootloaderOtaQuirksFromJsonAsset()
177+ Timber .d(" DeviceHardwareRepository: loaded %d bootloader quirks" , quirks.size)
178+ return quirks
179+ }
180+
181+ private fun applyBootloaderQuirk (
182+ hwModel : Int ,
183+ base : DeviceHardware ? ,
184+ quirks : List <BootloaderOtaQuirk >,
185+ ): DeviceHardware ? {
186+ if (base == null ) return null
187+
188+ val quirk = quirks.firstOrNull { it.hwModel == hwModel }
189+ Timber .d(
190+ " DeviceHardwareRepository: applyBootloaderQuirk for hwModel=%d, quirk found=%b" ,
191+ hwModel,
192+ quirk != null ,
193+ )
194+ return if (quirk != null ) {
195+ Timber .d(
196+ " DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=%b, infoUrl=%s" ,
197+ quirk.requiresBootloaderUpgradeForOta,
198+ quirk.infoUrl,
199+ )
200+ base.copy(
201+ requiresBootloaderUpgradeForOta = quirk.requiresBootloaderUpgradeForOta,
202+ bootloaderInfoUrl = quirk.infoUrl,
203+ )
204+ } else {
205+ base
206+ }
207+ }
208+
163209 companion object {
164210 private val CACHE_EXPIRATION_TIME_MS = TimeUnit .DAYS .toMillis(1 )
165211 }
0 commit comments