mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-04 19:47:22 +00:00
feat(wavelog): wavelog export option + upload helpers (ADIF, candidates, messages)
This commit is contained in:
1 parent
73ac41ff85
commit
c050e7bb9b
4 files changed
+149
-2
No files matched your search
@@ -18,7 +18,7 @@ import java.util.TimeZone
|
||||
object AdifCodec {
|
||||
private val utc = TimeZone.getTimeZone("UTC")
|
||||
|
||||
fun encode(records: List<QsoRecord>): String = buildString {
|
||||
fun encode(records: List<QsoRecord>, includeStationCallsign: Boolean = true): String = buildString {
|
||||
append(field("ADIF_VER", "3.1.7"))
|
||||
append(field("PROGRAMID", "Look4Sat"))
|
||||
append("<EOH>\r\n")
|
||||
@@ -32,7 +32,7 @@ object AdifCodec {
|
||||
append(field("TIME_OFF", format(it, "HHmmss")))
|
||||
}
|
||||
append(field("CALL", record.theirCallsign))
|
||||
append(field("STATION_CALLSIGN", record.myCallsign))
|
||||
if (includeStationCallsign) append(field("STATION_CALLSIGN", record.myCallsign))
|
||||
appendOptional("MODE", record.mode)
|
||||
appendOptional("SUBMODE", record.submode)
|
||||
appendOptional("GRIDSQUARE", record.theirGrid)
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Look4Sat-BA7OPF. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2026 BA7OPF.
|
||||
* Based on Look4Sat by Arty Bishop and contributors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.core.domain.logbook
|
||||
|
||||
import com.rtbishop.look4sat.core.domain.repository.WavelogProblem
|
||||
import com.rtbishop.look4sat.core.domain.repository.WavelogUploadOutcome
|
||||
import com.rtbishop.look4sat.core.domain.repository.WavelogUploadPreview
|
||||
|
||||
/**
|
||||
* The records a Wavelog upload may submit: locally recorded contacts only.
|
||||
*
|
||||
* Pure LoTW-imported confirmation rows never carry [QsoRecord.lotwUploaded] (the ADIF decoder
|
||||
* does not set it), while a locally recorded row keeps its value through a confirmation merge —
|
||||
* [withConfirmation] leaves `lotwUploaded` untouched. Confirmed local rows uploaded to LoTW
|
||||
* from this app therefore stay eligible; imported history stays out of the upload.
|
||||
*/
|
||||
fun wavelogUploadCandidates(records: List<QsoRecord>): List<QsoRecord> = records.filter {
|
||||
it.status == QsoStatus.COMPLETE && !it.wavelogUploaded && (!it.lotwConfirmed || it.lotwUploaded)
|
||||
}
|
||||
|
||||
/** Operator-facing wording for why a record cannot enter a Wavelog upload. */
|
||||
fun WavelogProblem.label(): String = when (this) {
|
||||
WavelogProblem.NOT_CONFIGURED -> "Wavelog upload not configured"
|
||||
WavelogProblem.NO_STATION -> "no station profile selected"
|
||||
WavelogProblem.GRID_MISMATCH -> "logged grid outside the Wavelog station grid"
|
||||
}
|
||||
|
||||
/** One-line summary of the records excluded before a Wavelog upload (empty when none). */
|
||||
fun wavelogSkipSummary(preview: WavelogUploadPreview): String {
|
||||
if (preview.skipped <= 0) return ""
|
||||
val detail = preview.reasons.entries.sortedByDescending { it.value }
|
||||
.joinToString(", ") { (problem, count) -> "$count× ${problem.label()}" }
|
||||
return "${preview.skipped} QSO(s) skipped" + if (detail.isNotBlank()) " — $detail" else ""
|
||||
}
|
||||
|
||||
/** One-line result wording for a finished Wavelog upload. */
|
||||
fun wavelogResultMessage(result: WavelogUploadOutcome): String = when (result) {
|
||||
is WavelogUploadOutcome.Imported -> buildString {
|
||||
if (result.imported == 0 && result.duplicates > 0) {
|
||||
append("All ${result.markIds.size} record(s) already in Wavelog")
|
||||
} else {
|
||||
append("Uploaded ${result.imported} QSO(s) to Wavelog")
|
||||
}
|
||||
if (result.duplicates > 0) append(" — ${result.duplicates} already there")
|
||||
if (result.skipped > 0) append(" — ${result.skipped} skipped by Wavelog")
|
||||
}
|
||||
is WavelogUploadOutcome.Rejected -> result.message
|
||||
is WavelogUploadOutcome.Failed -> result.message
|
||||
WavelogUploadOutcome.Expired -> "Preview expired — tap upload again"
|
||||
}
|
||||
@@ -83,4 +83,20 @@ class AdifCodecTest {
|
||||
assertTrue(record.lotwConfirmed)
|
||||
assertTrue(record.isSatellite)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encodeOmitsStationCallsignForWavelog() {
|
||||
val record = QsoRecord(
|
||||
startUtcMillis = 1_700_000_000_000L,
|
||||
theirCallsign = "N0CALL",
|
||||
myCallsign = "BA7OPF",
|
||||
txFrequencyHz = 145_850_000L,
|
||||
band = "2M",
|
||||
satelliteName = "SO-50"
|
||||
)
|
||||
val adif = AdifCodec.encode(listOf(record), includeStationCallsign = false)
|
||||
assertTrue(!adif.contains("<STATION_CALLSIGN"))
|
||||
assertTrue(adif.contains("<CALL:6>N0CALL"))
|
||||
assertTrue(adif.contains("<SAT_NAME:5>SO-50"))
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Look4Sat-BA7OPF. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2026 BA7OPF.
|
||||
* Based on Look4Sat by Arty Bishop and contributors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
package com.rtbishop.look4sat.core.domain.logbook
|
||||
|
||||
import com.rtbishop.look4sat.core.domain.repository.WavelogProblem
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class WavelogUploadMessagesTest {
|
||||
|
||||
private fun record(
|
||||
complete: Boolean = true,
|
||||
wavelogUploaded: Boolean = false,
|
||||
lotwConfirmed: Boolean = false,
|
||||
lotwUploaded: Boolean = false
|
||||
) = QsoRecord(
|
||||
startUtcMillis = 1_700_000_000_000L,
|
||||
theirCallsign = "BG5JVM",
|
||||
myCallsign = "BA7OPF",
|
||||
status = if (complete) QsoStatus.COMPLETE else QsoStatus.DRAFT,
|
||||
lotwConfirmed = lotwConfirmed,
|
||||
lotwUploaded = lotwUploaded,
|
||||
wavelogUploaded = wavelogUploaded
|
||||
)
|
||||
|
||||
@Test
|
||||
fun freshLocalRecordIsEligible() {
|
||||
assertTrue(wavelogUploadCandidates(listOf(record())).isNotEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun locallyUploadedToLotwStaysEligible() {
|
||||
assertTrue(wavelogUploadCandidates(listOf(record(lotwUploaded = true))).isNotEmpty())
|
||||
assertTrue(
|
||||
wavelogUploadCandidates(listOf(record(lotwConfirmed = true, lotwUploaded = true))).isNotEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun importedConfirmationRowIsExcluded() {
|
||||
assertTrue(wavelogUploadCandidates(listOf(record(lotwConfirmed = true))).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun draftAndAlreadyUploadedRecordsAreExcluded() {
|
||||
assertTrue(wavelogUploadCandidates(listOf(record(complete = false))).isEmpty())
|
||||
assertTrue(wavelogUploadCandidates(listOf(record(wavelogUploaded = true))).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun problemLabelsAreOperatorFacing() {
|
||||
assertEquals("Wavelog upload not configured", WavelogProblem.NOT_CONFIGURED.label())
|
||||
assertEquals("no station profile selected", WavelogProblem.NO_STATION.label())
|
||||
assertEquals("logged grid outside the Wavelog station grid", WavelogProblem.GRID_MISMATCH.label())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user