fix(lotw): MY_CNTY must not sink a non-US station's upload
TQSL validates MY_CNTY against the ADIF secondary-subdivision list, which is the US county enumeration — 'XX,County' with a two-letter state. A Canadian profile produced 'ONTARIO,Kawartha' (the export joined MY_STATE onto the county wholesale) and TQSL refused the whole record. Two layers: adifCounty only prefixes a two-letter state, so exports stop manufacturing the invalid shape; and UploadLoTW scrubs any MY_CNTY that is not the US shape before signing — MY_STATE and MY_GRIDSQUARE already locate the station for LoTW, and the US form survives for the county hunters. Table-tested.
This commit is contained in:
+4
-2
@@ -5,12 +5,14 @@
|
||||
"en": [
|
||||
"Switching the settings database no longer shows “OpsLog is already running”: the automatic relaunch now waits for the closing instance to release its lock instead of racing it.",
|
||||
"HamQTH upload: 8th external service — real-time QSO upload to the HamQTH online logbook with your callbook credentials (auto-upload on log, “Send to…” right-click, QSL Manager backlog upload, connection test).",
|
||||
"Fixed: the right-click “Send to HAMLOG.online” was uploading the selection to QRZ.com with the QRZ key — it now goes to HAMLOG.online."
|
||||
"Fixed: the right-click “Send to HAMLOG.online” was uploading the selection to QRZ.com with the QRZ key — it now goes to HAMLOG.online.",
|
||||
"LoTW: TQSL no longer refuses non-US stations over MY_CNTY — the field is stripped before signing unless it is the US “XX,County” shape LoTW actually validates (a Canadian “ONTARIO,Kawartha” was rejecting the whole record). Exports also stop gluing a full state name onto the county."
|
||||
],
|
||||
"fr": [
|
||||
"Changer de base de réglages n’affiche plus « OpsLog is already running » : la relance automatique attend désormais que l’instance qui se ferme libère son verrou au lieu de la prendre de vitesse.",
|
||||
"Upload HamQTH : 8e service externe — envoi des QSO en temps réel vers le logbook HamQTH avec vos identifiants du lookup (upload auto au log, « Envoyer vers… » au clic droit, rattrapage via le QSL Manager, test de connexion).",
|
||||
"Corrigé : le clic droit « Envoyer vers HAMLOG.online » envoyait la sélection à QRZ.com avec la clé QRZ — elle part maintenant vers HAMLOG.online."
|
||||
"Corrigé : le clic droit « Envoyer vers HAMLOG.online » envoyait la sélection à QRZ.com avec la clé QRZ — elle part maintenant vers HAMLOG.online.",
|
||||
"LoTW : TQSL ne refuse plus les stations hors US à cause de MY_CNTY — le champ est retiré avant signature sauf s’il a la forme US « XX,County » que LoTW valide réellement (un « ONTARIO,Kawartha » canadien rejetait tout l’enregistrement). L’export cesse aussi de coller un nom d’état complet devant le comté."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -443,5 +443,11 @@ func adifCounty(state, county string) string {
|
||||
if c == "" || s == "" || strings.Contains(c, ",") {
|
||||
return c
|
||||
}
|
||||
// The "STATE,County" join is the ADIF secondary-subdivision format, and that
|
||||
// enumeration is a US thing — a two-letter state code. Prefixing a Canadian
|
||||
// "ONTARIO" produced "ONTARIO,Kawartha", which is valid nowhere.
|
||||
if len(s) != 2 {
|
||||
return c
|
||||
}
|
||||
return strings.ToUpper(s) + "," + c
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
@@ -337,7 +339,36 @@ func fileExists(p string) bool {
|
||||
// they were already uploaded OR outside the callsign certificate's date range.
|
||||
// Reporting either as success is how a contact came to be stamped "uploaded"
|
||||
// while LoTW had never seen it.
|
||||
// scrubMyCnty removes MY_CNTY fields TQSL would refuse. LoTW's secondary
|
||||
// subdivisions are the US county enumeration — "XX,County" with a two-letter
|
||||
// state — and TQSL rejects the whole record over anything else, so a Canadian
|
||||
// station's "ONTARIO,Kawartha" (or a bare county) must simply not be sent.
|
||||
// MY_STATE and MY_GRIDSQUARE already locate the station for LoTW.
|
||||
var myCntyRe = regexp.MustCompile(`(?i)<MY_CNTY:([0-9]+)(?::[A-Za-z])?>`)
|
||||
|
||||
func scrubMyCnty(adif string) string {
|
||||
for {
|
||||
loc := myCntyRe.FindStringSubmatchIndex(adif)
|
||||
if loc == nil {
|
||||
return adif
|
||||
}
|
||||
n, _ := strconv.Atoi(adif[loc[2]:loc[3]])
|
||||
end := loc[1] + n
|
||||
if end > len(adif) {
|
||||
end = len(adif)
|
||||
}
|
||||
val := adif[loc[1]:end]
|
||||
if len(val) > 3 && val[2] == ',' {
|
||||
// "XX,..." — the US shape TQSL accepts; leave it for the county hunters.
|
||||
rest := scrubMyCnty(adif[end:])
|
||||
return adif[:end] + rest
|
||||
}
|
||||
adif = adif[:loc[0]] + strings.TrimLeft(adif[end:], " ")
|
||||
}
|
||||
}
|
||||
|
||||
func UploadLoTW(ctx context.Context, cfg ServiceConfig, tempDir, adifRecord string) (UploadResult, error) {
|
||||
adifRecord = scrubMyCnty(adifRecord)
|
||||
tqsl := strings.TrimSpace(cfg.TQSLPath)
|
||||
loc := strings.TrimSpace(cfg.StationLocation)
|
||||
switch {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package extsvc
|
||||
|
||||
import "testing"
|
||||
|
||||
// TQSL refuses whole records over a MY_CNTY it cannot validate, and its
|
||||
// validation is the US "XX,County" enumeration — so anything else must be
|
||||
// stripped before signing, and the US shape must survive untouched.
|
||||
func TestScrubMyCnty(t *testing.T) {
|
||||
cases := []struct{ in, want string }{
|
||||
{"<CALL:5>F4BPO<MY_CNTY:16>ONTARIO,Kawartha<MY_STATE:7>ONTARIO<EOR>",
|
||||
"<CALL:5>F4BPO<MY_STATE:7>ONTARIO<EOR>"},
|
||||
{"<MY_CNTY:8>Kawartha<EOR>", "<EOR>"},
|
||||
{"<MY_CNTY:9>NY,Monroe<EOR>", "<MY_CNTY:9>NY,Monroe<EOR>"},
|
||||
{"<CALL:4>K1AB<EOR>", "<CALL:4>K1AB<EOR>"},
|
||||
{"<MY_CNTY:8>Kawartha<EOR>\n<MY_CNTY:9>NY,Monroe<EOR>",
|
||||
"<EOR>\n<MY_CNTY:9>NY,Monroe<EOR>"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := scrubMyCnty(c.in); got != c.want {
|
||||
t.Errorf("scrubMyCnty(%q) = %q, want %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user