fix: while connected to MySQL if internet was lost no qso would be logged anymore

This commit is contained in:
2026-07-12 18:01:03 +02:00
parent 7398261c50
commit a00817b93e
16 changed files with 850 additions and 9 deletions
+39
View File
@@ -42,6 +42,7 @@ import (
"hamlog/internal/operating"
"hamlog/internal/pota"
"hamlog/internal/lotwusers"
"hamlog/internal/offlineq"
"hamlog/internal/powergenius"
"hamlog/internal/profile"
"hamlog/internal/qslcard"
@@ -456,6 +457,9 @@ type App struct {
logDb *sql.DB // QSO logbook connection — MySQL when the shared backend is enabled, else == db (local SQLite)
dbBackend string // "sqlite" | "mysql" — the logbook backend actually opened at startup
dbBackendErr string // non-empty when a configured MySQL backend failed and we fell back to SQLite
offlineQ *offlineq.Queue // ADIF outbox: QSOs logged while the DB was unreachable
offlineMode bool // last write failed because the DB was unreachable
catFlexSpots bool // push cluster spots to the FlexRadio panadapter
catFlexDecodeSpots bool // push WSJT-X decodes (heard stations) to the panadapter
catFlexDecodeSecs int // decode spot display duration (seconds)
@@ -772,6 +776,25 @@ func (a *App) startup(ctx context.Context) {
}
fmt.Println("OpsLog: cty.dat loaded —", a.dxcc.Info().Entities, "entities")
}()
// Offline safety net: with the shared MySQL logbook, losing the network would
// otherwise mean you simply cannot log. Instead, a QSO that can't reach the DB
// is parked in a local ADIF outbox and replayed automatically once the DB
// answers again. Anything already waiting from a previous session (a crash, a
// quit while offline) is replayed at startup.
a.offlineQ = newOfflineQueue(dataDir)
if n := a.offlineQ.Count(); n > 0 {
a.offlineMode = true
applog.Printf("offline: %d QSO(s) waiting in %s", n, a.offlineQ.Path())
}
go a.offlineReplayLoop()
go func() {
if a.offlineQ.Count() > 0 {
if n, err := a.replayOfflineQueue(); err == nil && n > 0 {
applog.Printf("offline: replayed %d QSO(s) left over from a previous session", n)
}
}
}()
// ClubLog Country File (cty.xml) — date-ranged callsign exceptions that
// cty.dat lacks (DXpeditions). Loaded from cache if present; downloaded on
// demand. Resolution applied only when the user enables it.
@@ -1711,6 +1734,17 @@ func (a *App) AddQSO(q qso.QSO) (id int64, err error) {
}
}
id, err = a.qso.Add(a.ctx, q)
if err != nil && db.IsConnLost(err) {
// The database is UNREACHABLE (not a data error) — park the QSO in the
// offline outbox rather than lose it. Returns id = -1 so the UI can say
// "saved, waiting to sync" instead of showing a failure. The post-save
// side effects (recording, uploads, UDP) are skipped: they need a real
// row id and will not be replayed — the operator can upload later.
if a.queueOffline(q, err) {
return -1, nil
}
// Couldn't even write the outbox → report the real error, never pretend.
}
if err == nil {
q.ID = id
a.saveQSORecording(&q)
@@ -1722,6 +1756,11 @@ func (a *App) AddQSO(q qso.QSO) (id int64, err error) {
if a.udp != nil {
go a.udp.EmitLoggedADIF(adif.SingleRecordADIF(q))
}
// A successful write means the link is healthy again — if QSOs are still
// parked, get them in now rather than waiting for the next tick.
if a.offlineMode && a.offlineQ != nil && a.offlineQ.Count() > 0 {
go func() { _, _ = a.replayOfflineQueue() }()
}
}
return id, err
}