Repos / gorts / 2c01184f92
commit 2c01184f92f946a725119f6eb3263321faaf7a8e
Author: Nhân <hi@imnhan.com>
Date: Wed Jun 21 20:58:51 2023 +0700
migrate to netstring IPC
TODO: stop embedding tcl scripts into go binary
diff --git a/main.go b/main.go
index 9b582cc..2dd7465 100644
--- a/main.go
+++ b/main.go
@@ -3,7 +3,6 @@
import (
"bufio"
_ "embed"
- "encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -14,9 +13,9 @@
"os/exec"
"runtime"
"strconv"
- "strings"
"time"
+ "go.imnhan.com/gorts/netstring"
"go.imnhan.com/gorts/players"
"go.imnhan.com/gorts/startgg"
)
@@ -94,6 +93,7 @@ func startGUI() {
fmt.Fprintln(stdin, "initialize")
scanner := bufio.NewScanner(stdout)
+ scanner.Split(netstring.SplitFunc)
next := func() string {
scanner.Scan()
@@ -102,90 +102,97 @@ func startGUI() {
return v
}
- respond := func(s string) {
+ respondOld := func(s string) {
debug := "<-- " + s
if len(debug) > 35 {
debug = debug[:35] + "[...]"
}
println(debug)
- io.WriteString(stdin, s+"\n")
+ io.WriteString(stdin, netstring.Encode(s))
+ }
+
+ respond := func(ss ...string) {
+ debug := fmt.Sprintf("<-- %v", ss)
+ if len(debug) > 35 {
+ debug = debug[:35] + "[...]"
+ }
+ println(debug)
+ payload := netstring.EncodeN(ss...)
+ io.WriteString(stdin, payload)
}
for scanner.Scan() {
- req := scanner.Text()
- println("--> " + req)
- switch req {
- case "readscoreboard":
+ req := netstring.DecodeMultiple(scanner.Text())
+ fmt.Printf("--> %v\n", req)
+ switch req[0] {
+ case "geticon":
+ respond(string(gortsPngIcon))
+
+ case "getstartgg":
+ respond(startggInputs.Token, startggInputs.Slug)
+
+ case "getwebport":
+ respond(WebPort)
+
+ case "getcountrycodes":
+ respond(startgg.CountryCodes...)
+
+ case "getscoreboard":
// TODO: there must be a more... civilized way.
- respond(scoreboard.Description)
- respond(scoreboard.Subtitle)
- respond(scoreboard.P1name)
- respond(scoreboard.P1country)
- respond(strconv.Itoa(scoreboard.P1score))
- respond(scoreboard.P1team)
- respond(scoreboard.P2name)
- respond(scoreboard.P2country)
- respond(strconv.Itoa(scoreboard.P2score))
- respond(scoreboard.P2team)
+ respond(
+ scoreboard.Description,
+ scoreboard.Subtitle,
+ scoreboard.P1name,
+ scoreboard.P1country,
+ strconv.Itoa(scoreboard.P1score),
+ scoreboard.P1team,
+ scoreboard.P2name,
+ scoreboard.P2country,
+ strconv.Itoa(scoreboard.P2score),
+ scoreboard.P2team,
+ )
case "applyscoreboard":
- scoreboard.Description = next()
- scoreboard.Subtitle = next()
- scoreboard.P1name = next()
- scoreboard.P1country = next()
- scoreboard.P1score, _ = strconv.Atoi(next())
- scoreboard.P1team = next()
- scoreboard.P2name = next()
- scoreboard.P2country = next()
- scoreboard.P2score, _ = strconv.Atoi(next())
- scoreboard.P2team = next()
+ sb := req[1:]
+ scoreboard.Description = sb[0]
+ scoreboard.Subtitle = sb[1]
+ scoreboard.P1name = sb[2]
+ scoreboard.P1country = sb[3]
+ scoreboard.P1score, _ = strconv.Atoi(sb[4])
+ scoreboard.P1team = sb[5]
+ scoreboard.P2name = sb[6]
+ scoreboard.P2country = sb[7]
+ scoreboard.P2score, _ = strconv.Atoi(sb[8])
+ scoreboard.P2team = sb[9]
scoreboard.Write()
-
- case "readplayernames":
- for _, player := range allplayers {
- respond(player.Name)
- }
- respond("end")
+ respond("ok")
case "searchplayers":
- query := strings.TrimSpace(next())
+ query := req[1]
+ var names []string
if query == "" {
for _, p := range allplayers {
- respond(p.Name)
+ names = append(names, p.Name)
}
- respond("end")
+ respond(names...)
break
}
for _, p := range allplayers {
if p.MatchesName(query) {
- respond(p.Name)
+ names = append(names, p.Name)
}
}
- respond("end")
+ respond(names...)
- case "fetchplayers":
+ case "fetchplayers": // FIXME
startggInputs.Token = next()
startggInputs.Slug = next()
time.Sleep(3 * time.Second)
- respond("fetchplayers__resp")
- respond("All done.")
+ respondOld("fetchplayers__resp")
+ respondOld("All done.")
startggInputs.Write(StartggFile)
-
- case "readwebport":
- respond(WebPort)
-
- case "geticon":
- b64icon := base64.StdEncoding.EncodeToString(gortsPngIcon)
- respond(b64icon)
-
- case "getcountrycodes":
- respond(strings.Join(startgg.CountryCodes, " "))
-
- case "readstartgg":
- respond(startggInputs.Token)
- respond(startggInputs.Slug)
}
}
diff --git a/netstring/netstring.go b/netstring/netstring.go
index 20c2506..be988e6 100644
--- a/netstring/netstring.go
+++ b/netstring/netstring.go
@@ -5,15 +5,25 @@
package netstring
import (
+ "bufio"
"bytes"
"fmt"
"strconv"
+ "strings"
)
func Encode(s string) string {
return fmt.Sprintf("%d:%s,", len(s), s)
}
+// Encode multiple strings into a nested netstring
+func EncodeN(strings ...string) (ns string) {
+ for _, s := range strings {
+ ns += Encode(s)
+ }
+ return Encode(ns)
+}
+
// A SplitFunc to be used in a bufio.Scanner
func SplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
colonIndex := bytes.IndexRune(data, ':')
@@ -36,3 +46,16 @@ func SplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
// The whole netstring should now be within the buffer
return colonIndex + 1 + length + 1, rest[:length], nil
}
+
+// Decode multiple concatenated netstrings into plain strings.
+// This is NOT a reverse EncodeN() - the input here is not a nested
+// netstring.
+func DecodeMultiple(nstrings string) (results []string) {
+ r := strings.NewReader(nstrings)
+ s := bufio.NewScanner(r)
+ s.Split(SplitFunc)
+ for s.Scan() {
+ results = append(results, s.Text())
+ }
+ return results
+}
diff --git a/tcl/main.tcl b/tcl/main.tcl
index 616a87a..9944ef1 100644
--- a/tcl/main.tcl
+++ b/tcl/main.tcl
@@ -8,6 +8,8 @@ foreach p {stdin stdout stderr} {
fconfigure $p -translation lf
}
+source tcl/netstring.tcl
+
package require Tk
wm title . "Overly Repetitive Tedious Software (in Go)"
@@ -164,86 +166,89 @@ grid .n.s.msg -row 3 -column 1 -stick W
grid columnconfigure .n.s 1 -weight 1
grid rowconfigure .n.s 1 -pad 5
-# The following procs constitute a very simple line-based IPC system where Tcl
-# client talks to Go server via stdin/stdout.
-
proc initialize {} {
- seticon
- setwebport
- setcountrycodes
- setstartgg
- readscoreboard
+ foreach p {stdin stdout} {
+ fconfigure $p -translation binary
+ }
+ loadicon
+ loadstartgg
+ loadwebmsg
+ loadcountrycodes
+ loadscoreboard
+ loadplayernames
+
setupdiffcheck
- readplayernames
- setupplayersuggestion
+ #setupplayersuggestion
}
-proc setstartgg {} {
- puts "readstartgg"
- set ::startgg(token) [gets stdin]
- set ::startgg(slug) [gets stdin]
+# Very simple IPC system where Tcl client talks to Go server via stdin/stdout
+# using netstrings as wire format.
+proc ipc {method args} {
+ set payload [concat $method $args]
+ puts -nonewline [netstrings $payload]
+ flush stdout
+ return [decodenetstrings [readnetstring stdin]]
}
-proc setwebport {} {
- puts "readwebport"
- set webport [gets stdin]
- set ::mainstatus "Point your OBS browser source to http://localhost:${webport}"
+proc loadicon {} {
+ set resp [ipc "geticon"]
+ set iconblob [lindex $resp 0]
+ image create photo applicationIcon -data $iconblob
+ wm iconphoto . -default applicationIcon
}
-proc seticon {} {
- puts "geticon"
- set b64data [gets stdin]
- image create photo applicationIcon -data [
- binary decode base64 $b64data
- ]
- wm iconphoto . -default applicationIcon
+proc loadstartgg {} {
+ set resp [ipc "getstartgg"]
+ set ::startgg(token) [lindex $resp 0]
+ set ::startgg(slug) [lindex $resp 1]
+}
+
+proc loadwebmsg {} {
+ set resp [ipc "getwebport"]
+ set webport [lindex $resp 0]
+ set ::mainstatus "Point your OBS browser source to http://localhost:${webport}"
}
-proc setcountrycodes {} {
- puts getcountrycodes
- set countrycodes [gets stdin]
- .n.m.players.p1country configure -values $countrycodes
- .n.m.players.p2country configure -values $countrycodes
+proc loadcountrycodes {} {
+ set codes [ipc "getcountrycodes"]
+ .n.m.players.p1country configure -values $codes
+ .n.m.players.p2country configure -values $codes
}
-proc readscoreboard {} {
- puts "readscoreboard"
- set ::scoreboard(description) [gets stdin]
- set ::scoreboard(subtitle) [gets stdin]
- set ::scoreboard(p1name) [gets stdin]
- set ::scoreboard(p1country) [gets stdin]
- set ::scoreboard(p1score) [gets stdin]
- set ::scoreboard(p1team) [gets stdin]
- set ::scoreboard(p2name) [gets stdin]
- set ::scoreboard(p2country) [gets stdin]
- set ::scoreboard(p2score) [gets stdin]
- set ::scoreboard(p2team) [gets stdin]
+proc loadscoreboard {} {
+ set sb [ipc "getscoreboard"]
+ set ::scoreboard(description) [lindex $sb 0]
+ set ::scoreboard(subtitle) [lindex $sb 1]
+ set ::scoreboard(p1name) [lindex $sb 2]
+ set ::scoreboard(p1country) [lindex $sb 3]
+ set ::scoreboard(p1score) [lindex $sb 4]
+ set ::scoreboard(p1team) [lindex $sb 5]
+ set ::scoreboard(p2name) [lindex $sb 6]
+ set ::scoreboard(p2country) [lindex $sb 7]
+ set ::scoreboard(p2score) [lindex $sb 8]
+ set ::scoreboard(p2team) [lindex $sb 9]
update_applied_scoreboard
}
proc applyscoreboard {} {
- puts "applyscoreboard"
- puts $::scoreboard(description)
- puts $::scoreboard(subtitle)
- puts $::scoreboard(p1name)
- puts $::scoreboard(p1country)
- puts $::scoreboard(p1score)
- puts $::scoreboard(p1team)
- puts $::scoreboard(p2name)
- puts $::scoreboard(p2country)
- puts $::scoreboard(p2score)
- puts $::scoreboard(p2team)
+ set sb [ \
+ ipc "applyscoreboard" \
+ $::scoreboard(description) \
+ $::scoreboard(subtitle) \
+ $::scoreboard(p1name) \
+ $::scoreboard(p1country) \
+ $::scoreboard(p1score) \
+ $::scoreboard(p1team) \
+ $::scoreboard(p2name) \
+ $::scoreboard(p2country) \
+ $::scoreboard(p2score) \
+ $::scoreboard(p2team) \
+ ]
update_applied_scoreboard
}
-proc readplayernames {} {
- set playernames {}
- puts "readplayernames"
- set line [gets stdin]
- while {$line != "end"} {
- lappend playernames $line
- set line [gets stdin]
- }
+proc loadplayernames {} {
+ set playernames [ipc "searchplayers" ""]
.n.m.players.p1name configure -values $playernames
.n.m.players.p2name configure -values $playernames
}
diff --git a/tcl/netstring.tcl b/tcl/netstring.tcl
index fa510b1..bfb9bb3 100644
--- a/tcl/netstring.tcl
+++ b/tcl/netstring.tcl
@@ -23,3 +23,19 @@ proc readnetstring {chan} {
read $chan 1; # consume the trailing ","
return $nstr
}
+
+# Assumes input is multiple well formed netstrings concatenated.
+# Returns list of decoded values.
+proc decodenetstrings {ns} {
+ set results {}
+ while {$ns != ""} {
+ set colonIdx [string first : $ns]
+ set len [string range $ns 0 [expr { $colonIdx - 1 }]]
+ set startIdx [expr {$colonIdx + 1}]
+ set endIdx [expr {$startIdx + $len - 1}]
+ set str [string range $ns $startIdx $endIdx]
+ lappend results $str
+ set ns [string range $ns [expr {$endIdx + 2}] end];
+ }
+ return $results
+}