Append an optional station log and announce the software after login.
A log path that cannot be written leaves the station on the reflector, and the reflector learns which program joined. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+187
-18
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -12,27 +13,34 @@ import (
|
||||
)
|
||||
|
||||
type loaded struct {
|
||||
host string
|
||||
port int
|
||||
callsign string
|
||||
authKey string
|
||||
talkGroup int
|
||||
host string
|
||||
port int
|
||||
callsign string
|
||||
authKey string
|
||||
talkGroup int
|
||||
enableLogging bool
|
||||
logPath string
|
||||
logNotice string
|
||||
}
|
||||
|
||||
type fileShape struct {
|
||||
Host jsontext.Value `json:"host"`
|
||||
Port jsontext.Value `json:"port"`
|
||||
Callsign jsontext.Value `json:"callsign"`
|
||||
AuthKey jsontext.Value `json:"authKey"`
|
||||
TalkGroup jsontext.Value `json:"talkGroup"`
|
||||
Host jsontext.Value `json:"host"`
|
||||
Port jsontext.Value `json:"port"`
|
||||
Callsign jsontext.Value `json:"callsign"`
|
||||
AuthKey jsontext.Value `json:"authKey"`
|
||||
TalkGroup jsontext.Value `json:"talkGroup"`
|
||||
EnableLogging jsontext.Value `json:"enableLogging"`
|
||||
LogFile jsontext.Value `json:"logFile"`
|
||||
}
|
||||
|
||||
type templateFile struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Callsign string `json:"callsign"`
|
||||
AuthKey string `json:"authKey"`
|
||||
TalkGroup int `json:"talkGroup"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Callsign string `json:"callsign"`
|
||||
AuthKey string `json:"authKey"`
|
||||
TalkGroup int `json:"talkGroup"`
|
||||
EnableLogging bool `json:"enableLogging"`
|
||||
LogFile string `json:"logFile"`
|
||||
}
|
||||
|
||||
var callsignPattern = regexp.MustCompile(`^[A-Z0-9][A-Z]{0,2}\d[A-Z0-9]{0,3}[A-Z](?:-[A-Z0-9]{1,3})?$`)
|
||||
@@ -48,11 +56,11 @@ func loadConfig(path string) (loaded, error) {
|
||||
}
|
||||
return loaded{}, fmt.Errorf("could not be read")
|
||||
}
|
||||
return parseConfig(body)
|
||||
return parseConfig(path, body)
|
||||
}
|
||||
|
||||
func writeTemplate(path string) error {
|
||||
body, err := jsonv2.Marshal(templateFile{})
|
||||
body, err := jsonv2.Marshal(templateFile{LogFile: defaultLogPath()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -72,11 +80,36 @@ func writeTemplate(path string) error {
|
||||
return os.Chmod(path, 0o600)
|
||||
}
|
||||
|
||||
func parseConfig(body []byte) (loaded, error) {
|
||||
func parseConfig(path string, body []byte) (loaded, error) {
|
||||
var shape fileShape
|
||||
if err := jsonv2.Unmarshal(body, &shape); err != nil {
|
||||
return loaded{}, fmt.Errorf("could not be read")
|
||||
}
|
||||
if block, notice := blockedLog(shape); block {
|
||||
cfg, err := parseOperational(shape)
|
||||
if err != nil {
|
||||
return loaded{}, err
|
||||
}
|
||||
cfg.logNotice = notice
|
||||
return cfg, nil
|
||||
}
|
||||
if missingLog(shape) {
|
||||
if !operationalKindsOK(shape) {
|
||||
return parseOperational(shape)
|
||||
}
|
||||
shape = withLogDefaults(shape)
|
||||
if err := rewriteConfig(path, shape); err != nil {
|
||||
return loaded{}, fmt.Errorf("could not be read")
|
||||
}
|
||||
}
|
||||
cfg, err := parseOperational(shape)
|
||||
if err != nil {
|
||||
return loaded{}, err
|
||||
}
|
||||
return applyLog(cfg, shape), nil
|
||||
}
|
||||
|
||||
func parseOperational(shape fileShape) (loaded, error) {
|
||||
host, err := stringField("host", shape.Host)
|
||||
if err != nil {
|
||||
return loaded{}, err
|
||||
@@ -153,3 +186,139 @@ func intField(name string, v jsontext.Value, min, max int) (int, error) {
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func defaultLogPath() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(home, "funknetz.log")
|
||||
}
|
||||
|
||||
func present(v jsontext.Value) bool { return len(v) > 0 }
|
||||
|
||||
func isBool(v jsontext.Value) bool {
|
||||
return v.Kind() == jsontext.KindTrue || v.Kind() == jsontext.KindFalse
|
||||
}
|
||||
|
||||
func missingLog(shape fileShape) bool {
|
||||
return !present(shape.EnableLogging) || !present(shape.LogFile)
|
||||
}
|
||||
|
||||
func operationalKindsOK(shape fileShape) bool {
|
||||
if present(shape.Host) && shape.Host.Kind() != jsontext.KindString {
|
||||
return false
|
||||
}
|
||||
if present(shape.Callsign) && shape.Callsign.Kind() != jsontext.KindString {
|
||||
return false
|
||||
}
|
||||
if present(shape.AuthKey) && shape.AuthKey.Kind() != jsontext.KindString {
|
||||
return false
|
||||
}
|
||||
if present(shape.Port) && shape.Port.Kind() != jsontext.KindNumber {
|
||||
return false
|
||||
}
|
||||
if present(shape.TalkGroup) && shape.TalkGroup.Kind() != jsontext.KindNumber {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// blockedLog reports that a log value has the wrong JSON type, so the file
|
||||
// must not be rewritten. notice is empty when logging is not switched on.
|
||||
func blockedLog(shape fileShape) (block bool, notice string) {
|
||||
enBad := present(shape.EnableLogging) && !isBool(shape.EnableLogging)
|
||||
fileBad := present(shape.LogFile) && shape.LogFile.Kind() != jsontext.KindString
|
||||
if !enBad && !fileBad {
|
||||
return false, ""
|
||||
}
|
||||
if enBad {
|
||||
return true, "enableLogging has the wrong type"
|
||||
}
|
||||
if shape.EnableLogging.Kind() == jsontext.KindTrue {
|
||||
return true, "logFile has the wrong type"
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func withLogDefaults(shape fileShape) fileShape {
|
||||
shape.Host = rawOr(shape.Host, `""`)
|
||||
shape.Port = rawOr(shape.Port, "0")
|
||||
shape.Callsign = rawOr(shape.Callsign, `""`)
|
||||
shape.AuthKey = rawOr(shape.AuthKey, `""`)
|
||||
shape.TalkGroup = rawOr(shape.TalkGroup, "0")
|
||||
shape.EnableLogging = rawOr(shape.EnableLogging, "false")
|
||||
if !present(shape.LogFile) {
|
||||
raw, err := jsonv2.Marshal(defaultLogPath())
|
||||
if err != nil {
|
||||
raw = []byte(`""`)
|
||||
}
|
||||
shape.LogFile = jsontext.Value(raw)
|
||||
}
|
||||
return shape
|
||||
}
|
||||
|
||||
func rawOr(v jsontext.Value, fallback string) jsontext.Value {
|
||||
if len(v) == 0 {
|
||||
return jsontext.Value(fallback)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func rewriteConfig(path string, shape fileShape) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := jsonv2.Marshal(shape)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = append(body, '\n')
|
||||
tmp, err := os.CreateTemp(filepath.Dir(path), ".funknetz-*.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpName := tmp.Name()
|
||||
_, werr := tmp.Write(body)
|
||||
cerr := tmp.Chmod(info.Mode().Perm())
|
||||
clerr := tmp.Close()
|
||||
if werr != nil || cerr != nil || clerr != nil {
|
||||
os.Remove(tmpName)
|
||||
if werr != nil {
|
||||
return werr
|
||||
}
|
||||
if cerr != nil {
|
||||
return cerr
|
||||
}
|
||||
return clerr
|
||||
}
|
||||
if err := os.Rename(tmpName, path); err != nil {
|
||||
os.Remove(tmpName)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyLog(cfg loaded, shape fileShape) loaded {
|
||||
if shape.EnableLogging.Kind() != jsontext.KindTrue {
|
||||
return cfg
|
||||
}
|
||||
var path string
|
||||
if err := jsonv2.Unmarshal(shape.LogFile, &path); err != nil {
|
||||
cfg.logNotice = "logFile has the wrong type"
|
||||
return cfg
|
||||
}
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
cfg.logNotice = "Cannot write logFile"
|
||||
return cfg
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
cfg.logNotice = "Cannot write " + path
|
||||
return cfg
|
||||
}
|
||||
cfg.enableLogging = true
|
||||
cfg.logPath = path
|
||||
return cfg
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user