fix: a watched callsign is never parked; refuse uploads to unconfigured services

ZD8GB — watch-listed, six streams a period, two of them RR73 — was
refused as "parked" for the rest of the session. Parking answers "it
will not answer, stop wasting the evening on it", which is a fair verdict
about a station the LOG picked out and the wrong one about a station the
OPERATOR named: a DXpedition running a pileup takes more than two series
of calls to get through to, which is exactly why it is on the list. The
rest between series still applies, so it cannot monopolise the
transmitter — it simply never becomes ineligible.

Send to (right-click) now refuses a service with no credentials and says
which are missing. The upload runs on its own goroutine and reports into
the QSL Manager's console, which is not open when the command came from
the QSO list, so an upload to an unconfigured service looked exactly like
one that worked. The toast is also raised only once the backend has
accepted the request, and Cloudlog / Wavelog and HamQTH name themselves
in it.
This commit is contained in:
2026-09-06 13:45:35 +02:00
parent 70ada49776
commit 8b59954ce0
6 changed files with 134 additions and 6 deletions
+48
View File
@@ -11820,10 +11820,58 @@ func (a *App) UploadQSOsManual(service string, ids []int64) error {
return fmt.Errorf("unknown service %q", service)
}
cfg := a.loadExternalServices()
// NOT CONFIGURED IS AN ANSWER, AND IT BELONGS HERE.
//
// The upload runs on its own goroutine and reports through the QSL Manager's
// console, which is not open when the command was given from a right-click in
// the QSO list: an upload to a service with no credentials looked exactly
// like one that worked. Refused synchronously instead, where the caller can
// show it.
if err := uploadConfigured(svc, cfg); err != nil {
return err
}
go a.runManualUpload(svc, ids, cfg)
return nil
}
// uploadConfigured reports whether a service has what it needs to be uploaded
// to at all — the credentials it cannot work without, not a guarantee they are
// correct. The service says whether they are; this says whether to ask.
func uploadConfigured(svc extsvc.Service, cfg extsvc.ExternalServices) error {
has := func(v string) bool { return strings.TrimSpace(v) != "" }
switch svc {
case extsvc.ServiceCloudlog:
if !has(cfg.Cloudlog.URL) || !has(cfg.Cloudlog.APIKey) {
return fmt.Errorf("Cloudlog / Wavelog is not configured — set its URL and API key in Settings → External services")
}
case extsvc.ServiceQRZ:
if !has(cfg.QRZ.APIKey) {
return fmt.Errorf("QRZ.com is not configured — set the logbook API key in Settings → External services")
}
case extsvc.ServiceClublog:
if !has(cfg.Clublog.Email) || !has(cfg.Clublog.Password) || !has(cfg.Clublog.APIKey) {
return fmt.Errorf("Club Log is not configured — set the account email, password and API key in Settings → External services")
}
case extsvc.ServiceHRDLog:
if !has(cfg.HRDLog.Callsign) || !has(cfg.HRDLog.Code) {
return fmt.Errorf("HRDLog.net is not configured — set the callsign and upload code in Settings → External services")
}
case extsvc.ServiceEQSL:
if !has(cfg.EQSL.Username) || !has(cfg.EQSL.Password) {
return fmt.Errorf("eQSL.cc is not configured — set the username and password in Settings → External services")
}
case extsvc.ServiceHamQTH:
if !has(cfg.HamQTH.Username) || !has(cfg.HamQTH.Password) {
return fmt.Errorf("HamQTH is not configured — set the username and password in Settings → External services")
}
case extsvc.ServiceLoTW:
if !has(cfg.LoTW.StationLocation) {
return fmt.Errorf("LoTW is not configured — set the TQSL station location in Settings → External services")
}
}
return nil
}
func (a *App) runManualUpload(svc extsvc.Service, ids []int64, cfg extsvc.ExternalServices) {
emit := func(line string) {
if a.ctx != nil {