hermeticum/client/cmd/main.go

205 lines
5.2 KiB
Go

package main
import (
"context"
"errors"
"flag"
"fmt"
"io"
"log"
"time"
"github.com/rivo/tview"
"github.com/vilmibm/hermeticum/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var (
tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP")
caFile = flag.String("ca_file", "", "The file containing the CA root cert file")
serverAddr = flag.String("addr", "localhost:6666", "The server address in the format of host:port")
serverHostOverride = flag.String("server_host_override", "x.test.example.com", "The server name used to verify the hostname returned by the TLS handshake")
)
func messages(cs *ClientState) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stream, err := cs.Client.Messages(ctx, cs.SessionInfo)
if err != nil {
return err
}
for {
msg, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
cs.AddMessage(msg)
}
return nil
}
type ClientState struct {
App *tview.Application
Client proto.GameWorldClient
SessionInfo *proto.SessionInfo
MaxMessages int
messagesView *tview.TextView
messages []*proto.ClientMessage
}
func (cs *ClientState) AddMessage(msg *proto.ClientMessage) {
// TODO i don't like this function
cs.messages = append(cs.messages, msg)
if len(cs.messages) > cs.MaxMessages {
cs.messages = cs.messages[1 : len(cs.messages)-1]
}
// TODO look into using the SetChangedFunc thing.
cs.App.QueueUpdateDraw(func() {
// TODO trim content of messagesView /or/ see if tview has a buffer size that does it for me. use cs.messages to re-constitute.
fmt.Fprintf(cs.messagesView, "%s: %s\n", msg.GetSpeaker(), msg.GetText())
cs.messagesView.ScrollToEnd()
})
}
func _main() error {
var opts []grpc.DialOption
if *tls {
return errors.New("TODO tls unsupported")
/*
if *caFile == "" {
*caFile = data.Path("x509/ca_cert.pem")
}
creds, err := credentials.NewClientTLSFromFile(*caFile, *serverHostOverride)
if err != nil {
log.Fatalf("Failed to create TLS credentials %v", err)
}
opts = append(opts, grpc.WithTransportCredentials(creds))
*/
} else {
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
conn, err := grpc.Dial(*serverAddr, opts...)
if err != nil {
return fmt.Errorf("fail to dial: %w", err)
}
defer conn.Close()
client := proto.NewGameWorldClient(conn)
// TODO registration and login stuff
app := tview.NewApplication()
// TODO make a NewClientState
cs := &ClientState{
App: app,
SessionInfo: &proto.SessionInfo{},
Client: client,
MaxMessages: 15, // TODO for testing
messages: []*proto.ClientMessage{},
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
pong, err := cs.Client.Ping(ctx, cs.SessionInfo)
if err != nil {
log.Fatalf("%v.Ping -> %v", cs.Client, err)
}
log.Printf("%#v", pong)
pages := tview.NewPages()
pages.AddPage("splash",
tview.NewModal().
AddButtons([]string{"hey. let's go"}).
SetDoneFunc(func(_ int, _ string) {
pages.SwitchToPage("main")
app.ResizeToFullScreen(pages)
}).SetText("h e r m e t i c u m"),
true,
true)
mainPage := tview.NewList().
AddItem("jack in", "connect using an existing account", '1', func() {
pages.SwitchToPage("game")
}).
AddItem("rez a toon", "create a new account", '2', func() {
pages.SwitchToPage("register")
}).
AddItem("open the hood", "client configuration", '3', nil).
AddItem("get outta here", "quit the client", '4', func() {
app.Stop()
})
pages.AddPage("main", mainPage, true, false)
unfi := tview.NewInputField().SetLabel("account name")
pwfi := tview.NewInputField().SetLabel("password").SetMaskCharacter('~')
registerPage := tview.NewForm().AddFormItem(unfi).AddFormItem(pwfi).
SetCancelFunc(func() {
pages.SwitchToPage("main")
})
submitFunc := func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cs.Client.Register(ctx, &proto.AuthInfo{
Username: unfi.GetText(),
Password: pwfi.GetText(),
})
}
registerPage.AddButton("gimme that shit", submitFunc)
registerPage.AddButton("nah get outta here", func() {
pages.SwitchToPage("main")
})
pages.AddPage("register", registerPage, true, false)
msgView := tview.NewTextView().SetScrollable(true).SetWrap(true).SetWordWrap(true)
cs.messagesView = msgView
gamePage := tview.NewGrid().
SetRows(1, 40, 3).
SetColumns(-1, -1).
SetBorders(true).
AddItem(
tview.NewTextView().SetTextAlign(tview.AlignLeft).SetText("h e r m e t i c u m"),
0, 0, 1, 1, 1, 1, false).
AddItem(
tview.NewTextView().SetTextAlign(tview.AlignRight).SetText("TODO server status"),
0, 1, 1, 1, 1, 1, false).
AddItem(
msgView,
1, 0, 1, 1, 10, 20, false).
AddItem(
tview.NewTextView().SetText("TODO detail window"),
1, 1, 1, 1, 10, 10, false).
AddItem(
tview.NewTextView().SetText("TODO input"),
2, 0, 1, 2, 1, 30, false)
pages.AddPage("game", gamePage, true, false)
go messages(cs)
return app.SetRoot(pages, true).SetFocus(pages).Run()
}
func main() {
err := _main()
if err != nil {
log.Fatal(err.Error())
}
}