refactor telnet server and sessions into standalone types

main
Nick Dumas 2 weeks ago
parent 9781fb6736
commit b4e7c1c0a8

@ -0,0 +1,56 @@
package main
import (
"context"
"flag"
"io"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
"code.ndumas.com/ndumas/muddy/core"
)
func main() {
var (
level string
logFile string
)
flag.StringVar(&level, "level", "INFO", "Log level: INFO/info, DEBUG/debug, or ERROR/error.")
flag.StringVar(&logFile, "log", "out.log", "Path to log file.")
flag.Parse()
f, err := os.Create(logFile)
if err != nil {
slog.With(
slog.Any("error", err),
).Error("could not open log file")
os.Exit(1)
}
appConfig := core.AppConfig{
Port: ":3001",
LogDest: io.MultiWriter(f, os.Stderr),
LogLevel: level,
TickInterval: time.Millisecond * 30,
}
app := core.New(appConfig)
ctx, cancel := context.WithCancel(context.Background())
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cancel()
}()
app.Run(ctx)
}

@ -1,24 +1,35 @@
package core package core
import ( import (
"bufio"
"context" "context"
"io" "io"
"log/slog" "log/slog"
"os"
"time" "time"
"github.com/EngoEngine/ecs" "github.com/EngoEngine/ecs"
) )
type Printer struct {
}
type AppConfig struct {
LogDest io.Writer
LogLevel string
Port string
TickInterval time.Duration
}
type App struct { type App struct {
Config AppConfig
W *ecs.World W *ecs.World
L *slog.Logger L *slog.Logger
} }
func New(level string, logFile io.Writer) *App { func New(ac AppConfig) *App {
var logLevel slog.Level var logLevel slog.Level
switch level { switch ac.LogLevel {
case "INFO", "info": case "INFO", "info":
logLevel = slog.LevelInfo logLevel = slog.LevelInfo
case "DEBUG", "debug": case "DEBUG", "debug":
@ -28,7 +39,7 @@ func New(level string, logFile io.Writer) *App {
} }
l := slog.New(slog.NewTextHandler( l := slog.New(slog.NewTextHandler(
logFile, &slog.HandlerOptions{ ac.LogDest, &slog.HandlerOptions{
Level: logLevel, Level: logLevel,
}, },
), ),
@ -39,32 +50,23 @@ func New(level string, logFile io.Writer) *App {
return &App{ return &App{
L: l, L: l,
W: world, W: world,
Config: ac,
} }
} }
func (a *App) Run(ctx context.Context) { func (a *App) Run(ctx context.Context) {
go func() { go func() {
t := time.NewTicker(time.Millisecond * 30) t := time.NewTicker(a.Config.TickInterval)
for range t.C { for range t.C {
a.W.Update(1) a.W.Update(1)
} }
}() }()
scanner := bufio.NewScanner(os.Stdin) s := NewServer(ctx, a.L, a.Config.Port)
var line string go s.Start()
for {
select {
case <-ctx.Done():
a.L.Error("cancelled")
return
default:
if scanner.Scan() {
line = scanner.Text()
}
a.L.Info("scanning input")
a.L.With(slog.String("input", line)).Info("received command")
}
}
for session := range s.Sessions {
go session.Start()
}
} }

@ -0,0 +1,94 @@
package core
import (
"bufio"
"context"
"fmt"
"log/slog"
"net"
)
type Server struct {
l *slog.Logger
port string
ctx context.Context
Sessions chan *Session
ln net.Listener
}
func NewServer(ctx context.Context, l *slog.Logger, port string) *Server {
var s Server
// TODO: Check for context cancellation and cleanup
c := make(chan *Session)
s.Sessions = c
ln, err := net.Listen("tcp", port)
if err != nil {
s.l.With(
slog.Any("error", err),
).Error("could not start tcp listener")
return &s
}
s.ctx = ctx
s.ln = ln
s.l = l
s.port = port
return &s
}
func (s *Server) Start() {
s.l.Debug("starting telnet server")
// TODO: Check for context cancellation and cleanup
for {
conn, err := s.ln.Accept()
if err != nil {
s.l.With(
slog.Any("error", err),
).Error("could not accept tcp connection")
}
s.Sessions <- NewSession(s.ctx, conn, Printer{}, s.l)
}
}
type Session struct {
c net.Conn
ctx context.Context
p Printer
l *slog.Logger
userID uint64
}
func NewSession(ctx context.Context, c net.Conn, p Printer, l *slog.Logger) *Session {
return &Session{
ctx: ctx,
l: l,
p: p,
c: c,
}
}
func (s *Session) Start() {
s.l.Debug("starting telnet session")
fmt.Fprint(s.c, "Welcome to Bing Bong.\n")
scanner := bufio.NewScanner(s.c)
var line string
for {
select {
case <-s.ctx.Done():
s.l.Error("cancelled")
return
default:
if scanner.Scan() {
line = scanner.Text()
fmt.Fprintf(s.c, "> %q\n", line)
}
// a.L.Info("scanning input")
s.l.With(slog.String("input", line)).Info("received command")
}
}
}
Loading…
Cancel
Save