diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt
index 05a6891c..99dd5b10 100644
--- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt
+++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt
@@ -107,12 +107,17 @@ class DatabaseRepo(
// launch all network requests concurrently
val tleJobs = tleUrls.values.map { url -> async { url to remoteSource.getNetworkStream(url) } }
val radioJobs = radioUrls.values.map { url -> async { url to remoteSource.getNetworkStream(url) } }
- // Count successful sources: zero successes = update failed (timestamp untouched, exception surfaced in the UI)
val tleResults = tleJobs.awaitAll()
val radioResults = radioJobs.awaitAll()
- val successCount = tleResults.count { it.second != null } + radioResults.count { it.second != null }
- if (successCount == 0) {
- throw java.io.IOException("All data sources failed to download")
+ // Orbital elements are counted on their own. A combined count let a successful transceivers
+ // fetch stand in for a failed orbital one: with a custom TLE URL there are two requests
+ // rather than 28, so if that URL was down and SatNOGS answered, the total was 1, no
+ // exception was raised, and setUpdateSuccessful stamped a fresh timestamp for an update
+ // that refreshed no orbital data at all - which also suppressed the 48-hour auto-update
+ // retry that keys off that timestamp. The failure existed before but 26 other sources hid
+ // it; replacing them made it easy to hit.
+ if (tleResults.none { it.second != null }) {
+ throw java.io.IOException("No orbital data source could be downloaded")
}
// parse fetched data concurrently and associate with types
val importedEntries = tleResults.flatMap { (url, stream) ->
diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt
index 46467c9b..03c193be 100644
--- a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt
+++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt
@@ -159,6 +159,42 @@ class DatabaseRepoTest {
)
}
+ /**
+ * A dead custom URL must fail the update even when the transceivers source answers.
+ *
+ * The counts used to be added together, so one transceivers success covered a total orbital
+ * failure: no exception, and a fresh "updated successfully" timestamp for an update that
+ * refreshed nothing. Replacing the built-in sources shrank the denominator from 28 to 2 and
+ * made that easy to hit.
+ */
+ @Test
+ fun `a dead custom url fails the update even if transceivers succeed`() = runTest(dispatcher) {
+ val localSource = FakeLocalSource()
+ val remoteSource = FakeRemoteSource().apply {
+ Sources.transceiversDataUrls.values.filter { it.isNotBlank() }
+ .forEach { networkStreams[it] = { "[]".byteInputStream() } }
+ }
+ val settingsRepo = FakeSettingsRepo(
+ dataSources = DataSourcesSettings(
+ useCustomTLE = true,
+ useCustomTransceivers = false,
+ tleUrl = "https://example.com/dead.csv",
+ transceiversUrl = ""
+ )
+ )
+ val repository = DatabaseRepo(dispatcher, dataParser, localSource, remoteSource, settingsRepo)
+
+ var threw = false
+ try {
+ repository.updateFromRemote()
+ } catch (_: java.io.IOException) {
+ threw = true
+ }
+
+ assertTrue("a total orbital failure must raise", threw)
+ assertTrue("no entries may be inserted", localSource.insertedEntries.isEmpty())
+ }
+
private fun validCsvStream(): InputStream = """
OBJECT_NAME,OBJECT_ID,EPOCH,MEAN_MOTION,ECCENTRICITY,INCLINATION,RA_OF_ASC_NODE,ARG_OF_PERICENTER,MEAN_ANOMALY,EPHEMERIS_TYPE,CLASSIFICATION_TYPE,NORAD_CAT_ID,ELEMENT_SET_NO,REV_AT_EPOCH,BSTAR,MEAN_MOTION_DOT,MEAN_MOTION_DDOT
ISS (ZARYA),1998-067A,2021-11-16T12:28:09.322176,15.48582035,.0004694,51.6447,309.4881,203.6966,299.8876,0,U,25544,999,31220,.31985E-4,.1288E-4,0
diff --git a/core/presentation/src/main/res/values-zh/strings.xml b/core/presentation/src/main/res/values-zh/strings.xml
index 5e83387e..b75408a7 100644
--- a/core/presentation/src/main/res/values-zh/strings.xml
+++ b/core/presentation/src/main/res/values-zh/strings.xml
@@ -263,6 +263,7 @@
已存入本地日志
已上传 %1$d 条, %2$d 条仍在队列
已上传 %1$d 条
+ 没有待上传的联络
未配置 WaveLog 服务器
读不到站点信息 - 请检查站点 ID 与密钥权限
上传失败
diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml
index 50d016de..a44bc0f4 100644
--- a/core/presentation/src/main/res/values/strings.xml
+++ b/core/presentation/src/main/res/values/strings.xml
@@ -294,6 +294,7 @@
Saved to local log
Uploaded %1$d, %2$d still queued
Uploaded %1$d QSO
+ Nothing waiting to upload
WaveLog server not configured
Cannot read the station profile - check the station ID and key permissions
Upload failed
diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt
index adc82c0e..16eadec6 100644
--- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt
+++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt
@@ -313,6 +313,8 @@ class SettingsViewModel(
showToast(R.string.wavelog_no_station)
result.failedCount > 0 ->
showToast(R.string.wavelog_upload_done, result.successCount, result.failedCount)
+ // "Uploaded 0" is accurate but reads oddly when the queue was already clear.
+ result.successCount == 0 -> showToast(R.string.wavelog_nothing_to_upload)
else -> showToast(R.string.wavelog_upload_all_ok, result.successCount)
}
}