feat: progress bar for bulk QSO updates from QRZ.com

Selecting the 102 contacts of a freshly imported contest log and updating them
from QRZ.com is 102 network round trips. Nothing moved on screen while it ran,
so the only honest reading was that the application had frozen — and the natural
response to that is to kill it, halfway through.

The backend now emits a progress event per QSO and the frontend shows the same
overlay the ADIF import uses: a bar, the count, and the callsign being queried.

The callsign is reported BEFORE the lookup rather than after. With a slow
provider that is the difference between "waiting on F4BPO" and a name that lags
one QSO behind whatever is actually taking the time — which would be the field
someone stares at while deciding whether it is stuck.

The overlay is cleared by the closing event and again in a finally, so neither
a completed run nor a failed one can leave it on screen.
This commit is contained in:
2026-07-30 17:54:07 +02:00
parent eb271e8f20
commit c931d8a762
4 changed files with 77 additions and 7 deletions
+29 -1
View File
@@ -10262,6 +10262,23 @@ func (a *App) UpdateQSOsFromCty(ids []int64) (int, error) {
return changed, nil
}
// emitBulkProgress reports how far a bulk update has got.
//
// The QRZ pass is the one that needs it: it is a NETWORK round trip per QSO, so
// 102 contacts imported from a contest take a couple of minutes during which
// nothing moved on screen and the only honest reading was "it has frozen". The
// cty.dat and ClubLog passes are local and usually instant, but they report too
// — a progress bar that appears only when things are slow teaches people that
// its absence means trouble.
func (a *App) emitBulkProgress(op string, processed, total int, call string) {
wruntime.EventsEmit(a.ctx, "bulkupdate:progress", map[string]any{
"op": op,
"processed": processed,
"total": total,
"call": call,
})
}
// UpdateQSOsFromQRZ re-queries the callsign database (QRZ.com / HamQTH per
// the configured providers) for each QSO id and overwrites the geographic
// + entity fields (country, continent, DXCC, zones, grid, state, county)
@@ -10272,13 +10289,23 @@ func (a *App) UpdateQSOsFromQRZ(ids []int64) (int, error) {
return 0, fmt.Errorf("not initialized")
}
changed := 0
for _, id := range ids {
total := len(ids)
a.emitBulkProgress("qrz", 0, total, "")
defer a.emitBulkProgress("qrz", total, total, "")
for i, id := range ids {
q, err := a.qso.GetByID(a.ctx, id)
if err != nil || q.Callsign == "" {
a.emitBulkProgress("qrz", i+1, total, "")
continue
}
// Reported BEFORE the lookup, so the callsign on screen is the one being
// waited on rather than the one already finished — with a slow provider
// that is the difference between "working on F4BPO" and a name that
// lags a QSO behind whatever is actually taking the time.
a.emitBulkProgress("qrz", i, total, q.Callsign)
r, err := a.lookup.Lookup(a.ctx, q.Callsign)
if err != nil {
a.emitBulkProgress("qrz", i+1, total, "")
continue
}
if r.Country != "" {
@@ -10330,6 +10357,7 @@ func (a *App) UpdateQSOsFromQRZ(ids []int64) (int, error) {
if err := a.qso.Update(a.ctx, q); err == nil {
changed++
}
a.emitBulkProgress("qrz", i+1, total, "")
}
if changed > 0 {
a.invalidateAwardStats()