hamnet70/impl/utils/visualizer/main.go
Thomas Kolb 2e91fd7c42 Apply free software+documentation licenses
All code is now licensed under GPLv3+. The documentation is licensed under
CC BY-SA 4.0.

This is now officially free software! \o/
2024-08-23 11:53:40 +02:00

139 lines
2.8 KiB
Go

// 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.
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2024 Simon Ruderich
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() {
if len(os.Args) != 2 && len(os.Args) != 3 {
log.SetFlags(0)
log.Fatalf("usage: %s <fifo> [<json-dup-prefix>]", os.Args[0])
}
var mutex sync.Mutex
listeners := make(map[*chan string]struct{})
go func() {
for {
path := os.Args[1]
var dupPath string
if len(os.Args) == 3 {
dupPath = os.Args[2] + "." + time.Now().Format(time.RFC3339)
}
fh, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
var dupFh *os.File
if dupPath != "" {
dupFh, err = os.Create(dupPath)
if err != nil {
log.Fatal(err)
}
}
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
if dupFh != nil {
_, err = dupFh.WriteString(line)
if err != nil {
log.Fatalf("write %q: %v", dupPath, err)
}
}
// 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)
}
if dupFh != nil {
err = dupFh.Close()
if err != nil {
log.Fatalf("close %q: %v", dupPath, err)
}
}
}
}()
// 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: