fix: added functionality to net control
This commit is contained in:
@@ -405,8 +405,9 @@ type App struct {
|
||||
// session (in-memory only — active stations currently in QSO).
|
||||
netStore *netctl.Store
|
||||
netMu sync.Mutex
|
||||
netOpenID string // id of the currently open net ("" = none)
|
||||
netActive []*netActiveEntry // stations on the air right now, in check-in order
|
||||
netOpenID string // id of the currently open net ("" = none)
|
||||
netActive []*qso.QSO // on-air QSO drafts (transient negative ids), check-in order
|
||||
netSeq int64 // transient-id counter for on-air drafts (decrements: -1, -2, …)
|
||||
|
||||
cwMu sync.Mutex // guards the CW decoder lifecycle
|
||||
cwStop chan struct{} // stops the CW decoder capture loop; nil when off
|
||||
@@ -4284,18 +4285,6 @@ func (a *App) RestartQSORecorder() { a.startQSORecorderIfEnabled() }
|
||||
// the session. The session is RAM-only — closing the app mid-net drops any
|
||||
// active stations that were never logged.
|
||||
|
||||
// netActiveEntry is one station currently on the air in the open net.
|
||||
type netActiveEntry struct {
|
||||
Callsign string `json:"callsign"`
|
||||
Name string `json:"name"`
|
||||
QTH string `json:"qth"`
|
||||
Country string `json:"country"`
|
||||
RSTSent string `json:"rst_sent"`
|
||||
RSTRcvd string `json:"rst_rcvd"`
|
||||
Comment string `json:"comment"`
|
||||
TimeOn time.Time `json:"time_on"`
|
||||
}
|
||||
|
||||
// NetList returns all nets (with rosters), ordered by name.
|
||||
func (a *App) NetList() []netctl.Net {
|
||||
if a.netStore == nil {
|
||||
@@ -4410,97 +4399,26 @@ func (a *App) NetOpenID() string {
|
||||
}
|
||||
|
||||
// NetActiveList returns the stations currently on the air, in check-in order.
|
||||
func (a *App) NetActiveList() []netActiveEntry {
|
||||
// Each is a full QSO *draft* (not yet in the DB) carrying a negative transient
|
||||
// id so the same QSOEditModal as Recent QSOs can edit every field.
|
||||
func (a *App) NetActiveList() []qso.QSO {
|
||||
a.netMu.Lock()
|
||||
defer a.netMu.Unlock()
|
||||
out := make([]netActiveEntry, len(a.netActive))
|
||||
out := make([]qso.QSO, len(a.netActive))
|
||||
for i, e := range a.netActive {
|
||||
out[i] = *e
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// NetActivate puts a station on the air (records time_on, seeds defaults from
|
||||
// the net + roster). No-op if already active. The net must be open.
|
||||
func (a *App) NetActivate(callsign string) (netActiveEntry, error) {
|
||||
call := strings.ToUpper(strings.TrimSpace(callsign))
|
||||
if call == "" {
|
||||
return netActiveEntry{}, fmt.Errorf("callsign required")
|
||||
}
|
||||
a.netMu.Lock()
|
||||
defer a.netMu.Unlock()
|
||||
if a.netOpenID == "" {
|
||||
return netActiveEntry{}, fmt.Errorf("no net open")
|
||||
}
|
||||
for _, e := range a.netActive {
|
||||
if e.Callsign == call {
|
||||
return *e, nil // already on the air
|
||||
}
|
||||
}
|
||||
e := &netActiveEntry{Callsign: call, TimeOn: time.Now().UTC()}
|
||||
if net, ok := a.netStore.Get(a.netOpenID); ok {
|
||||
e.RSTSent, e.RSTRcvd, e.Comment = net.DefaultRSTSent, net.DefaultRSTRcvd, net.DefaultComment
|
||||
for _, st := range net.Stations {
|
||||
if strings.EqualFold(st.Callsign, call) {
|
||||
e.Name, e.QTH, e.Country = st.Name, st.QTH, st.Country
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if e.RSTSent == "" {
|
||||
e.RSTSent = "59"
|
||||
}
|
||||
if e.RSTRcvd == "" {
|
||||
e.RSTRcvd = "59"
|
||||
}
|
||||
a.netActive = append(a.netActive, e)
|
||||
return *e, nil
|
||||
}
|
||||
|
||||
// NetUpdateActive edits the live fields (report/QTH/name/comment) of a station
|
||||
// already on the air. TimeOn is preserved.
|
||||
func (a *App) NetUpdateActive(e netActiveEntry) error {
|
||||
call := strings.ToUpper(strings.TrimSpace(e.Callsign))
|
||||
a.netMu.Lock()
|
||||
defer a.netMu.Unlock()
|
||||
for _, cur := range a.netActive {
|
||||
if cur.Callsign == call {
|
||||
cur.Name, cur.QTH, cur.Country = e.Name, e.QTH, e.Country
|
||||
cur.RSTSent, cur.RSTRcvd, cur.Comment = e.RSTSent, e.RSTRcvd, e.Comment
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("station not active")
|
||||
}
|
||||
|
||||
// NetDeactivate ends a station's QSO: it logs the contact to the active logbook
|
||||
// (live CAT freq/mode, time_on→now) and removes it from the session. Returns
|
||||
// the new QSO id.
|
||||
func (a *App) NetDeactivate(callsign string) (int64, error) {
|
||||
call := strings.ToUpper(strings.TrimSpace(callsign))
|
||||
a.netMu.Lock()
|
||||
var entry *netActiveEntry
|
||||
idx := -1
|
||||
for i, e := range a.netActive {
|
||||
if e.Callsign == call {
|
||||
entry, idx = e, i
|
||||
break
|
||||
}
|
||||
}
|
||||
if entry == nil {
|
||||
a.netMu.Unlock()
|
||||
return 0, fmt.Errorf("station not active")
|
||||
}
|
||||
a.netActive = append(a.netActive[:idx], a.netActive[idx+1:]...)
|
||||
a.netMu.Unlock()
|
||||
|
||||
// Frequency/mode come live from the rig; fall back to the last UI-reported
|
||||
// values when CAT is off.
|
||||
// netLiveFreq returns the rig's live freq/band/mode, falling back to the last
|
||||
// UI-reported values when CAT is off.
|
||||
func (a *App) netLiveFreq() (freq int64, band, mode string) {
|
||||
var st cat.RigState
|
||||
if a.cat != nil {
|
||||
st = a.cat.State()
|
||||
}
|
||||
freq, band, mode := st.FreqHz, st.Band, st.Mode
|
||||
freq, band, mode = st.FreqHz, st.Band, st.Mode
|
||||
if freq == 0 {
|
||||
a.liveActMu.Lock()
|
||||
freq, band, mode = a.liveFreqHz, a.liveBand, a.liveMode
|
||||
@@ -4509,22 +4427,130 @@ func (a *App) NetDeactivate(callsign string) (int64, error) {
|
||||
if band == "" && freq > 0 {
|
||||
band = bandForHz(freq)
|
||||
}
|
||||
q := qso.QSO{
|
||||
Callsign: call,
|
||||
QSODate: entry.TimeOn,
|
||||
QSODateOff: time.Now().UTC(),
|
||||
Band: band,
|
||||
Mode: mode,
|
||||
RSTSent: entry.RSTSent,
|
||||
RSTRcvd: entry.RSTRcvd,
|
||||
Name: entry.Name,
|
||||
QTH: entry.QTH,
|
||||
Comment: entry.Comment,
|
||||
return
|
||||
}
|
||||
|
||||
// NetActivate puts a station on the air: it builds a QSO draft (time_on now,
|
||||
// live freq/mode, defaults + roster info) with a transient negative id and
|
||||
// returns it. No-op (returns the existing draft) if already active.
|
||||
func (a *App) NetActivate(callsign string) (qso.QSO, error) {
|
||||
call := strings.ToUpper(strings.TrimSpace(callsign))
|
||||
if call == "" {
|
||||
return qso.QSO{}, fmt.Errorf("callsign required")
|
||||
}
|
||||
a.netMu.Lock()
|
||||
defer a.netMu.Unlock()
|
||||
if a.netOpenID == "" {
|
||||
return qso.QSO{}, fmt.Errorf("no net open")
|
||||
}
|
||||
for _, e := range a.netActive {
|
||||
if strings.EqualFold(e.Callsign, call) {
|
||||
return *e, nil // already on the air
|
||||
}
|
||||
}
|
||||
a.netSeq--
|
||||
q := &qso.QSO{ID: a.netSeq, Callsign: call, QSODate: time.Now().UTC()}
|
||||
if net, ok := a.netStore.Get(a.netOpenID); ok {
|
||||
q.RSTSent, q.RSTRcvd, q.Comment = net.DefaultRSTSent, net.DefaultRSTRcvd, net.DefaultComment
|
||||
for _, st := range net.Stations {
|
||||
if strings.EqualFold(st.Callsign, call) {
|
||||
q.Name, q.QTH, q.Country = st.Name, st.QTH, st.Country
|
||||
if st.DXCC != 0 {
|
||||
d := st.DXCC
|
||||
q.DXCC = &d
|
||||
}
|
||||
if st.CQ != 0 {
|
||||
c := st.CQ
|
||||
q.CQZ = &c
|
||||
}
|
||||
if st.ITU != 0 {
|
||||
i := st.ITU
|
||||
q.ITUZ = &i
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if q.RSTSent == "" {
|
||||
q.RSTSent = "59"
|
||||
}
|
||||
if q.RSTRcvd == "" {
|
||||
q.RSTRcvd = "59"
|
||||
}
|
||||
freq, band, mode := a.netLiveFreq()
|
||||
q.Band, q.Mode = band, mode
|
||||
if freq > 0 {
|
||||
f := freq
|
||||
q.FreqHz = &f
|
||||
}
|
||||
a.applyDXCCNumber(q) // fill country/dxcc/zones for display
|
||||
a.refineDistrictZones(q)
|
||||
a.netActive = append(a.netActive, q)
|
||||
return *q, nil
|
||||
}
|
||||
|
||||
// NetUpdateActive replaces an on-air QSO draft (matched by its transient id)
|
||||
// with the edited version from the QSOEditModal. Lets the operator change every
|
||||
// field of a station before it's logged.
|
||||
func (a *App) NetUpdateActive(q qso.QSO) error {
|
||||
a.netMu.Lock()
|
||||
defer a.netMu.Unlock()
|
||||
for i, cur := range a.netActive {
|
||||
if cur.ID == q.ID {
|
||||
qq := q
|
||||
a.netActive[i] = &qq
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("station not active")
|
||||
}
|
||||
|
||||
// NetDiscardActive removes an on-air draft (by transient id) WITHOUT logging it
|
||||
// — i.e. cancel a station added by mistake (the modal's Delete button).
|
||||
func (a *App) NetDiscardActive(id int64) error {
|
||||
a.netMu.Lock()
|
||||
defer a.netMu.Unlock()
|
||||
for i, e := range a.netActive {
|
||||
if e.ID == id {
|
||||
a.netActive = append(a.netActive[:i], a.netActive[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NetDeactivate ends a station's QSO (by transient id): it logs the draft to the
|
||||
// active logbook (time_off = now; freq/mode refreshed from the rig only if the
|
||||
// draft still has none, so manual edits are respected) and removes it from the
|
||||
// session. Returns the new QSO id.
|
||||
func (a *App) NetDeactivate(id int64) (int64, error) {
|
||||
a.netMu.Lock()
|
||||
var draft *qso.QSO
|
||||
idx := -1
|
||||
for i, e := range a.netActive {
|
||||
if e.ID == id {
|
||||
draft, idx = e, i
|
||||
break
|
||||
}
|
||||
}
|
||||
if draft == nil {
|
||||
a.netMu.Unlock()
|
||||
return 0, fmt.Errorf("station not active")
|
||||
}
|
||||
a.netActive = append(a.netActive[:idx], a.netActive[idx+1:]...)
|
||||
a.netMu.Unlock()
|
||||
|
||||
q := *draft
|
||||
q.ID = 0 // transient id must not reach the DB (AddQSO inserts a fresh row)
|
||||
q.QSODateOff = time.Now().UTC()
|
||||
if q.FreqHz == nil && q.Band == "" {
|
||||
freq, band, mode := a.netLiveFreq()
|
||||
q.Band, q.Mode = band, mode
|
||||
if freq > 0 {
|
||||
f := freq
|
||||
q.FreqHz = &f
|
||||
}
|
||||
}
|
||||
return a.AddQSO(q)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user