diff --git a/README.md b/README.md index d14d0f6..88e60b9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ A fully native RCON client implementation, zero third parties* -*except for other golang maintained packages, until i fully master tty :( +*except for other golang maintained packages about terminal emulators, until i fully master tty :( ![tcprcon demo](.meta/demo.png) + diff --git a/cmd/terminal.go b/cmd/terminal.go index d972e2a..2dbf4b8 100644 --- a/cmd/terminal.go +++ b/cmd/terminal.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "os" "strconv" "strings" @@ -17,10 +18,14 @@ import ( func runRconTerminal(client *client.RCONClient, ctx context.Context, logLevel uint8) { app := fullterm.CreateApp(fmt.Sprintf("rcon@%v", client.Address)) + // dont worry we are resetting the logger before returning logger.SetupCustomDestination(logLevel, app) - errChan := make(chan error, 1) + + appErrors := make(chan error, 1) + connectionErrors := make(chan error, 1) + appRun := func() { - errChan <- app.Run(ctx) + appErrors <- app.Run(ctx) } packetChannel := packet.CreateResponseChannel(client, ctx) packetReader := func() { @@ -34,15 +39,19 @@ func runRconTerminal(client *client.RCONClient, ctx context.Context, logLevel ui logger.Debug.Println("read deadline reached; connection is idle or server is silent.") continue } + if errors.Is(streamedPacket.Error, io.EOF) { + connectionErrors <- io.EOF + return + } logger.Err.Println(errors.Join(errors.New("error while reading from RCON client"), streamedPacket.Error)) continue } fmt.Fprintf( app, - "(%v): RESPONSE TYPE %v\n%v\n", + "(%v): RCV PKT %v\n%v\n", ansi.Format(strconv.Itoa(int(streamedPacket.Id)), ansi.Green, ansi.Bold), ansi.Format(strconv.Itoa(int(streamedPacket.Type)), ansi.Green, ansi.Bold), - ansi.Format(strings.TrimRight(streamedPacket.BodyStr(), "\n\r")+"\n", ansi.Green), + ansi.Format(strings.TrimRight(streamedPacket.BodyStr(), "\n\r"), ansi.Green), ) } } @@ -55,6 +64,12 @@ func runRconTerminal(client *client.RCONClient, ctx context.Context, logLevel ui return case cmd := <-submissionChan: execPacket := packet.New(client.Id(), packet.SERVERDATA_EXECCOMMAND, []byte(cmd)) + fmt.Fprintf( + app, + "(%v): SND CMD %v\n", + ansi.Format(strconv.Itoa(int(client.Id())), ansi.Green, ansi.Bold), + ansi.Format(cmd, ansi.Blue), + ) client.Write(execPacket.Serialize()) } } @@ -62,17 +77,29 @@ func runRconTerminal(client *client.RCONClient, ctx context.Context, logLevel ui go submissionReader() go packetReader() go appRun() - for { - select { - case <-ctx.Done(): - return - case err := <-errChan: - logger.Setup(logLevel) - logger.Debug.Println("exiting app") - if err != nil { - logger.Critical.Println(err) - } - return + + select { + case <-ctx.Done(): + logger.Debug.Println("context done") + break + case err := <-connectionErrors: + defer func() { + logger.Critical.Println(errors.Join(errors.New("connection error"), err)) + }() + break + case err := <-appErrors: + // lets do this because the app might be unrealiable at this point + if err != nil { + defer func() { + logger.Critical.Println(errors.Join(errors.New("app error"), err)) + }() + } else { + defer func() { + logger.Debug.Println("graceful app exit") + }() } + break } + app.Close() + logger.Setup(logLevel) } diff --git a/internal/fullterm/app.go b/internal/fullterm/app.go index 262a8be..28f0bb3 100644 --- a/internal/fullterm/app.go +++ b/internal/fullterm/app.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "os/signal" + "sync" "syscall" "github.com/UltimateForm/tcprcon/internal/ansi" @@ -22,6 +23,7 @@ type app struct { cmdLine []byte content []string commandSignature string + once sync.Once } func (src *app) Write(bytes []byte) (int, error) { @@ -132,11 +134,13 @@ func (src *app) Run(context context.Context) error { } func (src *app) Close() { - // note: consider closing channels - fmt.Print(ansi.ExitAltScreen) - src.DrawContent(true) - term.Restore(src.fd, src.prevState) - fmt.Println() + src.once.Do(func() { + // note: consider closing channels + fmt.Print(ansi.ExitAltScreen) + src.DrawContent(true) + term.Restore(src.fd, src.prevState) + fmt.Println() + }) } func CreateApp(commandSignature string) *app {