fix: download lotw only for current callsign in case
of mixed logs (tm2q & f4bpo in same log)
This commit is contained in:
@@ -5243,12 +5243,17 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
|
|||||||
switch svc {
|
switch svc {
|
||||||
case extsvc.ServiceLoTW:
|
case extsvc.ServiceLoTW:
|
||||||
sinceDate := resolveSince(keyExtLoTWLastDownload)
|
sinceDate := resolveSince(keyExtLoTWLastDownload)
|
||||||
if sinceDate != "" {
|
ownCall := a.uploadOwnerCall(extsvc.ServiceLoTW)
|
||||||
emit("Downloading LoTW confirmations received since " + sinceDate + "…")
|
callLabel := ownCall
|
||||||
} else {
|
if callLabel == "" {
|
||||||
emit("Downloading all LoTW confirmations…")
|
callLabel = "all callsigns"
|
||||||
}
|
}
|
||||||
adifText, err := extsvc.DownloadLoTWConfirmations(ctx, nil, cfg.LoTW, sinceDate)
|
if sinceDate != "" {
|
||||||
|
emit(fmt.Sprintf("Downloading LoTW confirmations for %s received since %s…", callLabel, sinceDate))
|
||||||
|
} else {
|
||||||
|
emit(fmt.Sprintf("Downloading all LoTW confirmations for %s…", callLabel))
|
||||||
|
}
|
||||||
|
adifText, err := extsvc.DownloadLoTWConfirmations(ctx, nil, cfg.LoTW, sinceDate, ownCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
emit("Download failed: " + err.Error())
|
emit("Download failed: " + err.Error())
|
||||||
done(matched, total)
|
done(matched, total)
|
||||||
@@ -5713,34 +5718,14 @@ func (a *App) stationCallOf(id int64) string {
|
|||||||
return strings.ToUpper(strings.TrimSpace(q.StationCallsign))
|
return strings.ToUpper(strings.TrimSpace(q.StationCallsign))
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeUploadIDs returns the QSO ids to upload to a service at app close,
|
// uploadOwnerCall returns the callsign OpsLog signs/uploads/downloads as for a
|
||||||
// scanning the whole logbook: LoTW matches the configured sent-status set
|
// service in the active profile: the configured Force/owner callsign, else the
|
||||||
// (N/R), QRZ/Club Log return anything not yet "Y". This is what lets an
|
// active profile's callsign. "" when nothing is known (don't scope by call).
|
||||||
// imported ADIF (old QSOs still unsent) flush on close.
|
func (a *App) uploadOwnerCall(svc extsvc.Service) string {
|
||||||
func (a *App) closeUploadIDs(svc extsvc.Service) []int64 {
|
|
||||||
if a.qso == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
col := uploadColumnFor(string(svc))
|
|
||||||
if col == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cfg := a.loadExternalServices()
|
cfg := a.loadExternalServices()
|
||||||
|
|
||||||
// owner is the callsign this logbook signs/uploads as. Each external
|
|
||||||
// logbook belongs to ONE call, so in a mixed-call DB (F4BPO, F4BPO/P, TM2Q)
|
|
||||||
// we must only sweep the QSOs that belong to it — otherwise TM2Q QSOs would
|
|
||||||
// be signed under the F4BPO certificate, or pushed to the wrong QRZ/Club Log
|
|
||||||
// logbook. Prefer the configured force/owner call; fall back to the active
|
|
||||||
// profile's callsign.
|
|
||||||
var statuses []string
|
|
||||||
owner := ""
|
owner := ""
|
||||||
switch svc {
|
switch svc {
|
||||||
case extsvc.ServiceLoTW:
|
case extsvc.ServiceLoTW:
|
||||||
statuses = cfg.LoTW.UploadFlags
|
|
||||||
if len(statuses) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
owner = cfg.LoTW.ForceStationCallsign
|
owner = cfg.LoTW.ForceStationCallsign
|
||||||
case extsvc.ServiceQRZ:
|
case extsvc.ServiceQRZ:
|
||||||
owner = cfg.QRZ.ForceStationCallsign
|
owner = cfg.QRZ.ForceStationCallsign
|
||||||
@@ -5753,6 +5738,40 @@ func (a *App) closeUploadIDs(svc extsvc.Service) []int64 {
|
|||||||
owner = strings.ToUpper(strings.TrimSpace(p.Callsign))
|
owner = strings.ToUpper(strings.TrimSpace(p.Callsign))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return owner
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadCallsign exposes uploadOwnerCall to the UI so the QSL Manager can show
|
||||||
|
// which of the operator's callsigns a download/upload targets in this profile.
|
||||||
|
func (a *App) UploadCallsign(service string) string {
|
||||||
|
return a.uploadOwnerCall(extsvc.Service(service))
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeUploadIDs returns the QSO ids to upload to a service at app close,
|
||||||
|
// scanning the whole logbook: LoTW matches the configured sent-status set
|
||||||
|
// (N/R), QRZ/Club Log return anything not yet "Y". This is what lets an
|
||||||
|
// imported ADIF (old QSOs still unsent) flush on close.
|
||||||
|
func (a *App) closeUploadIDs(svc extsvc.Service) []int64 {
|
||||||
|
if a.qso == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
col := uploadColumnFor(string(svc))
|
||||||
|
if col == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// owner is the callsign this logbook signs/uploads as. Each external
|
||||||
|
// logbook belongs to ONE call, so in a mixed-call DB (F4BPO, F4BPO/P, TM2Q)
|
||||||
|
// we must only sweep the QSOs that belong to it — otherwise TM2Q QSOs would
|
||||||
|
// be signed under the F4BPO certificate, or pushed to the wrong QRZ/Club Log
|
||||||
|
// logbook.
|
||||||
|
var statuses []string
|
||||||
|
if svc == extsvc.ServiceLoTW {
|
||||||
|
statuses = a.loadExternalServices().LoTW.UploadFlags
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
owner := a.uploadOwnerCall(svc)
|
||||||
|
|
||||||
cands, err := a.qso.ListUploadCandidates(a.ctx, col, statuses)
|
cands, err := a.qso.ListUploadCandidates(a.ctx, col, statuses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
|
Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { FindQSOsForUpload, UploadQSOsManual, DownloadConfirmations, SyncPOTAHunterLog, ListQSO, BulkUpdateQSL } from '../../wailsjs/go/main/App';
|
import { FindQSOsForUpload, UploadQSOsManual, DownloadConfirmations, SyncPOTAHunterLog, ListQSO, BulkUpdateQSL, UploadCallsign } from '../../wailsjs/go/main/App';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
||||||
|
|
||||||
@@ -77,6 +77,14 @@ export function fmtQslDate(s?: string): string {
|
|||||||
// and download confirmations, while the rest of the app stays usable.
|
// and download confirmations, while the rest of the app stays usable.
|
||||||
export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => void } = {}) {
|
export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => void } = {}) {
|
||||||
const [service, setService] = useState('lotw');
|
const [service, setService] = useState('lotw');
|
||||||
|
// The callsign this profile signs/uploads/downloads as for the selected
|
||||||
|
// service (Force station callsign, else the profile call). Shown so the user
|
||||||
|
// knows WHICH of their calls a download/upload targets in a mixed-call log.
|
||||||
|
const [uploadCall, setUploadCall] = useState('');
|
||||||
|
useEffect(() => {
|
||||||
|
if (service === 'pota' || service === 'paper') { setUploadCall(''); return; }
|
||||||
|
UploadCallsign(service).then((c) => setUploadCall(c || '')).catch(() => setUploadCall(''));
|
||||||
|
}, [service]);
|
||||||
const [potaSyncing, setPotaSyncing] = useState(false);
|
const [potaSyncing, setPotaSyncing] = useState(false);
|
||||||
const [potaRes, setPotaRes] = useState<POTASync | null>(null);
|
const [potaRes, setPotaRes] = useState<POTASync | null>(null);
|
||||||
const [potaErr, setPotaErr] = useState('');
|
const [potaErr, setPotaErr] = useState('');
|
||||||
@@ -251,6 +259,17 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
|||||||
<SelectContent>{SERVICES.map((s) => <SelectItem key={s.v} value={s.v}>{s.label}</SelectItem>)}</SelectContent>
|
<SelectContent>{SERVICES.map((s) => <SelectItem key={s.v} value={s.v}>{s.label}</SelectItem>)}</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
{uploadCall && (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-[10px] uppercase tracking-wider text-muted-foreground">Callsign</label>
|
||||||
|
<span
|
||||||
|
className="h-8 inline-flex items-center rounded border border-border bg-muted/40 px-2 font-mono text-sm font-semibold"
|
||||||
|
title="Upload/download is scoped to this callsign (Force station callsign, else the active profile's call)"
|
||||||
|
>
|
||||||
|
{uploadCall}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{service === 'pota' ? (
|
{service === 'pota' ? (
|
||||||
<>
|
<>
|
||||||
<Button size="sm" className="h-8" onClick={syncPota} disabled={potaSyncing}>
|
<Button size="sm" className="h-8" onClick={syncPota} disabled={potaSyncing}>
|
||||||
|
|||||||
Vendored
+2
@@ -501,6 +501,8 @@ export function UpdateQSOsFromCty(arg1:Array<number>):Promise<number>;
|
|||||||
|
|
||||||
export function UpdateQSOsFromQRZ(arg1:Array<number>):Promise<number>;
|
export function UpdateQSOsFromQRZ(arg1:Array<number>):Promise<number>;
|
||||||
|
|
||||||
|
export function UploadCallsign(arg1:string):Promise<string>;
|
||||||
|
|
||||||
export function UploadQSOsManual(arg1:string,arg2:Array<number>):Promise<void>;
|
export function UploadQSOsManual(arg1:string,arg2:Array<number>):Promise<void>;
|
||||||
|
|
||||||
export function WinkeyerBackspace():Promise<void>;
|
export function WinkeyerBackspace():Promise<void>;
|
||||||
|
|||||||
@@ -974,6 +974,10 @@ export function UpdateQSOsFromQRZ(arg1) {
|
|||||||
return window['go']['main']['App']['UpdateQSOsFromQRZ'](arg1);
|
return window['go']['main']['App']['UpdateQSOsFromQRZ'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function UploadCallsign(arg1) {
|
||||||
|
return window['go']['main']['App']['UploadCallsign'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function UploadQSOsManual(arg1, arg2) {
|
export function UploadQSOsManual(arg1, arg2) {
|
||||||
return window['go']['main']['App']['UploadQSOsManual'](arg1, arg2);
|
return window['go']['main']['App']['UploadQSOsManual'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ const lotwReportURL = "https://lotw.arrl.org/lotwuser/lotwreport.adi"
|
|||||||
// DownloadLoTWConfirmations fetches confirmed QSOs from LoTW as ADIF text.
|
// DownloadLoTWConfirmations fetches confirmed QSOs from LoTW as ADIF text.
|
||||||
// Uses the LoTW *website* login (Username/Password), not the TQSL cert. When
|
// Uses the LoTW *website* login (Username/Password), not the TQSL cert. When
|
||||||
// since is non-empty (YYYY-MM-DD) only confirmations received since then are
|
// since is non-empty (YYYY-MM-DD) only confirmations received since then are
|
||||||
// returned — used for incremental "Last download" updates.
|
// returned — used for incremental "Last download" updates. When ownCall is
|
||||||
func DownloadLoTWConfirmations(ctx context.Context, client *http.Client, cfg ServiceConfig, since string) (string, error) {
|
// non-empty, only confirmations for that station callsign are returned (an
|
||||||
|
// LoTW account holds every call you operate — F4BPO, F4BPO/P, TM2Q — so this
|
||||||
|
// scopes the pull to the active profile's call).
|
||||||
|
func DownloadLoTWConfirmations(ctx context.Context, client *http.Client, cfg ServiceConfig, since, ownCall string) (string, error) {
|
||||||
user := strings.TrimSpace(cfg.Username)
|
user := strings.TrimSpace(cfg.Username)
|
||||||
if user == "" || cfg.Password == "" {
|
if user == "" || cfg.Password == "" {
|
||||||
return "", fmt.Errorf("lotw: website login (username/password) not set")
|
return "", fmt.Errorf("lotw: website login (username/password) not set")
|
||||||
@@ -33,6 +36,9 @@ func DownloadLoTWConfirmations(ctx context.Context, client *http.Client, cfg Ser
|
|||||||
q.Set("qso_query", "1")
|
q.Set("qso_query", "1")
|
||||||
q.Set("qso_qsl", "yes") // only QSLed (confirmed) records
|
q.Set("qso_qsl", "yes") // only QSLed (confirmed) records
|
||||||
q.Set("qso_qsldetail", "yes") // include QSL_RCVD / QSLRDATE detail
|
q.Set("qso_qsldetail", "yes") // include QSL_RCVD / QSLRDATE detail
|
||||||
|
if c := strings.TrimSpace(ownCall); c != "" {
|
||||||
|
q.Set("qso_owncall", c) // restrict to this station callsign
|
||||||
|
}
|
||||||
if s := strings.TrimSpace(since); s != "" {
|
if s := strings.TrimSpace(since); s != "" {
|
||||||
q.Set("qso_qslsince", s)
|
q.Set("qso_qslsince", s)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user