feat+fix batch: Station Control dashboard (fixed-width panels, grip-handle drag reorder, amp meters from the Flex stream, compact relays), Ultrabeam element list capped to 3 (READ_BANDS over-read), spot-click on Flex panadapter now sets mode, DAX TX toggle, DVK panel state persists across relaunch, duplicate amp meters cleared on power-cycle, QSO audio recording snapshot taken synchronously at log time (was racing the form-clear cancel), Flex COMP meter max -20->-25 and MIC scale to 0; changelog 0.20.10
This commit is contained in:
@@ -1983,6 +1983,12 @@ func (a *App) AddQSO(q qso.QSO) (id int64, err error) {
|
||||
q.ID = id
|
||||
a.noteWorked(q.Callsign, q.Band, q.Mode) // keep the alert worked-index fresh (in-memory)
|
||||
a.noteLiveQSO() // multi-op: flip this operator back "online" (publishes async)
|
||||
// Snapshot the QSO recording SYNCHRONOUSLY, BEFORE announcing the log: the
|
||||
// qso:logged event clears the entry form, and clearing the callsign cancels
|
||||
// the recorder — so if this ran on the async path below it raced the cancel
|
||||
// and the audio was discarded (recordings silently stopped working). The
|
||||
// snapshot is an in-memory copy; the heavy file encode still runs async.
|
||||
a.saveQSORecording(&q)
|
||||
// Announce the log RIGHT AWAY so the grid/UI refresh at once and the entry
|
||||
// form clears immediately — the operator is not made to wait on the DB.
|
||||
wruntime.EventsEmit(a.ctx, "qso:logged", id)
|
||||
@@ -1994,7 +2000,6 @@ func (a *App) AddQSO(q qso.QSO) (id int64, err error) {
|
||||
qc := q
|
||||
go func() {
|
||||
a.materializeAwardRefs(qc) // fills the award_refs column
|
||||
a.saveQSORecording(&qc)
|
||||
if a.extsvc != nil {
|
||||
a.extsvc.OnQSOLogged(id)
|
||||
}
|
||||
@@ -6226,19 +6231,14 @@ func (a *App) saveQSORecording(q *qso.QSO) {
|
||||
return
|
||||
}
|
||||
|
||||
// Stamp the recording's path on the QSO now, synchronously, so it's set
|
||||
// before the eQSL auto-send reads the QSO (their full-row Updates would
|
||||
// otherwise race and clobber each other's extras).
|
||||
// Stamp the recording's path on the in-memory QSO now (the encode goroutine
|
||||
// reads it). The DB write is deferred into that goroutine so this stays OFF
|
||||
// the critical logging path — saveQSORecording is now called synchronously.
|
||||
if q.ID != 0 {
|
||||
if q.Extras == nil {
|
||||
q.Extras = map[string]string{}
|
||||
}
|
||||
q.Extras["APP_OPSLOG_RECORDING"] = name // in-memory copy for the encode goroutine
|
||||
// Persist ONLY this extras key (targeted) — a full-row Update from this
|
||||
// in-memory copy could revert a column a concurrent post-log action changed.
|
||||
if err := a.qso.SetExtra(a.ctx, q.ID, "APP_OPSLOG_RECORDING", name); err != nil {
|
||||
applog.Printf("qso-rec: store recording path: %v", err)
|
||||
}
|
||||
q.Extras["APP_OPSLOG_RECORDING"] = name
|
||||
}
|
||||
|
||||
// Clone the QSO for the goroutine with its OWN copy of the Extras map: a
|
||||
@@ -6264,6 +6264,13 @@ func (a *App) saveQSORecording(q *qso.QSO) {
|
||||
applog.Printf("qso-rec: save failed: %v", err)
|
||||
return
|
||||
}
|
||||
// Persist the recording path (targeted extras write) now that the file
|
||||
// exists — off the critical path, so a busy MySQL never delays logging.
|
||||
if qc.ID != 0 {
|
||||
if err := a.qso.SetExtra(a.ctx, qc.ID, "APP_OPSLOG_RECORDING", name); err != nil {
|
||||
applog.Printf("qso-rec: store recording path: %v", err)
|
||||
}
|
||||
}
|
||||
applog.Printf("qso-rec: saved %s", path)
|
||||
// Auto-send the recording once the file exists. Gated ONLY on its own
|
||||
// "auto-send recording" toggle (email.auto_send) — NOT the SMTP panel's
|
||||
|
||||
Reference in New Issue
Block a user