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:
Konrad Neitzel
2026-10-03 19:24:00 +02:00
co-authored by Cursor
parent 6233292931
commit ec5f660fdd
12 changed files with 848 additions and 43 deletions
+125
View File
@@ -0,0 +1,125 @@
package station
import (
"fmt"
"os"
"strings"
"time"
)
// Close releases the log file. It is safe to call more than once.
func (s *Station) Close() {
s.mu.Lock()
defer s.mu.Unlock()
s.closeLogLocked()
}
func (s *Station) openLog() {
f, err := os.OpenFile(s.logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
s.logNotice = "Cannot write " + s.logPath
if s.state.Talker == "" {
s.state.Detail = s.logNotice
}
return
}
s.logFile = f
}
func (s *Station) closeLogLocked() {
if s.logFile != nil {
s.logFile.Close()
s.logFile = nil
}
}
func (s *Station) writeLog(ts time.Time, msg string) {
if s.logFile == nil {
return
}
line := ts.Format(time.RFC3339) + " " + msg + "\n"
if _, err := s.logFile.Write([]byte(line)); err != nil {
s.closeLogLocked()
if s.logNotice == "" {
s.logNotice = "Cannot write " + s.logPath
}
}
}
func (s *Station) noteTransmitLocked() {
s.noteTransmitAt(s.clock.Now())
}
func (s *Station) noteTransmitAt(now time.Time) {
tx := s.transmitting()
if tx && !s.sending {
s.sending = true
s.sendStart = now
return
}
if !tx && s.sending {
start := s.sendStart
s.sending = false
s.writeLog(start, fmt.Sprintf("sending for %d seconds.", wholeSeconds(start, now)))
}
}
func (s *Station) closeTalkerLocked(now time.Time) {
if s.talkCall == "" {
return
}
call := s.talkCall
start := s.talkStart
s.talkCall = ""
s.writeLog(start, fmt.Sprintf("%s talked for %d seconds.", call, wholeSeconds(start, now)))
}
func (s *Station) noteDownLocked(now time.Time, dial bool) {
s.closeTalkerLocked(now)
s.state.Connection = "Disconnected"
s.remoteTalker = ""
s.roomBusy = false
s.noteTransmitAt(now)
switch {
case s.up:
s.up = false
s.outage = true
s.writeLog(now, "disconnected")
case dial && !s.outage:
s.outage = true
s.writeLog(now, "disconnected")
}
s.publish()
}
func (s *Station) endFatal(msg string, log bool) {
s.mu.Lock()
defer s.mu.Unlock()
if s.fatal != "" {
return
}
now := s.clock.Now()
s.closeTalkerLocked(now)
s.fatal = msg
s.remoteTalker = ""
s.roomBusy = false
s.noteTransmitAt(now)
if log {
s.up = false
s.outage = true
s.writeLog(now, oneLine(msg))
}
s.publish()
}
func wholeSeconds(start, end time.Time) int {
d := end.Sub(start)
if d <= 0 {
return 0
}
return int(d / time.Second)
}
func oneLine(msg string) string {
return strings.NewReplacer("\r\n", " ", "\n", " ", "\r", " ").Replace(msg)
}