package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os/exec" "path" "regexp" "syscall" "time" "github.com/awesome-gocui/gocui" "github.com/pelletier/go-toml" ) var ( settings *toml.Tree srv_stdin io.WriteCloser srv_cmd *exec.Cmd ) // reload settings (and maybe other things too) func reload() { settings, _ = toml.LoadFile("serverwrapper.toml") } func settings_list(option string) []string { list := settings.Get(option).([]interface{}) str_array := make([]string, len(list)) for i := range list { str_array[i] = list[i].(string) } return str_array } //builds the cmd which is called to run the server func build_cmd() (string, []string) { java_args := []string{"-jar"} jvm_args := settings_list("server.jvm-args") java_args = append(java_args, jvm_args...) forge_path := path.Join(settings.Get("server.directory").(string), settings.Get("server.forge").(string)) java_args = append(java_args, forge_path) jar_args := settings_list("server.jar-args") java_args = append(java_args, jar_args...) return "java", java_args } //run the server-thread func server_run(g *gocui.Gui) { //create the command cmd_str, cmd_args := build_cmd() srv_cmd = exec.Command(cmd_str, cmd_args...) //execute in the server directory srv_cmd.Dir = settings.Get("server.directory").(string) //connect pipes stdout, err := srv_cmd.StdoutPipe() srv_cmd.Stderr = srv_cmd.Stdout srv_stdin, err = srv_cmd.StdinPipe() //this one is global, because we write to it elsewhere //debugging stuff cmd_bytes := []byte(srv_cmd.String()) ioutil.WriteFile("cmd.txt", cmd_bytes, 0644) g.Update(func(g *gocui.Gui) error { v, err := g.View("srv_log") g.SetCurrentView("srv_log") if err != nil { return err } fmt.Fprintln(v, cmd_str) for i := range cmd_args { fmt.Fprintln(v, cmd_args[i]) } g.SetCurrentView("input") return nil }) // run the process err = srv_cmd.Start() if err != nil { log.Panicln("Failed to call server command \"" + srv_cmd.String() + "\"") } //preload the regexes from the config regexes := settings_list("logging.stdout_excludes") lastline := "" //read pipes and write them to the ring buffer buf := bufio.NewScanner(stdout) for { if buf.Scan() { g.Update(func(g *gocui.Gui) error { v, err := g.View("srv_log") if err != nil { return err } line := buf.Text() match := lastline == line lastline = line for i := range regexes { match_line, err := regexp.Match(regexes[i], []byte(line)) if err != nil { v2, _ := g.View("status") fmt.Fprintln(v2, err) } match = match || match_line if match { break } } if !match { fmt.Fprintln(v, buf.Text()) } return nil }) } else { time.Sleep(100 * time.Millisecond) } } } func main() { //initialize settings by "re"loading them from disk reload() //init the CUI gui, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } defer gui.Close() gui.SetManagerFunc(layout) gui.Cursor = false if err := gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { log.Panicln(err) } if err := gui.SetKeybinding("", gocui.KeyCtrlI, gocui.ModNone, procstatus); err != nil { log.Panicln(err) } if err := gui.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, killserver); err != nil { log.Panicln(err) } go server_run(gui) //run the CUI main loop if err := gui.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) } } func inputHandler(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { switch { case ch != 0 && mod == 0: v.EditWrite(ch) case key == gocui.KeyBackspace || key == gocui.KeyBackspace2: v.EditDelete(true) case key == gocui.KeyEnter: s := v.Buffer() srv_stdin.Write([]byte(s)) srv_stdin.Write([]byte("\n")) v.EditDeleteToStartOfLine() } } //create or update the CUI layout func layout(g *gocui.Gui) error { maxX, maxY := g.Size() v_log, err := g.SetView("srv_log", 0, 0, maxX-1, maxY-5, 0) if err != nil { if err != gocui.ErrUnknownView { return err } //*only* put initialization code for the view here v_log.Autoscroll = true v_log.Title = "Server Output" v_log.Overlaps = gocui.BOTTOM } v_status, err := g.SetView("status", 0, maxY-5, maxX-1, maxY-3, 0) if err != nil { if err != gocui.ErrUnknownView { return err } //*only* put initialization code for the view here v_status.Title = "Status" v_status.Overlaps = gocui.TOP | gocui.BOTTOM } v_in, err := g.SetView("input", 0, maxY-3, maxX-1, maxY-1, 0) if err != nil { if err != gocui.ErrUnknownView { return err } //*only* put initialization code for the view here v_in.Title = "Input" v_in.Editable = true v_in.Editor = gocui.EditorFunc(inputHandler) v_in.Overlaps = gocui.TOP } g.SetCurrentView("input") return nil } func killserver(g *gocui.Gui, v *gocui.View) error { v_status, err := g.View("status") if err != nil { return err } v_status.Clear() err = srv_cmd.Process.Signal(syscall.SIGINT) if err != nil { fmt.Fprintf(v_status, fmt.Sprint(err)) } err = srv_cmd.Wait() if err != nil { fmt.Fprintf(v_status, fmt.Sprint(err)) } return nil } func procstatus(g *gocui.Gui, v *gocui.View) error { v_status, err := g.View("status") if err != nil { return err } v_status.Clear() state := srv_cmd.ProcessState if state == nil { fmt.Fprintf(v_status, "Server Process is still running") } else { fmt.Fprintf(v_status, fmt.Sprint(state)) } return nil } func quit(g *gocui.Gui, v *gocui.View) error { v_status, err := g.View("status") if err != nil { return err } if srv_cmd != nil { if srv_cmd.Process != nil { if srv_cmd.ProcessState != nil { if srv_cmd.ProcessState.Exited() { return gocui.ErrQuit } else { v_status.Clear() fmt.Fprintf(v_status, fmt.Sprint(srv_cmd.ProcessState)) } } else { v_status.Clear() fmt.Fprintf(v_status, "Server process still running, sending SIGINT... ") srv_cmd.Process.Signal(syscall.SIGINT) defer srv_cmd.Wait() if err != nil { fmt.Fprintf(v_status, fmt.Sprint(err)) } return nil } } } return gocui.ErrQuit }