91 lines
3.3 KiB
Go
91 lines
3.3 KiB
Go
// Package extsvc uploads logged QSOs to external logbook services
|
||
// (QRZ.com first; Clublog and LoTW to follow). Each service has its own
|
||
// credentials and an upload mode chosen per-service: "immediate" pushes as
|
||
// soon as the QSO is saved, "delayed" waits a random 1–2 minutes (like
|
||
// Log4OM) so a mistakenly-logged QSO can still be edited or removed before
|
||
// it leaves.
|
||
//
|
||
// The Manager is intentionally fire-and-forget: a failed or skipped upload
|
||
// just leaves the QSO's per-service upload-status column empty, and the
|
||
// (future) manual-upload window will let the user retry the backlog.
|
||
package extsvc
|
||
|
||
import (
|
||
"errors"
|
||
"strings"
|
||
)
|
||
|
||
// errFromResult turns a non-OK result with no transport error into one
|
||
// (defensive — uploaders normally return an error alongside !OK).
|
||
func errFromResult(r UploadResult) error {
|
||
if r.Message != "" {
|
||
return errors.New(r.Message)
|
||
}
|
||
return errors.New("upload rejected")
|
||
}
|
||
|
||
// Service identifies one external logbook.
|
||
type Service string
|
||
|
||
const (
|
||
ServiceQRZ Service = "qrz" // QRZ.com Logbook
|
||
ServiceClublog Service = "clublog" // Club Log real-time upload
|
||
// ServiceLoTW to come.
|
||
)
|
||
|
||
// UploadMode selects when an auto-upload fires after a QSO is saved.
|
||
type UploadMode string
|
||
|
||
const (
|
||
// ModeImmediate uploads as soon as the QSO is logged.
|
||
ModeImmediate UploadMode = "immediate"
|
||
// ModeDelayed waits a random 1–2 minutes before uploading.
|
||
ModeDelayed UploadMode = "delayed"
|
||
)
|
||
|
||
// ServiceConfig is the per-service user configuration. It's a superset of
|
||
// the credential shapes the different services need — each service reads
|
||
// only the fields it uses:
|
||
//
|
||
// QRZ.com → APIKey, ForceStationCallsign
|
||
// Club Log → Email, Password, Callsign, APIKey
|
||
//
|
||
// AutoUpload + UploadMode are common to all (timing is per-service, so the
|
||
// user can run e.g. Club Log immediate and QRZ delayed).
|
||
type ServiceConfig struct {
|
||
APIKey string `json:"api_key"`
|
||
Email string `json:"email"` // Club Log account email
|
||
Password string `json:"password"` // Club Log account password
|
||
Callsign string `json:"callsign"` // Club Log logbook (owner) callsign
|
||
ForceStationCallsign string `json:"force_station_callsign"` // QRZ
|
||
AutoUpload bool `json:"auto_upload"`
|
||
UploadMode UploadMode `json:"upload_mode"`
|
||
}
|
||
|
||
// normalised returns the config with whitespace trimmed and a valid upload
|
||
// mode (defaults to immediate).
|
||
func (c ServiceConfig) normalised() ServiceConfig {
|
||
c.APIKey = strings.TrimSpace(c.APIKey)
|
||
c.Email = strings.TrimSpace(c.Email)
|
||
c.Callsign = strings.ToUpper(strings.TrimSpace(c.Callsign))
|
||
c.ForceStationCallsign = strings.ToUpper(strings.TrimSpace(c.ForceStationCallsign))
|
||
if c.UploadMode != ModeDelayed {
|
||
c.UploadMode = ModeImmediate
|
||
}
|
||
return c
|
||
}
|
||
|
||
// ExternalServices bundles every service's config for the settings UI.
|
||
// LoTW fields will be added as that service lands.
|
||
type ExternalServices struct {
|
||
QRZ ServiceConfig `json:"qrz"`
|
||
Clublog ServiceConfig `json:"clublog"`
|
||
}
|
||
|
||
// UploadResult is the outcome of a single upload attempt.
|
||
type UploadResult struct {
|
||
OK bool // the service accepted (or already had) the QSO
|
||
LogID string // service-assigned record id, when provided
|
||
Message string // human-readable detail (reason on failure)
|
||
}
|