fix(lotw): send a date even for 'all', and give the report time to arrive
Without qso_qslsince LoTW does not answer 'every confirmation' — it answers with a few recent ones, as a 200 and a valid ADIF. 'All' therefore downloaded a nearly empty report and said 'matched 1 of 1' on an account holding twelve thousand. It now asks from 1945-11-15, older than any QSO LoTW accepts. And the request had two minutes: LoTW spends several of them building a full account before the first byte, so asking for everything ended in 'context deadline exceeded while reading body' — a network-shaped message for a too-short deadline. Twenty minutes, and the read cap raised past the 18 MB such a report actually weighs.
This commit is contained in:
+4
-2
@@ -10,7 +10,8 @@
|
|||||||
"Cluster: a SOTA column, read from the summit reference the SOTA feeds put in the spot comment. Clicking the spot fills the QSO’s SOTA award reference, as a POTA spot already did. Turn the column on in Columns.",
|
"Cluster: a SOTA column, read from the summit reference the SOTA feeds put in the spot comment. Clicking the spot fills the QSO’s SOTA award reference, as a POTA spot already did. Turn the column on in Columns.",
|
||||||
"Awards: a \"Slots to confirm\" filter and a running count beside the reference total, so the gap between worked and confirmed band-slots — the Challenge difference — can be seen reference by reference instead of only as two numbers.",
|
"Awards: a \"Slots to confirm\" filter and a running count beside the reference total, so the gap between worked and confirmed band-slots — the Challenge difference — can be seen reference by reference instead of only as two numbers.",
|
||||||
"QSL Manager: a QRZ button next to the Paper QSL search, opening the callsign on QRZ.com.",
|
"QSL Manager: a QRZ button next to the Paper QSL search, opening the callsign on QRZ.com.",
|
||||||
"LoTW: an \"All my callsigns\" option beside the download. The download is scoped to the profile’s own callsign, so a QSO made as a portable or contest call was confirmed at LoTW and never marked here. Confirmations made under a callsign this logbook has never used are skipped, and a suspiciously small report now shows what LoTW actually answered."
|
"LoTW: an \"All my callsigns\" option beside the download. The download is scoped to the profile’s own callsign, so a QSO made as a portable or contest call was confirmed at LoTW and never marked here. Confirmations made under a callsign this logbook has never used are skipped, and a suspiciously small report now shows what LoTW actually answered.",
|
||||||
|
"LoTW download: \"All\" really means all — without a date LoTW answered with a handful of recent confirmations, which looked like a successful download of an empty account. A full account also has time to arrive: the two-minute limit that ended in \"context deadline exceeded\" is now twenty."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"Chaque radio porte son propre MY_RIG (Réglages → CAT), inscrit sur chaque QSO fait avec elle — avant la station par bande des Conditions de trafic, qui dit ce qui était prévu et non quelle radio émet. Laissé vide, rien ne change.",
|
"Chaque radio porte son propre MY_RIG (Réglages → CAT), inscrit sur chaque QSO fait avec elle — avant la station par bande des Conditions de trafic, qui dit ce qui était prévu et non quelle radio émet. Laissé vide, rien ne change.",
|
||||||
@@ -20,7 +21,8 @@
|
|||||||
"Cluster : une colonne SOTA, lue dans la référence de sommet que les flux SOTA mettent dans le commentaire du spot. Cliquer le spot remplit la référence SOTA du QSO, comme le faisait déjà un spot POTA. Colonne à activer dans Colonnes.",
|
"Cluster : une colonne SOTA, lue dans la référence de sommet que les flux SOTA mettent dans le commentaire du spot. Cliquer le spot remplit la référence SOTA du QSO, comme le faisait déjà un spot POTA. Colonne à activer dans Colonnes.",
|
||||||
"Awards : un filtre « Slots à confirmer » et un compteur à côté du total de références, pour voir l'écart entre créneaux contactés et confirmés — la différence du Challenge — référence par référence et non plus seulement en deux chiffres.",
|
"Awards : un filtre « Slots à confirmer » et un compteur à côté du total de références, pour voir l'écart entre créneaux contactés et confirmés — la différence du Challenge — référence par référence et non plus seulement en deux chiffres.",
|
||||||
"Gestionnaire QSL : un bouton QRZ à côté de la recherche QSL papier, qui ouvre l'indicatif sur QRZ.com.",
|
"Gestionnaire QSL : un bouton QRZ à côté de la recherche QSL papier, qui ouvre l'indicatif sur QRZ.com.",
|
||||||
"LoTW : une option « Tous mes indicatifs » à côté du téléchargement. Celui-ci est limité à l'indicatif du profil, si bien qu'un QSO fait sous un indicatif portable ou de contest était confirmé chez LoTW sans jamais être marqué ici. Les confirmations faites sous un indicatif que ce carnet n'a jamais utilisé sont ignorées, et un rapport anormalement petit affiche désormais ce que LoTW a réellement répondu."
|
"LoTW : une option « Tous mes indicatifs » à côté du téléchargement. Celui-ci est limité à l'indicatif du profil, si bien qu'un QSO fait sous un indicatif portable ou de contest était confirmé chez LoTW sans jamais être marqué ici. Les confirmations faites sous un indicatif que ce carnet n'a jamais utilisé sont ignorées, et un rapport anormalement petit affiche désormais ce que LoTW a réellement répondu.",
|
||||||
|
"Téléchargement LoTW : « Tout » veut enfin dire tout — sans date, LoTW ne renvoyait qu'une poignée de confirmations récentes, ce qui ressemblait à un téléchargement réussi d'un compte vide. Un compte complet a aussi le temps d'arriver : la limite de deux minutes, qui finissait en « context deadline exceeded », passe à vingt."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+17
-4
@@ -41,23 +41,36 @@ func DownloadLoTWConfirmations(ctx context.Context, client *http.Client, cfg Ser
|
|||||||
if c := strings.TrimSpace(ownCall); c != "" {
|
if c := strings.TrimSpace(ownCall); c != "" {
|
||||||
q.Set("qso_owncall", c) // restrict to this station callsign
|
q.Set("qso_owncall", c) // restrict to this station callsign
|
||||||
}
|
}
|
||||||
if s := strings.TrimSpace(since); s != "" {
|
// qso_qslsince is ALWAYS sent, even for "everything".
|
||||||
q.Set("qso_qslsince", s)
|
//
|
||||||
|
// Left out, LoTW does not answer "all confirmations" — it answers with a
|
||||||
|
// handful of recent ones, which arrives as a 200 and a valid ADIF and reads
|
||||||
|
// as a successful download of a nearly empty account. Asking from a date
|
||||||
|
// older than the service itself is the only way to mean "all".
|
||||||
|
sinceDate := strings.TrimSpace(since)
|
||||||
|
if sinceDate == "" {
|
||||||
|
sinceDate = "1945-11-15" // older than any QSO LoTW will accept
|
||||||
}
|
}
|
||||||
|
q.Set("qso_qslsince", sinceDate)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, lotwReportURL+"?"+q.Encode(), nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, lotwReportURL+"?"+q.Encode(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("lotw: build request: %w", err)
|
return "", fmt.Errorf("lotw: build request: %w", err)
|
||||||
}
|
}
|
||||||
if client == nil {
|
if client == nil {
|
||||||
client = &http.Client{Timeout: 120 * time.Second}
|
// A full account is tens of megabytes and LoTW builds it slowly — several
|
||||||
|
// minutes for a log of 30 000 QSOs, all of it before the first byte. The
|
||||||
|
// old two-minute limit turned that into "context deadline exceeded while
|
||||||
|
// reading body", which reads as a network fault rather than as "ask for
|
||||||
|
// less at a time".
|
||||||
|
client = &http.Client{Timeout: 20 * time.Minute}
|
||||||
}
|
}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("lotw: request failed: %w", err)
|
return "", fmt.Errorf("lotw: request failed: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 32*1024*1024))
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 256*1024*1024))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("lotw: read response: %w", err)
|
return "", fmt.Errorf("lotw: read response: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user