Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: use channels for browser src updates #190

Merged
merged 4 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestMain(t *testing.T) {
t.Fatal("failed to init txt store")
}

testSuite.trackingHandler = cmd.NewTrackingHandler(nil, sqlDb, nosqlDb, txtDb, nil)
testSuite.trackingHandler = cmd.NewTrackingHandler(nil, sqlDb, nosqlDb, txtDb, nil, nil)
testSuite.trackingHandler.SetEventEmitter(func(eventName string, optionalData ...interface{}) {
t.Log(fmt.Sprintf("[EVENT] %s", eventName), optionalData[0])
})
Expand Down
48 changes: 34 additions & 14 deletions cmd/tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type TrackingHandler struct {

cancelPolling context.CancelFunc
forcePollChan chan struct{}
matchChans []chan model.Match

sqlDb *sql.Storage
nosqlDb *nosql.Storage
Expand All @@ -38,13 +39,21 @@ type TrackingHandler struct {

var _ CmdHandler = (*TrackingHandler)(nil)

func NewTrackingHandler(browser *browser.Browser, sqlDb *sql.Storage, nosqlDb *nosql.Storage, txtDb *txt.Storage, cfg *config.Config) *TrackingHandler {
func NewTrackingHandler(
browser *browser.Browser,
sqlDb *sql.Storage,
nosqlDb *nosql.Storage,
txtDb *txt.Storage,
cfg *config.Config,
matchChans ...chan model.Match,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can do this? 0.o

) *TrackingHandler {
return &TrackingHandler{
sqlDb: sqlDb,
nosqlDb: nosqlDb,
txtDb: txtDb,
browser: browser,
cfg: cfg,
sqlDb: sqlDb,
nosqlDb: nosqlDb,
txtDb: txtDb,
browser: browser,
cfg: cfg,
matchChans: matchChans,
}
}

Expand All @@ -58,36 +67,47 @@ func (ch *TrackingHandler) StartTracking(userCode string, restore bool) {
ctx, cancel := context.WithCancel(context.Background())
ch.cancelPolling = cancel
ch.forcePollChan = make(chan struct{})
var matchChan = make(chan model.Match)
matchChan := make(chan model.Match, 1)

defer func() {
ch.eventEmitter("stopped-tracking")
ticker.Stop()
cancel()
close(ch.forcePollChan)
ch.forcePollChan = nil
ch.eventEmitter("stopped-tracking")
}()

session, err := ch.gameTracker.Init(ctx, userCode, restore)
if err != nil {
return
}

onNewMatch := func(match model.Match) {
for _, mc := range ch.matchChans {
mc <- match
}
matchChan <- match
}

if len(session.Matches) > 0 {
ch.eventEmitter("match", *session.Matches[0])
match := *session.Matches[0]
ch.eventEmitter("match", match)
for _, mc := range ch.matchChans {
mc <- match
}
}

go func() {
log.Println("polling")
ch.gameTracker.Poll(ctx, cancel, session, matchChan)
ch.gameTracker.Poll(ctx, cancel, session, onNewMatch)
for {
select {
case <-ch.forcePollChan:
log.Println("forced poll")
ch.gameTracker.Poll(ctx, cancel, session, matchChan)
ch.gameTracker.Poll(ctx, cancel, session, onNewMatch)
case <-ticker.C:
log.Println("polling")
ch.gameTracker.Poll(ctx, cancel, session, matchChan)
ch.gameTracker.Poll(ctx, cancel, session, onNewMatch)
case <-ctx.Done():
close(matchChan)
return
Expand All @@ -103,11 +123,11 @@ func (ch *TrackingHandler) StartTracking(userCode string, restore bool) {
session.Matches = append([]*model.Match{&match}, session.Matches...)

if err := ch.sqlDb.UpdateSession(ctx, session); err != nil {
log.Println("failed to update session", err)
log.Println("failed to update session:", err)
break
}
if err := ch.sqlDb.SaveMatch(ctx, match); err != nil {
log.Println("failed to save match to database", err)
log.Println("failed to save match to database:", err)
break
}
if err := ch.txtDb.SaveMatch(match); err != nil {
Expand Down
16 changes: 9 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"net/http"
"os"
"strings"
"time"

"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
Expand All @@ -26,6 +25,7 @@ import (
"github.com/williamsjokvist/cfn-tracker/pkg/browser"
"github.com/williamsjokvist/cfn-tracker/pkg/config"
"github.com/williamsjokvist/cfn-tracker/pkg/errorsx"
"github.com/williamsjokvist/cfn-tracker/pkg/model"
"github.com/williamsjokvist/cfn-tracker/pkg/server"
"github.com/williamsjokvist/cfn-tracker/pkg/storage/nosql"
"github.com/williamsjokvist/cfn-tracker/pkg/storage/sql"
Expand Down Expand Up @@ -151,10 +151,14 @@ func main() {
return
}

browserSrcMatchChan := make(chan model.Match, 1)

cmdHandler := cmd.NewCommandHandler(appBrowser, sqlDb, noSqlDb, txtDb, &cfg)
trackingHandler := cmd.NewTrackingHandler(appBrowser, sqlDb, noSqlDb, txtDb, &cfg)
trackingHandler := cmd.NewTrackingHandler(appBrowser, sqlDb, noSqlDb, txtDb, &cfg, browserSrcMatchChan)
cmdHandlers := []cmd.CmdHandler{cmdHandler, trackingHandler}

browserSrcServer := server.NewBrowserSourceServer(browserSrcMatchChan)

var onSecondInstanceLaunch func(secondInstanceData options.SecondInstanceData)
err = wails.Run(&options.App{
Title: fmt.Sprintf(`CFN Tracker v%s`, appVersion),
Expand Down Expand Up @@ -197,15 +201,13 @@ func main() {

for _, c := range cmdHandlers {
c.SetEventEmitter(func(eventName string, optionalData ...interface{}) {
log.Println(eventName, optionalData)
runtime.EventsEmit(ctx, eventName, optionalData[0])
log.Println("[FE EVENT]", eventName, optionalData)
runtime.EventsEmit(ctx, eventName, optionalData...)
})
}
},
OnStartup: func(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
server.Start(ctx, &cfg)
go browserSrcServer.Start(ctx, &cfg)
williamsjokvist marked this conversation as resolved.
Show resolved Hide resolved
},
OnShutdown: func(_ context.Context) {
appBrowser.Page.Browser().Close()
Expand Down
41 changes: 0 additions & 41 deletions pkg/model/tracking_state.go

This file was deleted.

108 changes: 57 additions & 51 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import (
"log"
"net/http"
"strings"
"time"

wails "github.com/wailsapp/wails/v2/pkg/runtime"

"github.com/williamsjokvist/cfn-tracker/pkg/config"
"github.com/williamsjokvist/cfn-tracker/pkg/model"
Expand All @@ -20,84 +17,74 @@ import (
//go:embed static
var staticFs embed.FS

var mhJson *[]byte

func GetInternalThemes() []model.Theme {
var themes = make([]model.Theme, 0, 10)
type BrowserSourceServer struct {
matchChan chan model.Match
lastMatch []byte
}

if err := fs.WalkDir(staticFs, "static/themes", func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
return nil
}
b, _ := fs.ReadFile(staticFs, path)
themes = append(themes, model.Theme{
Name: strings.Split(d.Name(), ".css")[0],
CSS: string(b),
})
return nil
}); err != nil {
return []model.Theme{}
func NewBrowserSourceServer(matchChan chan model.Match) *BrowserSourceServer {
return &BrowserSourceServer{
matchChan: matchChan,
lastMatch: nil,
}
return themes
}

func Start(ctx context.Context, cfg *config.Config) {
log.Println(`Starting browser source server`)
func (b *BrowserSourceServer) Start(ctx context.Context, cfg *config.Config) {
log.Println("Starting browser source server")

wails.EventsOn(ctx, `match`, func(incomingData ...interface{}) {
mh, ok := incomingData[0].(*model.Match)
if !ok {
return
}
js, _ := json.Marshal(mh)
mhJson = &js
})
http.HandleFunc("/", handleRoot)
http.HandleFunc(`GET /stream`, handleStream)
http.HandleFunc("GET /themes/{theme}", handleTheme)
http.HandleFunc("/", b.handleRoot)
http.HandleFunc(`GET /stream`, b.handleStream)
http.HandleFunc("GET /themes/{theme}", b.handleTheme)
williamsjokvist marked this conversation as resolved.
Show resolved Hide resolved

// serve custom themes through "themes" directory in the same directory as the user's executable
fs := http.FileServer(http.Dir("./themes"))
http.Handle("/themes/", http.StripPrefix("/themes/", fs))

if err := http.ListenAndServe(fmt.Sprintf(`:%d`, cfg.BrowserSourcePort), nil); err != nil {
if err := http.ListenAndServe(fmt.Sprintf(":%d", cfg.BrowserSourcePort), nil); err != nil {
williamsjokvist marked this conversation as resolved.
Show resolved Hide resolved
log.Println("failed to launch browser source server", err)
}
}

func handleStream(w http.ResponseWriter, _ *http.Request) {
func (b *BrowserSourceServer) handleStream(w http.ResponseWriter, _ *http.Request) {
flusher, ok := w.(http.Flusher)
var lastJson *[]byte = nil
if !ok {
http.Error(w, `SSE not supported`, http.StatusInternalServerError)
http.Error(w, "SSE not supported", http.StatusInternalServerError)
return
}

w.Header().Set(`Content-Type`, `text/event-stream`)
w.Header().Set(`Cache-Control`, `no-cache`)
w.Header().Set(`Connection`, `keep-alive`)
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")

ticker := time.NewTicker(time.Second * 5)
for range ticker.C {
if mhJson == lastJson {
continue
}
if b.lastMatch != nil {
fmt.Fprint(w, "event: message\n\n")
fmt.Fprintf(w, "data: %s\n\n", b.lastMatch)
flusher.Flush()
}

for match := range b.matchChan {
log.Println("[BS EVENT] new match played")
matchJson, err := json.Marshal(match)
if err != nil {
log.Println("browser source: failed to marshal match", err)
http.Error(w, "browser source: failed to marshal match", http.StatusInternalServerError)
break
}
b.lastMatch = matchJson
fmt.Fprint(w, "event: message\n\n")
fmt.Fprintf(w, "data: %s\n\n", *mhJson)
lastJson = mhJson
fmt.Fprintf(w, "data: %s\n\n", matchJson)
flusher.Flush()
}
williamsjokvist marked this conversation as resolved.
Show resolved Hide resolved
}

func handleTheme(w http.ResponseWriter, req *http.Request) {
func (b *BrowserSourceServer) handleTheme(w http.ResponseWriter, req *http.Request) {
fileName := req.PathValue("theme")
css, err := staticFs.ReadFile(fmt.Sprintf("static/themes/%s", fileName))

if err != nil {
williamsjokvist marked this conversation as resolved.
Show resolved Hide resolved
w.WriteHeader(http.StatusNotFound)
} else {
w.Header().Set(`Content-Type`, `text/css`)
w.Header().Set("Content-Type", "text/css")
w.WriteHeader(http.StatusOK)
_, err := w.Write(css)
if err != nil {
Expand All @@ -106,16 +93,35 @@ func handleTheme(w http.ResponseWriter, req *http.Request) {
}
}

func handleRoot(w http.ResponseWriter, _ *http.Request) {
func (b *BrowserSourceServer) handleRoot(w http.ResponseWriter, _ *http.Request) {
html, err := staticFs.ReadFile("static/index.html")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
} else {
w.Header().Set(`Content-Type`, `text/html`)
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
_, err := w.Write(html)
if err != nil {
log.Println("failed to write browser source html")
}
}
}

func GetInternalThemes() []model.Theme {
var themes = make([]model.Theme, 0, 10)

if err := fs.WalkDir(staticFs, "static/themes", func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
return nil
}
b, _ := fs.ReadFile(staticFs, path)
williamsjokvist marked this conversation as resolved.
Show resolved Hide resolved
themes = append(themes, model.Theme{
Name: strings.Split(d.Name(), ".css")[0],
CSS: string(b),
})
return nil
}); err != nil {
return []model.Theme{}
}
return themes
}
williamsjokvist marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions pkg/tracker/sf6/track.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (t *SF6Tracker) Init(ctx context.Context, userCode string, restore bool) (*
return session, nil
}

func (t *SF6Tracker) Poll(ctx context.Context, cancel context.CancelFunc, session *model.Session, matchChan chan model.Match) {
func (t *SF6Tracker) Poll(ctx context.Context, cancel context.CancelFunc, session *model.Session, onNewMatch func(model.Match)) {
bl, err := t.cfnClient.GetBattleLog(session.UserId)
if err != nil {
cancel()
Expand All @@ -79,7 +79,7 @@ func (t *SF6Tracker) Poll(ctx context.Context, cancel context.CancelFunc, sessio
if session.LP == bl.GetLP() {
return
}
matchChan <- getMatch(session, bl)
onNewMatch(getMatch(session, bl))
}

func getOpponentInfo(myCfn string, replay *cfn.Replay) cfn.PlayerInfo {
Expand Down
Loading