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:
2026-08-27 23:15:54 +02:00
parent cff590fe8a
commit 9270b227b0
2 changed files with 21 additions and 6 deletions
+17 -4
View File
@@ -41,23 +41,36 @@ func DownloadLoTWConfirmations(ctx context.Context, client *http.Client, cfg Ser
if c := strings.TrimSpace(ownCall); c != "" {
q.Set("qso_owncall", c) // restrict to this station callsign
}
if s := strings.TrimSpace(since); s != "" {
q.Set("qso_qslsince", s)
// qso_qslsince is ALWAYS sent, even for "everything".
//
// 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)
if err != nil {
return "", fmt.Errorf("lotw: build request: %w", err)
}
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)
if err != nil {
return "", fmt.Errorf("lotw: request failed: %w", err)
}
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 {
return "", fmt.Errorf("lotw: read response: %w", err)
}