mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
fix(lotw): precise uploaded marking; surface skipped/unavailable counts
P2 — markUploaded now only marks the records that actually made it into the accepted TQ8 batch (preview.submittedIds), never the whole candidate list. Previously, un-signable records skipped by prepare() were still marked UP after any accepted upload, falsely showing them as uploaded. P4 — surface why records did not upload: - When audit finds nothing pending, distinguish unavailable records (invalid call/date — check logbook) from unknown-result records (not retried automatically) instead of a blanket 'no pending'. - Upload preview dialogs (Log tab + logbook) show a breakdown line: N already uploaded/duplicate · N unknown result · N un-uploadable.
This commit is contained in:
1 parent
fe85703e56
commit
1dd185971f
6 files changed
+45
-7
No files matched your search
+5
-2
@@ -196,6 +196,7 @@ class LoTWUploadRepository internal constructor(
|
|||||||
val ledger = ledger()
|
val ledger = ledger()
|
||||||
var skipped = 0
|
var skipped = 0
|
||||||
var unknown = 0
|
var unknown = 0
|
||||||
|
var unavailable = 0
|
||||||
val unique = hashSetOf<String>()
|
val unique = hashSetOf<String>()
|
||||||
val contacts = records.sortedBy { it.startUtcMillis }.mapNotNull { record ->
|
val contacts = records.sortedBy { it.startUtcMillis }.mapNotNull { record ->
|
||||||
coroutineContext.ensureActive()
|
coroutineContext.ensureActive()
|
||||||
@@ -205,7 +206,7 @@ class LoTWUploadRepository internal constructor(
|
|||||||
} catch (_: LoTWOperationException) {
|
} catch (_: LoTWOperationException) {
|
||||||
// One un-signable record must not abort the whole batch:
|
// One un-signable record must not abort the whole batch:
|
||||||
// skip it, count it, and let the rest upload.
|
// skip it, count it, and let the rest upload.
|
||||||
skipped++
|
unavailable++
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
val previous = ledger[contact.fingerprint]
|
val previous = ledger[contact.fingerprint]
|
||||||
@@ -221,7 +222,9 @@ class LoTWUploadRepository internal constructor(
|
|||||||
contacts.firstOrNull()?.record?.let { utc(it.startUtcMillis, "yyyy-MM-dd HH:mm:ss") }.orEmpty(),
|
contacts.firstOrNull()?.record?.let { utc(it.startUtcMillis, "yyyy-MM-dd HH:mm:ss") }.orEmpty(),
|
||||||
contacts.lastOrNull()?.record?.let { utc(it.startUtcMillis, "yyyy-MM-dd HH:mm:ss") }.orEmpty(),
|
contacts.lastOrNull()?.record?.let { utc(it.startUtcMillis, "yyyy-MM-dd HH:mm:ss") }.orEmpty(),
|
||||||
contacts.map { "${utc(it.record.startUtcMillis, "MM-dd HH:mm")} ${it.record.theirCallsign} ${it.fields["MODE"]} ${it.fields["SAT_NAME"].orEmpty()}" },
|
contacts.map { "${utc(it.record.startUtcMillis, "MM-dd HH:mm")} ${it.record.theirCallsign} ${it.fields["MODE"]} ${it.fields["SAT_NAME"].orEmpty()}" },
|
||||||
unknown
|
unknown,
|
||||||
|
unavailable,
|
||||||
|
contacts.map { it.record.id }
|
||||||
)
|
)
|
||||||
if (contacts.isNotEmpty()) pending = Pending(
|
if (contacts.isNotEmpty()) pending = Pending(
|
||||||
preview,
|
preview,
|
||||||
|
|||||||
+6
-1
@@ -66,7 +66,12 @@ data class LoTWUploadPreview(
|
|||||||
val firstUtc: String,
|
val firstUtc: String,
|
||||||
val lastUtc: String,
|
val lastUtc: String,
|
||||||
val contacts: List<String>,
|
val contacts: List<String>,
|
||||||
val unknownSkipped: Int = 0
|
val unknownSkipped: Int = 0,
|
||||||
|
/** Un-signable records (invalid call/date/…) skipped instead of aborting the batch. */
|
||||||
|
val unavailableSkipped: Int = 0,
|
||||||
|
/** Ids of the records that actually made it into this TQ8 batch. Only these
|
||||||
|
* may be marked "uploaded" after an accepted POST — never the full candidate list. */
|
||||||
|
val submittedIds: List<Long> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Local comparison against downloaded LoTW receipt flags and this app's durable upload receipts. */
|
/** Local comparison against downloaded LoTW receipt flags and this app's durable upload receipts. */
|
||||||
|
|||||||
@@ -294,6 +294,14 @@ private fun UploadPreviewDialog(
|
|||||||
}.parse(it).time }.getOrDefault(System.currentTimeMillis())
|
}.parse(it).time }.getOrDefault(System.currentTimeMillis())
|
||||||
}))
|
}))
|
||||||
}Z", fontSize = 13.sp)
|
}Z", fontSize = 13.sp)
|
||||||
|
if (preview.skipped > 0 || preview.unknownSkipped > 0 || preview.unavailableSkipped > 0) {
|
||||||
|
val parts = buildList {
|
||||||
|
if (preview.skipped > 0) add("${preview.skipped} already uploaded/duplicate")
|
||||||
|
if (preview.unknownSkipped > 0) add("${preview.unknownSkipped} unknown result")
|
||||||
|
if (preview.unavailableSkipped > 0) add("${preview.unavailableSkipped} un-uploadable")
|
||||||
|
}
|
||||||
|
Text(parts.joinToString(" · "), fontSize = 12.sp, color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -160,11 +160,18 @@ class LogViewModel(
|
|||||||
val pending = all.filter { !it.lotwConfirmed && it.status == QsoStatus.COMPLETE }
|
val pending = all.filter { !it.lotwConfirmed && it.status == QsoStatus.COMPLETE }
|
||||||
val audit = lotwUploadRepository.audit(pending)
|
val audit = lotwUploadRepository.audit(pending)
|
||||||
if (audit.pending == 0) {
|
if (audit.pending == 0) {
|
||||||
_uiState.update { it.copy(busy = false, message = "No pending QSOs to upload") }
|
val msg = when {
|
||||||
|
audit.unavailable > 0 -> "${audit.unavailable} QSO(s) can't be uploaded (invalid call/date — check the logbook)"
|
||||||
|
audit.unknown > 0 -> "${audit.unknown} QSO(s) had an unknown upload result — not retried automatically"
|
||||||
|
else -> "No pending QSOs to upload"
|
||||||
|
}
|
||||||
|
_uiState.update { it.copy(busy = false, message = msg) }
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val preview = lotwUploadRepository.prepare(pending, false)
|
val preview = lotwUploadRepository.prepare(pending, false)
|
||||||
lastUploadedIds = pending.map { it.id }
|
// Only the records that actually made it into the TQ8 may be
|
||||||
|
// marked uploaded later — never the whole candidate list.
|
||||||
|
lastUploadedIds = preview.submittedIds
|
||||||
_uiState.update { it.copy(busy = false, preview = preview) }
|
_uiState.update { it.copy(busy = false, preview = preview) }
|
||||||
} catch (e: LoTWOperationException) {
|
} catch (e: LoTWOperationException) {
|
||||||
_uiState.update { it.copy(busy = false, message = "Upload unavailable: ${e.reason}") }
|
_uiState.update { it.copy(busy = false, message = "Upload unavailable: ${e.reason}") }
|
||||||
|
|||||||
@@ -145,6 +145,14 @@ private fun LogbookUploadPreviewDialog(
|
|||||||
"${preview.count} QSO(s) · ${preview.firstUtc} – ${preview.lastUtc}",
|
"${preview.count} QSO(s) · ${preview.firstUtc} – ${preview.lastUtc}",
|
||||||
fontSize = 13.sp
|
fontSize = 13.sp
|
||||||
)
|
)
|
||||||
|
if (preview.skipped > 0 || preview.unknownSkipped > 0 || preview.unavailableSkipped > 0) {
|
||||||
|
val parts = buildList {
|
||||||
|
if (preview.skipped > 0) add("${preview.skipped} already uploaded/duplicate")
|
||||||
|
if (preview.unknownSkipped > 0) add("${preview.unknownSkipped} unknown result")
|
||||||
|
if (preview.unavailableSkipped > 0) add("${preview.unavailableSkipped} un-uploadable")
|
||||||
|
}
|
||||||
|
Text(parts.joinToString(" · "), fontSize = 12.sp, color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+9
-2
@@ -367,11 +367,18 @@ class SettingsViewModel(
|
|||||||
val pending = all.filter { !it.lotwConfirmed && it.status == com.rtbishop.look4sat.core.domain.logbook.QsoStatus.COMPLETE }
|
val pending = all.filter { !it.lotwConfirmed && it.status == com.rtbishop.look4sat.core.domain.logbook.QsoStatus.COMPLETE }
|
||||||
val audit = lotwUploadRepository.audit(pending)
|
val audit = lotwUploadRepository.audit(pending)
|
||||||
if (audit.pending == 0) {
|
if (audit.pending == 0) {
|
||||||
_uiState.update { it.copy(logbookUploadBusy = false, logbookUploadMessage = "No pending QSOs to upload") }
|
val msg = when {
|
||||||
|
audit.unavailable > 0 -> "${audit.unavailable} QSO(s) can't be uploaded (invalid call/date — check the logbook)"
|
||||||
|
audit.unknown > 0 -> "${audit.unknown} QSO(s) had an unknown upload result — not retried automatically"
|
||||||
|
else -> "No pending QSOs to upload"
|
||||||
|
}
|
||||||
|
_uiState.update { it.copy(logbookUploadBusy = false, logbookUploadMessage = msg) }
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val preview = lotwUploadRepository.prepare(pending, false)
|
val preview = lotwUploadRepository.prepare(pending, false)
|
||||||
lastLogbookUploadIds = pending.map { it.id }
|
// Only the records that actually made it into the TQ8 may be
|
||||||
|
// marked uploaded later — never the whole candidate list.
|
||||||
|
lastLogbookUploadIds = preview.submittedIds
|
||||||
_uiState.update { it.copy(logbookUploadBusy = false, logbookPreview = preview) }
|
_uiState.update { it.copy(logbookUploadBusy = false, logbookPreview = preview) }
|
||||||
} catch (e: com.rtbishop.look4sat.core.domain.repository.LoTWOperationException) {
|
} catch (e: com.rtbishop.look4sat.core.domain.repository.LoTWOperationException) {
|
||||||
_uiState.update { it.copy(logbookUploadBusy = false, logbookUploadMessage = "Upload unavailable: ${e.reason}") }
|
_uiState.update { it.copy(logbookUploadBusy = false, logbookUploadMessage = "Upload unavailable: ${e.reason}") }
|
||||||
|
|||||||
Reference in new issue
Block a user