2024-05-11 10:07:44 +02:00
|
|
|
// Visualize debug information from hamnet70.
|
|
|
|
//
|
|
|
|
// This program is a HTTP server which reads JSON messages (one per line) from
|
|
|
|
// a FIFO filled by hamnet70 and passes them to the browser via WebSocket.
|
|
|
|
// Concurrent browser sessions and reconnects are supported. This binary is
|
|
|
|
// standalone and can be used without the static/ directory.
|
|
|
|
|
2024-08-23 11:53:40 +02:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// Copyright (C) 2024 Simon Ruderich
|
|
|
|
|
2024-05-11 10:07:44 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"embed"
|
|
|
|
"io/fs"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/net/websocket"
|
|
|
|
)
|
|
|
|
|
|
|
|
//go:embed static/*
|
|
|
|
var static embed.FS
|
|
|
|
|
|
|
|
func main() {
|
2024-05-28 08:59:06 +02:00
|
|
|
if len(os.Args) != 2 && len(os.Args) != 3 {
|
2024-05-11 10:07:44 +02:00
|
|
|
log.SetFlags(0)
|
2024-05-28 08:59:06 +02:00
|
|
|
log.Fatalf("usage: %s <fifo> [<json-dup-prefix>]", os.Args[0])
|
2024-05-11 10:07:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var mutex sync.Mutex
|
|
|
|
listeners := make(map[*chan string]struct{})
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
path := os.Args[1]
|
2024-05-28 08:59:06 +02:00
|
|
|
var dupPath string
|
|
|
|
if len(os.Args) == 3 {
|
|
|
|
dupPath = os.Args[2] + "." + time.Now().Format(time.RFC3339)
|
|
|
|
}
|
2024-05-11 10:07:44 +02:00
|
|
|
|
|
|
|
fh, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2024-05-28 08:59:06 +02:00
|
|
|
var dupFh *os.File
|
|
|
|
if dupPath != "" {
|
|
|
|
dupFh, err = os.Create(dupPath)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2024-05-11 10:07:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
r := bufio.NewReader(fh)
|
|
|
|
for {
|
|
|
|
line, err := r.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("read %q: %v", path, err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Duplicate JSON input to a file so it can be replayed later
|
|
|
|
// if necessary
|
2024-05-28 08:59:06 +02:00
|
|
|
if dupFh != nil {
|
|
|
|
_, err = dupFh.WriteString(line)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("write %q: %v", dupPath, err)
|
|
|
|
}
|
2024-05-11 10:07:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send to all listeners
|
|
|
|
mutex.Lock()
|
|
|
|
for x := range listeners {
|
|
|
|
*x <- line
|
|
|
|
}
|
|
|
|
mutex.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
err = fh.Close()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("close %q: %v", path, err)
|
|
|
|
}
|
2024-05-28 08:59:06 +02:00
|
|
|
if dupFh != nil {
|
|
|
|
err = dupFh.Close()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("close %q: %v", dupPath, err)
|
|
|
|
}
|
2024-05-11 10:07:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Use existing "./static/" directory for quick changes
|
|
|
|
var staticFS http.FileSystem
|
|
|
|
_, err := os.Stat("static")
|
|
|
|
if err == nil {
|
|
|
|
staticFS = http.Dir("static")
|
|
|
|
} else {
|
|
|
|
x, err := fs.Sub(static, "static")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
staticFS = http.FS(x)
|
|
|
|
}
|
|
|
|
http.Handle("/", http.FileServer(staticFS))
|
|
|
|
|
|
|
|
http.Handle("/events", websocket.Handler(func(conn *websocket.Conn) {
|
|
|
|
// Register new listener for this connection
|
|
|
|
ch := make(chan string, 512) // buffer up to x number of messages
|
|
|
|
mutex.Lock()
|
|
|
|
listeners[&ch] = struct{}{}
|
|
|
|
mutex.Unlock()
|
|
|
|
defer func() {
|
|
|
|
mutex.Lock()
|
|
|
|
delete(listeners, &ch)
|
|
|
|
mutex.Unlock()
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
|
|
|
x := <-ch
|
|
|
|
err := websocket.Message.Send(conn, x)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Websocket error: %v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
|
|
|
|
err = http.ListenAndServe("localhost:8080", nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// vi: set noet ts=4 sw=4 sts=4:
|