From 394a7d643f03d0650ec7437ea6e3c45084bed017 Mon Sep 17 00:00:00 2001 From: yeroc Date: Sat, 23 May 2026 18:59:14 -0400 Subject: [PATCH 1/2] add env flag for custom config location --- engine/viewmodel.go | 42 +++++++++++++++++++++++++++++++++++++++--- webui/o2/main.go | 5 +++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/engine/viewmodel.go b/engine/viewmodel.go index bb70cfb..cdf1b7b 100644 --- a/engine/viewmodel.go +++ b/engine/viewmodel.go @@ -45,6 +45,7 @@ type ViewModel struct { serverViewModel *ServerViewModel config Config + configName string } type Config struct { @@ -112,6 +113,14 @@ func (vm *ViewModel) NotifyView(view string, model interface{}) { vn.NotifyView(view, viewModel) } +func (vm *ViewModel) GetConfigName() string { + return vm.configName +} + +func (vm *ViewModel) SetConfigName(configName string) { + vm.configName = configName +} + // initializes all view models: func (vm *ViewModel) Init() { for _, model := range vm.viewModels { @@ -141,9 +150,27 @@ func (vm *ViewModel) LoadConfiguration() bool { log.Printf("viewmodel: loadConfiguration: could not find configuration directory: %v\n", err) return false } - path := filepath.Join(dir, "config.json") + defaultPath := filepath.Join(dir, "config.json") + + name := vm.GetConfigName() + + var b []byte + err = nil - b, err := ioutil.ReadFile(path) + // load from specified config location: + if name != "" { + config := "config-" + name + ".json" + path := filepath.Join(dir, config) + b, err = ioutil.ReadFile(path) + if err != nil { + // fall back to default config location: + log.Printf("viewmodel: loadConfiguration: could not find read custom configuration file: %v\n", err) + b, err = ioutil.ReadFile(defaultPath) + } + } else { + b, err = ioutil.ReadFile(defaultPath) + } + if err != nil { log.Printf("viewmodel: loadConfiguration: could not find read configuration file: %v\n", err) return false @@ -216,7 +243,16 @@ func (vm *ViewModel) SaveConfiguration() bool { log.Printf("viewmodel: saveConfiguration: could not make directories along the path '%s': %v\n", dir, err) } - path := filepath.Join(dir, "config.json") + name := vm.GetConfigName() + config := "config" + + if name != "" { + config += ("-" + name) + } + + config += ".json" + + path := filepath.Join(dir, config) err = ioutil.WriteFile(path, b, 0644) if err != nil { diff --git a/webui/o2/main.go b/webui/o2/main.go index 920a309..75a85cd 100644 --- a/webui/o2/main.go +++ b/webui/o2/main.go @@ -43,6 +43,7 @@ var ( listenPort int // port number to listen on for webserver browserHost string // hostname to send as part of URL to browser to connect to webserver browserUrl string // full URL that is sent to browser (composed of browserHost:listenPort) + configName string // save configuration data to a unique location logPath string ) @@ -99,10 +100,14 @@ func main() { browserHost = env.GetOrDefault("O2_WEB_BROWSER_HOST", "127.0.0.1") browserUrl = fmt.Sprintf("http://%s:%d/", browserHost, listenPort) + configName = env.GetOrDefault("O2_CONFIG_NAME", ""); + // construct our viewModel: viewModel := engine.NewViewModel() viewModel.SetViewModel("o2", &O2ViewModel{Version: version}) + viewModel.SetConfigName(configName) + // construct the web server: webServer := NewWebServer(listenAddr) From 020381bb9730619e1afb944c9faa383a98eb77e8 Mon Sep 17 00:00:00 2001 From: yeroc Date: Tue, 16 Jun 2026 19:22:07 -0400 Subject: [PATCH 2/2] pr feedback: sanitize config flag, commonize filepath resolver, add port number fallback logic --- engine/viewmodel.go | 45 +++++++++++++++++++-------------------------- webui/o2/main.go | 30 ++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/engine/viewmodel.go b/engine/viewmodel.go index cdf1b7b..368f6fa 100644 --- a/engine/viewmodel.go +++ b/engine/viewmodel.go @@ -3,7 +3,6 @@ package engine import ( "encoding/json" "fmt" - "io/ioutil" "log" "o2/client" "o2/games" @@ -121,6 +120,17 @@ func (vm *ViewModel) SetConfigName(configName string) { vm.configName = configName } +func (vm *ViewModel) getConfigFilePath(dir string) string { + path := "config.json" + configName := vm.GetConfigName() + + if configName != "" { + path = "config-" + configName + ".json" + } + + return filepath.Join(dir, path) +} + // initializes all view models: func (vm *ViewModel) Init() { for _, model := range vm.viewModels { @@ -151,24 +161,16 @@ func (vm *ViewModel) LoadConfiguration() bool { return false } defaultPath := filepath.Join(dir, "config.json") - - name := vm.GetConfigName() + path := vm.getConfigFilePath(dir) var b []byte err = nil - // load from specified config location: - if name != "" { - config := "config-" + name + ".json" - path := filepath.Join(dir, config) - b, err = ioutil.ReadFile(path) - if err != nil { - // fall back to default config location: - log.Printf("viewmodel: loadConfiguration: could not find read custom configuration file: %v\n", err) - b, err = ioutil.ReadFile(defaultPath) - } - } else { - b, err = ioutil.ReadFile(defaultPath) + b, err = os.ReadFile(path) + if path != defaultPath && err != nil { + // fall back to default config location: + log.Printf("viewmodel: loadConfiguration: could not find read custom configuration file: %v\n", err) + b, err = os.ReadFile(defaultPath) } if err != nil { @@ -242,19 +244,10 @@ func (vm *ViewModel) SaveConfiguration() bool { if err != nil { log.Printf("viewmodel: saveConfiguration: could not make directories along the path '%s': %v\n", dir, err) } - - name := vm.GetConfigName() - config := "config" - - if name != "" { - config += ("-" + name) - } - - config += ".json" - path := filepath.Join(dir, config) + path := vm.getConfigFilePath(dir) - err = ioutil.WriteFile(path, b, 0644) + err = os.WriteFile(path, b, 0644) if err != nil { log.Printf("viewmodel: saveConfiguration: could not write configuration file '%s': %v\n", path, err) return false diff --git a/webui/o2/main.go b/webui/o2/main.go index 75a85cd..be0c910 100644 --- a/webui/o2/main.go +++ b/webui/o2/main.go @@ -10,6 +10,7 @@ import ( "o2/util/env" "os" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -30,6 +31,8 @@ import ( _ "o2/games/alttp" ) +const defaultPort = 27637 + // build variables set via ldflags by goreleaser: var ( version string = "v0.0.0" @@ -76,6 +79,25 @@ type O2ViewModel struct { Version string `json:"version"` } +func sanitizeConfigName(s string) string { + var invalidChars = regexp.MustCompile(`[^\p{L}\p{N}_-]+`) + result := invalidChars.ReplaceAllString(s, "_") + result = strings.Trim(result, "._-") + return result +} + +func resolveConfigName(configName string, listenPort int) string { + if configName != "" { + return sanitizeConfigName(configName) + } + + if listenPort != defaultPort { + return strconv.Itoa(listenPort) + } + + return "" +} + func main() { defer func() { if err := recover(); err != nil { @@ -88,19 +110,19 @@ func main() { // Parse env vars: listenHost = env.GetOrDefault("O2_WEB_LISTEN_HOST", "0.0.0.0") - listenPort, err = strconv.Atoi(env.GetOrDefault("O2_WEB_LISTEN_PORT", "27637")) + listenPort, err = strconv.Atoi(env.GetOrDefault("O2_WEB_LISTEN_PORT", strconv.Itoa(defaultPort))) if err != nil { - listenPort = 27637 + listenPort = defaultPort } if listenPort <= 0 { - listenPort = 27637 + listenPort = defaultPort } listenAddr := net.JoinHostPort(listenHost, strconv.Itoa(listenPort)) browserHost = env.GetOrDefault("O2_WEB_BROWSER_HOST", "127.0.0.1") browserUrl = fmt.Sprintf("http://%s:%d/", browserHost, listenPort) - configName = env.GetOrDefault("O2_CONFIG_NAME", ""); + configName = resolveConfigName(env.GetOrDefault("O2_CONFIG_NAME", ""), listenPort); // construct our viewModel: viewModel := engine.NewViewModel()