mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
fix: AMSAT live page timeouts wiped FM/Linear lists; add fallback
Root cause of the recurring empty AMSAT Live filters: OkHttp defaults to a
10s read timeout and amsat.org's live pages take 7-10s+ from many
networks — every sync timed out, fmNames/linearNames stayed empty, and
(with no previous list, e.g. right after clearing data) the filters were
written as EMPTY. The stale pre-whitelist FM list survived only because
older syncs had occasionally succeeded and the failure path preserved it.
Fix:
1. OkHttp timeouts raised (connect 20s / read 30s / write 30s / call 60s)
in MainContainer.provideRemoteSource so slow AMSAT pages actually load.
2. updateAmSatLiveLists gains a last-resort fallback: when the AMSAT page
is down AND there is no previous list to clean, derive FM/Linear from
the local transceivers (FM / SSB,CW,USB,LSB modes) intersected with
the amateur whitelist, so a network timeout never wipes the filters.
ISS module aliases stay excluded by the whitelist intersection.
Regression test: page fails + empty previous list -> fallback yields
{25544} (26700 excluded by whitelist).
This commit is contained in:
1 parent
a082abb72c
commit
865fa9c6f2
3 files changed
+53
-3
No files matched your search
@@ -74,6 +74,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainContainer(private val context: Context) : IMainContainer {
|
class MainContainer(private val context: Context) : IMainContainer {
|
||||||
|
|
||||||
@@ -177,7 +178,16 @@ class MainContainer(private val context: Context) : IMainContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun provideRemoteSource(): IRemoteSource {
|
private fun provideRemoteSource(): IRemoteSource {
|
||||||
return RemoteSource(Dispatchers.IO, context.contentResolver, OkHttpClient.Builder().build())
|
// amsat.org live pages are slow (7-10s+ from some networks); the
|
||||||
|
// OkHttp defaults (10s read timeout) made every AMSAT Live update
|
||||||
|
// time out, silently keeping stale FM/Linear lists forever.
|
||||||
|
val client = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(20, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.callTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
return RemoteSource(Dispatchers.IO, context.contentResolver, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun provideSatelliteRepo(): ISatelliteRepo {
|
private fun provideSatelliteRepo(): ISatelliteRepo {
|
||||||
|
|||||||
@@ -181,10 +181,20 @@ class DatabaseRepo(
|
|||||||
previous.mapNotNull { catnum ->
|
previous.mapNotNull { catnum ->
|
||||||
nameToCatnum.entries.firstOrNull { it.value == catnum }?.key
|
nameToCatnum.entries.firstOrNull { it.value == catnum }?.key
|
||||||
}.flatMap { resolvePerName(it) }.toSet()
|
}.flatMap { resolvePerName(it) }.toSet()
|
||||||
|
// Last-resort fallback when the AMSAT page is down AND there is no
|
||||||
|
// previous list to clean (e.g. right after clearing data): derive
|
||||||
|
// the FM/Linear sets from the local transceivers intersected with
|
||||||
|
// the amateur whitelist, so a network timeout never wipes the
|
||||||
|
// filters to empty.
|
||||||
|
val fmFallback = localSource.getIdsWithModes(listOf("FM")).toSet().intersect(activeCatnums)
|
||||||
|
val linearFallback = localSource
|
||||||
|
.getIdsWithModes(listOf("SSB", "CW", "USB", "LSB")).toSet().intersect(activeCatnums)
|
||||||
val fmCatnums = fmNames.flatMap { resolvePerName(it) }.toSet()
|
val fmCatnums = fmNames.flatMap { resolvePerName(it) }.toSet()
|
||||||
.ifEmpty { cleanStaleList(settingsRepo.getAmSatFmCatnums()) }
|
.ifEmpty { cleanStaleList(settingsRepo.getAmSatFmCatnums()) }
|
||||||
|
.ifEmpty { fmFallback }
|
||||||
val linearCatnums = linearNames.flatMap { resolvePerName(it) }.toSet()
|
val linearCatnums = linearNames.flatMap { resolvePerName(it) }.toSet()
|
||||||
.ifEmpty { cleanStaleList(settingsRepo.getAmSatLinearCatnums()) }
|
.ifEmpty { cleanStaleList(settingsRepo.getAmSatLinearCatnums()) }
|
||||||
|
.ifEmpty { linearFallback }
|
||||||
settingsRepo.setAmSatCatnums(fmCatnums, linearCatnums)
|
settingsRepo.setAmSatCatnums(fmCatnums, linearCatnums)
|
||||||
println("AMSAT live lists updated: FM=${fmCatnums.size}, Linear=${linearCatnums.size}, Active=${activeCatnums.size}")
|
println("AMSAT live lists updated: FM=${fmCatnums.size}, Linear=${linearCatnums.size}, Active=${activeCatnums.size}")
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
|||||||
+32
-2
@@ -270,6 +270,35 @@ class DatabaseRepoTest {
|
|||||||
assertEquals(setOf(25544), settingsRepo.amSatFm)
|
assertEquals(setOf(25544), settingsRepo.amSatFm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `amsat fm list falls back to local FM transceivers when page fails and no previous list`() =
|
||||||
|
runTest(dispatcher) {
|
||||||
|
// After clearing data, the previous FM list is empty AND the AMSAT
|
||||||
|
// page is down (timeout) -> the filter must fall back to local FM
|
||||||
|
// transceivers intersected with the whitelist, never stay empty.
|
||||||
|
val satnogsUrl = Sources.satelliteDataUrls.getValue("SatNOGS")
|
||||||
|
val localSource = FakeLocalSource().apply {
|
||||||
|
idsWithModes = listOf(25544, 26700, 39444) // local FM-transceiver sats
|
||||||
|
}
|
||||||
|
val remoteSource = FakeRemoteSource().apply {
|
||||||
|
networkStreams[satnogsUrl] = { issModulesCsvStream() }
|
||||||
|
// FM/Linear pages NOT registered -> 404 -> fetch fails.
|
||||||
|
}
|
||||||
|
val settingsRepo = FakeSettingsRepo(
|
||||||
|
dataSources = DataSourcesSettings(
|
||||||
|
satelliteUrls = listOf(satnogsUrl),
|
||||||
|
transceiversUrls = emptyList()
|
||||||
|
)
|
||||||
|
).apply { amSatActive = setOf(25544) } // whitelist has only ZARYA
|
||||||
|
val repository = DatabaseRepo(dispatcher, dataParser, localSource, remoteSource, settingsRepo)
|
||||||
|
|
||||||
|
repository.updateFromRemote()
|
||||||
|
|
||||||
|
// Fallback = local FM ids ∩ whitelist = {25544}; ISS module aliases
|
||||||
|
// (26700) are excluded by the whitelist.
|
||||||
|
assertEquals(setOf(25544), settingsRepo.amSatFm)
|
||||||
|
}
|
||||||
|
|
||||||
private fun amsatFmPageWithUnknownStream(): InputStream = """
|
private fun amsatFmPageWithUnknownStream(): InputStream = """
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><th>Satellite</th><th>Uplink</th><th>Downlink</th><th>Comment</th></tr></thead>
|
<thead><tr><th>Satellite</th><th>Uplink</th><th>Downlink</th><th>Comment</th></tr></thead>
|
||||||
@@ -320,6 +349,7 @@ private class FakeRemoteSource : IRemoteSource {
|
|||||||
private class FakeLocalSource : ILocalSource {
|
private class FakeLocalSource : ILocalSource {
|
||||||
val insertedEntries = mutableListOf<OrbitalData>()
|
val insertedEntries = mutableListOf<OrbitalData>()
|
||||||
private val insertedRadios = mutableListOf<SatRadio>()
|
private val insertedRadios = mutableListOf<SatRadio>()
|
||||||
|
var idsWithModes: List<Int> = emptyList()
|
||||||
|
|
||||||
override suspend fun getEntriesTotal(): Int = insertedEntries.size
|
override suspend fun getEntriesTotal(): Int = insertedEntries.size
|
||||||
|
|
||||||
@@ -336,8 +366,8 @@ private class FakeLocalSource : ILocalSource {
|
|||||||
insertedEntries.clear()
|
insertedEntries.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getIdsWithModes(modes: List<String>): List<Int> = emptyList()
|
override suspend fun getIdsWithModes(modes: List<String>): List<Int> = idsWithModes
|
||||||
override suspend fun getIdsWithModesAndUplink(modes: List<String>): List<Int> = emptyList()
|
override suspend fun getIdsWithModesAndUplink(modes: List<String>): List<Int> = idsWithModes
|
||||||
override suspend fun getIdsWithModesAndAmateur(modes: List<String>): List<Int> = emptyList()
|
override suspend fun getIdsWithModesAndAmateur(modes: List<String>): List<Int> = emptyList()
|
||||||
|
|
||||||
override suspend fun getRadiosTotal(): Int = insertedRadios.size
|
override suspend fun getRadiosTotal(): Int = insertedRadios.size
|
||||||
|
|||||||
Reference in new issue
Block a user