129 lines
3.9 KiB
Go
129 lines
3.9 KiB
Go
// Package udp manages user-defined UDP integrations: inbound listeners
|
|
// (WSJT-X, JTDX, MSHV log events; JTAlert ADIF; N1MM XML; DXHunter call)
|
|
// and outbound emitters (db_updated → notifies Cloudlog/N1MM when HamLog
|
|
// just logged a QSO).
|
|
//
|
|
// One Server per connection row, started/stopped by the Manager when the
|
|
// user enables/disables or edits the row. Multicast support lets multiple
|
|
// apps share the same port without bind conflicts — essential since
|
|
// WSJT-X uses 2237 and several tools already listen there.
|
|
package udp
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
)
|
|
|
|
// Direction is "inbound" (we listen) or "outbound" (we emit).
|
|
type Direction string
|
|
|
|
const (
|
|
Inbound Direction = "inbound"
|
|
Outbound Direction = "outbound"
|
|
)
|
|
|
|
// ServiceType selects the parser/emitter for a connection.
|
|
type ServiceType string
|
|
|
|
const (
|
|
ServiceWSJT ServiceType = "wsjt" // WSJT-X / JTDX / MSHV binary
|
|
ServiceADIF ServiceType = "adif" // text ADIF over UDP
|
|
ServiceN1MM ServiceType = "n1mm" // N1MM Logger+ XML
|
|
ServiceRemoteCall ServiceType = "remote_call" // plain text callsign
|
|
ServiceDBUpdated ServiceType = "db_updated" // outbound ADIF of local QSO
|
|
)
|
|
|
|
// Config is one user-defined UDP connection.
|
|
type Config struct {
|
|
ID int64 `json:"id"`
|
|
Direction Direction `json:"direction"`
|
|
Name string `json:"name"`
|
|
Port int `json:"port"`
|
|
ServiceType ServiceType `json:"service_type"`
|
|
Multicast bool `json:"multicast"`
|
|
MulticastGroup string `json:"multicast_group"`
|
|
DestinationIP string `json:"destination_ip"` // outbound only
|
|
Enabled bool `json:"enabled"`
|
|
SortOrder int `json:"sort_order"`
|
|
}
|
|
|
|
// Repo is the persistence layer for UDP integration rows.
|
|
type Repo struct{ db *sql.DB }
|
|
|
|
func NewRepo(db *sql.DB) *Repo { return &Repo{db: db} }
|
|
|
|
func (r *Repo) List(ctx context.Context) ([]Config, error) {
|
|
rows, err := r.db.QueryContext(ctx, `
|
|
SELECT id, direction, name, port, service_type,
|
|
multicast, multicast_group, destination_ip,
|
|
enabled, sort_order
|
|
FROM integrations_udp
|
|
ORDER BY direction, sort_order, id`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list udp: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
var out []Config
|
|
for rows.Next() {
|
|
var c Config
|
|
var mc, en int
|
|
if err := rows.Scan(&c.ID, &c.Direction, &c.Name, &c.Port, &c.ServiceType,
|
|
&mc, &c.MulticastGroup, &c.DestinationIP, &en, &c.SortOrder); err != nil {
|
|
return nil, err
|
|
}
|
|
c.Multicast = mc != 0
|
|
c.Enabled = en != 0
|
|
out = append(out, c)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (r *Repo) Save(ctx context.Context, c *Config) error {
|
|
if c.Direction != Inbound && c.Direction != Outbound {
|
|
return fmt.Errorf("invalid direction %q", c.Direction)
|
|
}
|
|
if c.Name == "" {
|
|
return fmt.Errorf("name required")
|
|
}
|
|
mc, en := 0, 0
|
|
if c.Multicast {
|
|
mc = 1
|
|
}
|
|
if c.Enabled {
|
|
en = 1
|
|
}
|
|
if c.ID == 0 {
|
|
res, err := r.db.ExecContext(ctx, `
|
|
INSERT INTO integrations_udp(direction, name, port, service_type,
|
|
multicast, multicast_group, destination_ip, enabled, sort_order)
|
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
c.Direction, c.Name, c.Port, c.ServiceType,
|
|
mc, c.MulticastGroup, c.DestinationIP, en, c.SortOrder)
|
|
if err != nil {
|
|
return fmt.Errorf("insert udp: %w", err)
|
|
}
|
|
id, _ := res.LastInsertId()
|
|
c.ID = id
|
|
return nil
|
|
}
|
|
_, err := r.db.ExecContext(ctx, `
|
|
UPDATE integrations_udp SET
|
|
direction = ?, name = ?, port = ?, service_type = ?,
|
|
multicast = ?, multicast_group = ?, destination_ip = ?,
|
|
enabled = ?, sort_order = ?,
|
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
|
WHERE id = ?`,
|
|
c.Direction, c.Name, c.Port, c.ServiceType,
|
|
mc, c.MulticastGroup, c.DestinationIP, en, c.SortOrder, c.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("update udp: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Repo) Delete(ctx context.Context, id int64) error {
|
|
_, err := r.db.ExecContext(ctx, `DELETE FROM integrations_udp WHERE id = ?`, id)
|
|
return err
|
|
}
|