From f0d7dc80fc37510c053ee831cf756ce1404a52b3 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Sun, 27 Oct 2024 22:46:58 +0100 Subject: [PATCH 01/44] feat: update protocol version handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add ProtocolVersion variable to metadata package - don't set `ServerMessage.ProtocolVersion` in `main` - update `ServerMessage` default to include ProtocolVersion 🤖 --- cmd/server/main.go | 4 +--- internal/metadata/version.go | 3 +++ internal/shared/state.go | 14 ++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 5d0b3a0..9f9db88 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -10,7 +10,6 @@ import ( "github.com/charmbracelet/log" "net/http" "os" - "strings" ) func Start() { @@ -21,10 +20,9 @@ func Start() { showVersionFlag := flag.Bool("version", false, "Output version") flag.Parse() - shared.Message.ProtocolVersion = strings.Split(metadata.GoTomatoVersion, ".")[0] if *showVersionFlag { fmt.Printf("App-Version: %s\n", metadata.GoTomatoVersion) - fmt.Printf("Protocol-Version: %s\n", shared.Message.ProtocolVersion) + fmt.Printf("Protocol-Version: %s\n", metadata.ProtocolVersion) os.Exit(0) } diff --git a/internal/metadata/version.go b/internal/metadata/version.go index d06d970..f1c52e1 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -1,3 +1,6 @@ package metadata +import "strings" + const GoTomatoVersion = "v0.0.4" // The GoTomato Version +var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] diff --git a/internal/shared/state.go b/internal/shared/state.go index 1f8c670..a9eac1b 100644 --- a/internal/shared/state.go +++ b/internal/shared/state.go @@ -1,16 +1,18 @@ package shared import ( + "git.smsvc.net/pomodoro/GoTomato/internal/metadata" "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) var Message = models.ServerMessage{ - Mode: "Idle", - Settings: DefaultPomodoroConfig, - Session: 0, - TimeLeft: DefaultPomodoroConfig.Work, - Ongoing: false, - Paused: false, + Mode: "Idle", + Settings: DefaultPomodoroConfig, + Session: 0, + TimeLeft: DefaultPomodoroConfig.Work, + Ongoing: false, + Paused: false, + ProtocolVersion: metadata.ProtocolVersion, } var PomodoroPassword string From 3a6be4c1870fdefc783e4dbbe94ff856f73df95e Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 28 Oct 2024 09:30:00 +0100 Subject: [PATCH 02/44] feat: update settings handling for pomodoro configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add `UpdateSettings()` to update pomodoro configuration - refactor client command handling to use `UpdateSettings()` 🤖 --- internal/pomodoro/pomodoro.go | 8 ++++++++ internal/websocket/client_commands.go | 6 +----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/pomodoro/pomodoro.go b/internal/pomodoro/pomodoro.go index be39e6e..c24d4a5 100644 --- a/internal/pomodoro/pomodoro.go +++ b/internal/pomodoro/pomodoro.go @@ -2,6 +2,7 @@ package pomodoro import ( "git.smsvc.net/pomodoro/GoTomato/internal/shared" + "git.smsvc.net/pomodoro/GoTomato/pkg/models" "sync" "time" ) @@ -99,3 +100,10 @@ func IsPomodoroPaused() bool { defer mu.Unlock() // Ensures that the mutex is unlocked after the function is done return shared.Message.Paused } + +func UpdateSettings(settings models.PomodoroConfig) { + if settings != (models.PomodoroConfig{}) { + shared.Message.Settings = settings + shared.Message.TimeLeft = settings.Work + } +} diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index cafcd67..c1f323f 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -49,11 +49,7 @@ func handleClientCommands(ws *websocket.Conn) { } case "updateSettings": if !pomodoro.IsPomodoroOngoing() { - if clientCommand.Settings != (models.PomodoroConfig{}) { - shared.Message.Settings = clientCommand.Settings - shared.Message.TimeLeft = clientCommand.Settings.Work - } - + pomodoro.UpdateSettings(clientCommand.Settings) } } } From e2ab19066d563d0398f2159e5159caf64b5f8f9f Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 28 Oct 2024 09:33:05 +0100 Subject: [PATCH 03/44] format: update import statements order for consistency --- cmd/server/main.go | 7 ++++--- internal/pomodoro/pomodoro.go | 5 +++-- internal/pomodoro/timer.go | 4 +--- internal/shared/configDefaults.go | 4 +--- internal/websocket/broadcast.go | 3 ++- internal/websocket/client_commands.go | 5 +++-- internal/websocket/handle_connections.go | 3 ++- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 9f9db88..67fba91 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -3,13 +3,14 @@ package server import ( "flag" "fmt" + "github.com/charmbracelet/log" + "net/http" + "os" + "git.smsvc.net/pomodoro/GoTomato/internal/metadata" "git.smsvc.net/pomodoro/GoTomato/internal/shared" "git.smsvc.net/pomodoro/GoTomato/internal/websocket" "git.smsvc.net/pomodoro/GoTomato/pkg/models" - "github.com/charmbracelet/log" - "net/http" - "os" ) func Start() { diff --git a/internal/pomodoro/pomodoro.go b/internal/pomodoro/pomodoro.go index c24d4a5..6b83842 100644 --- a/internal/pomodoro/pomodoro.go +++ b/internal/pomodoro/pomodoro.go @@ -1,10 +1,11 @@ package pomodoro import ( - "git.smsvc.net/pomodoro/GoTomato/internal/shared" - "git.smsvc.net/pomodoro/GoTomato/pkg/models" "sync" "time" + + "git.smsvc.net/pomodoro/GoTomato/internal/shared" + "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) var mu sync.Mutex // to synchronize access to shared state diff --git a/internal/pomodoro/timer.go b/internal/pomodoro/timer.go index 55cb654..c80f288 100644 --- a/internal/pomodoro/timer.go +++ b/internal/pomodoro/timer.go @@ -1,8 +1,6 @@ package pomodoro -import ( - "time" -) +import "time" type Timer struct { TimeLeft chan int // time left diff --git a/internal/shared/configDefaults.go b/internal/shared/configDefaults.go index a3840e0..9366889 100644 --- a/internal/shared/configDefaults.go +++ b/internal/shared/configDefaults.go @@ -1,8 +1,6 @@ package shared -import ( - "git.smsvc.net/pomodoro/GoTomato/pkg/models" -) +import "git.smsvc.net/pomodoro/GoTomato/pkg/models" var DefaultServerConfig = models.ServerConfig{ ListenAddress: "0.0.0.0", diff --git a/internal/websocket/broadcast.go b/internal/websocket/broadcast.go index 99827e7..64b8fb4 100644 --- a/internal/websocket/broadcast.go +++ b/internal/websocket/broadcast.go @@ -2,10 +2,11 @@ package websocket import ( "encoding/json" - "git.smsvc.net/pomodoro/GoTomato/internal/shared" "github.com/charmbracelet/log" "github.com/gorilla/websocket" "time" + + "git.smsvc.net/pomodoro/GoTomato/internal/shared" ) // sends continous messages to all connected WebSocket clients. diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index c1f323f..472484f 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -2,11 +2,12 @@ package websocket import ( "encoding/json" + "github.com/charmbracelet/log" + "github.com/gorilla/websocket" + "git.smsvc.net/pomodoro/GoTomato/internal/pomodoro" "git.smsvc.net/pomodoro/GoTomato/internal/shared" "git.smsvc.net/pomodoro/GoTomato/pkg/models" - "github.com/charmbracelet/log" - "github.com/gorilla/websocket" ) // handleClientCommands listens for commands from WebSocket clients diff --git a/internal/websocket/handle_connections.go b/internal/websocket/handle_connections.go index 687f472..4b5f1b1 100644 --- a/internal/websocket/handle_connections.go +++ b/internal/websocket/handle_connections.go @@ -1,11 +1,12 @@ package websocket import ( - "git.smsvc.net/pomodoro/GoTomato/pkg/models" "github.com/charmbracelet/log" "github.com/gorilla/websocket" "net/http" "sync" + + "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) // Clients is a map of connected WebSocket clients, where each client is represented by the Client struct From d69a4bec1040cf298c75967f4d329a5411077fad Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 28 Oct 2024 09:34:12 +0100 Subject: [PATCH 04/44] format: use group style variable declaration to define cli flags --- cmd/server/main.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 67fba91..bb2eb2f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -13,12 +13,15 @@ import ( "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) +var ( + // Define CLI flags + listenAddress = flag.String("listenAddress", shared.DefaultServerConfig.ListenAddress, "IP address to listen on") + listenPort = flag.Int("listenPort", shared.DefaultServerConfig.ListenPort, "Port to listen on") + password = flag.String("password", "", "Control password for pomodoro session (optional)") + showVersionFlag = flag.Bool("version", false, "Output version") +) + func Start() { - // Define CLI flags for ListenAddress and ListenPort - listenAddress := flag.String("listenAddress", shared.DefaultServerConfig.ListenAddress, "IP address to listen on") - listenPort := flag.Int("listenPort", shared.DefaultServerConfig.ListenPort, "Port to listen on") - password := flag.String("password", "", "Control password for pomodoro session (optional)") - showVersionFlag := flag.Bool("version", false, "Output version") flag.Parse() if *showVersionFlag { From 5750ec96cb47593bedea3435f19e089d3662cedc Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 28 Oct 2024 10:41:20 +0100 Subject: [PATCH 05/44] refactor: update version output - use `Println` for better readability --- cmd/server/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index bb2eb2f..ebcee49 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -25,8 +25,8 @@ func Start() { flag.Parse() if *showVersionFlag { - fmt.Printf("App-Version: %s\n", metadata.GoTomatoVersion) - fmt.Printf("Protocol-Version: %s\n", metadata.ProtocolVersion) + fmt.Println("App-Version:", metadata.GoTomatoVersion) + fmt.Println("Protocol-Version:", metadata.ProtocolVersion) os.Exit(0) } From b8823acc97c6a48ff9a21818a6718ce04423158c Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Tue, 29 Oct 2024 18:46:00 +0100 Subject: [PATCH 06/44] refacor: replace shared.Message with shared.State MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 --- internal/pomodoro/pomodoro.go | 40 ++++++++++++++++----------------- internal/shared/state.go | 2 +- internal/websocket/broadcast.go | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/internal/pomodoro/pomodoro.go b/internal/pomodoro/pomodoro.go index 6b83842..9eeb04c 100644 --- a/internal/pomodoro/pomodoro.go +++ b/internal/pomodoro/pomodoro.go @@ -18,7 +18,7 @@ func waitForTimer(t Timer) bool { return true case <-t.Abort: return false - case shared.Message.TimeLeft = <-t.TimeLeft: + case shared.State.TimeLeft = <-t.TimeLeft: // do nothing, just let the timer update the message } } @@ -27,48 +27,48 @@ func waitForTimer(t Timer) bool { // RunPomodoro iterates the Pomodoro work/break sessions. func RunPomodoro() { mu.Lock() - shared.Message.Ongoing = true - shared.Message.Paused = false + shared.State.Ongoing = true + shared.State.Paused = false mu.Unlock() - pomodoroConfig := shared.Message.Settings + pomodoroConfig := shared.State.Settings for session := 1; session <= pomodoroConfig.Sessions; session++ { timer = timer.Init() - shared.Message.Session = session + shared.State.Session = session - shared.Message.Mode = "Work" + shared.State.Mode = "Work" go timer.Start(pomodoroConfig.Work) if !waitForTimer(timer) { break } if session < pomodoroConfig.Sessions { - shared.Message.Mode = "ShortBreak" + shared.State.Mode = "ShortBreak" go timer.Start(pomodoroConfig.ShortBreak) if !waitForTimer(timer) { break } } else { // last phase, prepare for finish - shared.Message.Mode = "LongBreak" + shared.State.Mode = "LongBreak" go timer.Start(pomodoroConfig.LongBreak) if !waitForTimer(timer) { break } - shared.Message.Mode = "End" + shared.State.Mode = "End" time.Sleep(time.Second) } } mu.Lock() - shared.Message.Ongoing = false - shared.Message.Paused = false + shared.State.Ongoing = false + shared.State.Paused = false mu.Unlock() - shared.Message.Mode = "Idle" - shared.Message.Session = 0 - shared.Message.TimeLeft = shared.Message.Settings.Work + shared.State.Mode = "Idle" + shared.State.Session = 0 + shared.State.TimeLeft = shared.State.Settings.Work } func ResetPomodoro() { @@ -77,7 +77,7 @@ func ResetPomodoro() { func PausePomodoro() { mu.Lock() - shared.Message.Paused = true + shared.State.Paused = true mu.Unlock() timer.Pause() @@ -85,7 +85,7 @@ func PausePomodoro() { func ResumePomodoro() { mu.Lock() - shared.Message.Paused = false + shared.State.Paused = false mu.Unlock() timer.Resume() } @@ -93,18 +93,18 @@ func ResumePomodoro() { func IsPomodoroOngoing() bool { mu.Lock() defer mu.Unlock() // Ensures that the mutex is unlocked after the function is done - return shared.Message.Ongoing + return shared.State.Ongoing } func IsPomodoroPaused() bool { mu.Lock() defer mu.Unlock() // Ensures that the mutex is unlocked after the function is done - return shared.Message.Paused + return shared.State.Paused } func UpdateSettings(settings models.PomodoroConfig) { if settings != (models.PomodoroConfig{}) { - shared.Message.Settings = settings - shared.Message.TimeLeft = settings.Work + shared.State.Settings = settings + shared.State.TimeLeft = settings.Work } } diff --git a/internal/shared/state.go b/internal/shared/state.go index a9eac1b..a6e1829 100644 --- a/internal/shared/state.go +++ b/internal/shared/state.go @@ -5,7 +5,7 @@ import ( "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) -var Message = models.ServerMessage{ +var State = models.ServerMessage{ Mode: "Idle", Settings: DefaultPomodoroConfig, Session: 0, diff --git a/internal/websocket/broadcast.go b/internal/websocket/broadcast.go index 64b8fb4..d0e5f67 100644 --- a/internal/websocket/broadcast.go +++ b/internal/websocket/broadcast.go @@ -14,7 +14,7 @@ func SendPermanentBroadCastMessage() { tick := time.NewTicker(time.Second) for { // Marshal the message into JSON format - jsonMessage, err := json.Marshal(shared.Message) + jsonMessage, err := json.Marshal(shared.State) if err != nil { log.Error("Error marshalling message:", "msg", err) return From d83acc77b2be6fc01ed3b304177f2d98b7e7323d Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Tue, 29 Oct 2024 18:49:00 +0100 Subject: [PATCH 07/44] refactor: rename HandleConnections to HandleConnection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rename function to better reflect its purpose 🤖 --- cmd/server/main.go | 2 +- .../websocket/{handle_connections.go => handle_connection.go} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename internal/websocket/{handle_connections.go => handle_connection.go} (86%) diff --git a/cmd/server/main.go b/cmd/server/main.go index ebcee49..e97a17d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -38,7 +38,7 @@ func Start() { listen := fmt.Sprintf("%s:%d", serverConfig.ListenAddress, serverConfig.ListenPort) - http.HandleFunc("/ws", websocket.HandleConnections) + http.HandleFunc("/ws", websocket.HandleConnection) go websocket.SendPermanentBroadCastMessage() log.Info("GoTomato started", "version", metadata.GoTomatoVersion) diff --git a/internal/websocket/handle_connections.go b/internal/websocket/handle_connection.go similarity index 86% rename from internal/websocket/handle_connections.go rename to internal/websocket/handle_connection.go index 4b5f1b1..707bea8 100644 --- a/internal/websocket/handle_connections.go +++ b/internal/websocket/handle_connection.go @@ -18,8 +18,8 @@ var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } -// HandleConnections upgrades HTTP requests to WebSocket connections and manages the client lifecycle. -func HandleConnections(w http.ResponseWriter, r *http.Request) { +// HandleConnection upgrades HTTP requests to WebSocket connections and manages the client lifecycle. +func HandleConnection(w http.ResponseWriter, r *http.Request) { // Upgrade initial GET request to a WebSocket ws, err := upgrader.Upgrade(w, r, nil) if err != nil { From d0b1260f6243a6b63ca26a025073b52aac920401 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 07:37:14 +0100 Subject: [PATCH 08/44] doc: add and improve comments --- cmd/server/main.go | 11 +++++++++-- internal/metadata/version.go | 4 ++-- internal/pomodoro/pomodoro.go | 12 +++++++++--- internal/pomodoro/timer.go | 11 +++++++---- internal/shared/configDefaults.go | 2 ++ internal/shared/state.go | 2 ++ internal/websocket/broadcast.go | 2 +- internal/websocket/client_commands.go | 2 +- internal/websocket/handle_connection.go | 4 ++-- pkg/models/client.go | 16 +++++++++------- pkg/models/config.go | 4 +++- pkg/models/server.go | 6 +++--- 12 files changed, 50 insertions(+), 26 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index e97a17d..126ec2f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -14,35 +14,42 @@ import ( ) var ( - // Define CLI flags + // define CLI flags listenAddress = flag.String("listenAddress", shared.DefaultServerConfig.ListenAddress, "IP address to listen on") listenPort = flag.Int("listenPort", shared.DefaultServerConfig.ListenPort, "Port to listen on") password = flag.String("password", "", "Control password for pomodoro session (optional)") showVersionFlag = flag.Bool("version", false, "Output version") ) +// Start the pomodoro server func Start() { flag.Parse() + // show server and protocl version and exit if *showVersionFlag { - fmt.Println("App-Version:", metadata.GoTomatoVersion) + fmt.Println("Server-Version:", metadata.GoTomatoVersion) fmt.Println("Protocol-Version:", metadata.ProtocolVersion) os.Exit(0) } + // set server config serverConfig := models.ServerConfig{ ListenAddress: *listenAddress, ListenPort: *listenPort, } shared.PomodoroPassword = *password + // define listen address for websocket listen := fmt.Sprintf("%s:%d", serverConfig.ListenAddress, serverConfig.ListenPort) + // start connection handler and broadcast http.HandleFunc("/ws", websocket.HandleConnection) go websocket.SendPermanentBroadCastMessage() log.Info("GoTomato started", "version", metadata.GoTomatoVersion) log.Info("Websocket listening", "address", listen) + + // start the listener err := http.ListenAndServe(listen, nil) if err != nil { log.Fatal("Error starting server:", "msg", err) diff --git a/internal/metadata/version.go b/internal/metadata/version.go index f1c52e1..9463896 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,5 @@ package metadata import "strings" -const GoTomatoVersion = "v0.0.4" // The GoTomato Version -var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] +const GoTomatoVersion = "v0.0.4" // The GoTomato version +var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version diff --git a/internal/pomodoro/pomodoro.go b/internal/pomodoro/pomodoro.go index 9eeb04c..8228249 100644 --- a/internal/pomodoro/pomodoro.go +++ b/internal/pomodoro/pomodoro.go @@ -11,6 +11,8 @@ import ( var mu sync.Mutex // to synchronize access to shared state var timer Timer +// Update `State` with the remaining seconds of the passed timer +// until the timer sends an End or Abort signal func waitForTimer(t Timer) bool { for { select { @@ -24,7 +26,7 @@ func waitForTimer(t Timer) bool { } } -// RunPomodoro iterates the Pomodoro work/break sessions. +// RunPomodoro iterates the Pomodoro work/break sessions func RunPomodoro() { mu.Lock() shared.State.Ongoing = true @@ -38,12 +40,14 @@ func RunPomodoro() { shared.State.Session = session + // Work shared.State.Mode = "Work" go timer.Start(pomodoroConfig.Work) if !waitForTimer(timer) { break } + // Breaks if session < pomodoroConfig.Sessions { shared.State.Mode = "ShortBreak" go timer.Start(pomodoroConfig.ShortBreak) @@ -56,6 +60,8 @@ func RunPomodoro() { if !waitForTimer(timer) { break } + + // send "End" state for one second shared.State.Mode = "End" time.Sleep(time.Second) } @@ -92,13 +98,13 @@ func ResumePomodoro() { func IsPomodoroOngoing() bool { mu.Lock() - defer mu.Unlock() // Ensures that the mutex is unlocked after the function is done + defer mu.Unlock() return shared.State.Ongoing } func IsPomodoroPaused() bool { mu.Lock() - defer mu.Unlock() // Ensures that the mutex is unlocked after the function is done + defer mu.Unlock() return shared.State.Paused } diff --git a/internal/pomodoro/timer.go b/internal/pomodoro/timer.go index c80f288..09a50ee 100644 --- a/internal/pomodoro/timer.go +++ b/internal/pomodoro/timer.go @@ -2,15 +2,17 @@ package pomodoro import "time" +// Represents a timer type Timer struct { - TimeLeft chan int // time left + TimeLeft chan int // signals time left on every second End chan bool // signal on successful end Abort chan bool // signal on premature end - stop chan bool - wait chan bool - resume chan bool + stop chan bool // internal channel + wait chan bool // internal channel + resume chan bool // internal channel } +// Initializes a new timer with fresh channels func (t Timer) Init() Timer { return Timer{ TimeLeft: make(chan int), @@ -22,6 +24,7 @@ func (t Timer) Init() Timer { } } +// Start the timer func (t *Timer) Start(duration int) { tick := time.NewTicker(time.Second) for timeLeft := duration; timeLeft > 0; { diff --git a/internal/shared/configDefaults.go b/internal/shared/configDefaults.go index 9366889..e125da8 100644 --- a/internal/shared/configDefaults.go +++ b/internal/shared/configDefaults.go @@ -2,11 +2,13 @@ package shared import "git.smsvc.net/pomodoro/GoTomato/pkg/models" +// The default server config if nothing else is set var DefaultServerConfig = models.ServerConfig{ ListenAddress: "0.0.0.0", ListenPort: 8080, } +// The default pomodoro config if nothing else is set var DefaultPomodoroConfig = models.PomodoroConfig{ Work: 25 * 60, ShortBreak: 5 * 60, diff --git a/internal/shared/state.go b/internal/shared/state.go index a6e1829..e21d4d3 100644 --- a/internal/shared/state.go +++ b/internal/shared/state.go @@ -5,6 +5,7 @@ import ( "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) +// The global state of the pomodoro var State = models.ServerMessage{ Mode: "Idle", Settings: DefaultPomodoroConfig, @@ -15,4 +16,5 @@ var State = models.ServerMessage{ ProtocolVersion: metadata.ProtocolVersion, } +// The password needed to execute client commands or change the pomodoro config var PomodoroPassword string diff --git a/internal/websocket/broadcast.go b/internal/websocket/broadcast.go index d0e5f67..14254c0 100644 --- a/internal/websocket/broadcast.go +++ b/internal/websocket/broadcast.go @@ -9,7 +9,7 @@ import ( "git.smsvc.net/pomodoro/GoTomato/internal/shared" ) -// sends continous messages to all connected WebSocket clients. +// Sends continous messages to all connected WebSocket clients func SendPermanentBroadCastMessage() { tick := time.NewTicker(time.Second) for { diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index 472484f..53464dd 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -10,7 +10,7 @@ import ( "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) -// handleClientCommands listens for commands from WebSocket clients +// Listens for commands from a client and handles them func handleClientCommands(ws *websocket.Conn) { for { var clientCommand models.ClientCommand diff --git a/internal/websocket/handle_connection.go b/internal/websocket/handle_connection.go index 707bea8..5e97df2 100644 --- a/internal/websocket/handle_connection.go +++ b/internal/websocket/handle_connection.go @@ -13,12 +13,12 @@ import ( var Clients = make(map[*websocket.Conn]*models.Client) var mu sync.Mutex // Mutex to protect access to the Clients map -// Upgrader to upgrade HTTP requests to WebSocket connections +// Upgrade HTTP requests to WebSocket connections var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } -// HandleConnection upgrades HTTP requests to WebSocket connections and manages the client lifecycle. +// Upgrades HTTP requests to WebSocket connections and manages the client lifecycle func HandleConnection(w http.ResponseWriter, r *http.Request) { // Upgrade initial GET request to a WebSocket ws, err := upgrader.Upgrade(w, r, nil) diff --git a/pkg/models/client.go b/pkg/models/client.go index 8116d8b..7a6ae21 100644 --- a/pkg/models/client.go +++ b/pkg/models/client.go @@ -6,19 +6,21 @@ import ( "sync" ) -// ClientCommand represents a command from the client (start/stop). +// Represents a command from the client (start/stop) type ClientCommand struct { - Command string `json:"command"` // comman send to the server - Password string `json:"password"` // pomodoro control password - Settings PomodoroConfig `json:"settings"` // pomodoro config + Command string `json:"command"` // Command send to the server + Password string `json:"password"` // Pomodoro control password + Settings PomodoroConfig `json:"settings"` // Pomodoro config } +// Represents a single client type Client struct { - Conn *websocket.Conn - Mutex sync.Mutex + Conn *websocket.Conn // Websocket connection of the client + Mutex sync.Mutex // Mutex used to lock } -// It automatically locks and unlocks the mutex to ensure that only one goroutine can write at a time. +// Sends a message to the websocket. +// Automatically locks and unlocks the client mutex, to ensure that only one goroutine can write at a time. func (c *Client) SendMessage(messageType int, data []byte) error { c.Mutex.Lock() defer c.Mutex.Unlock() diff --git a/pkg/models/config.go b/pkg/models/config.go index 50c7923..36b9dbb 100644 --- a/pkg/models/config.go +++ b/pkg/models/config.go @@ -1,12 +1,14 @@ package models +// Represents the configuration of a pomodoro type PomodoroConfig struct { Work int `json:"work"` // Length of work sessions in seconds ShortBreak int `json:"shortBreak"` // Length of short break in seconds - LongBreak int `json:"longBreak"` // Length if ling break in seconds + LongBreak int `json:"longBreak"` // Length of long break in seconds Sessions int `json:"sessions"` // Number of total sessions } +// Represents the server configuration type ServerConfig struct { ListenAddress string `json:"listenAddress"` // Server listen address ListenPort int `json:"listenPort"` // Server listen port diff --git a/pkg/models/server.go b/pkg/models/server.go index 092c557..6aa914a 100644 --- a/pkg/models/server.go +++ b/pkg/models/server.go @@ -1,12 +1,12 @@ package models -// ServerMessage represents the data sent to the client via WebSocket. +// Represents the data sent to the client via WebSocket type ServerMessage struct { Mode string `json:"mode"` // "Idle", "Work", "ShortBreak", "LongBreak" or "End" Settings PomodoroConfig `json:"settings"` // The currrent pomodoro settings Session int `json:"session"` // Current session number TimeLeft int `json:"time_left"` // Remaining time in seconds - Ongoing bool `json:"ongoing"` // Ongoing pomodoro + Ongoing bool `json:"ongoing"` // Pomodoro ongoing Paused bool `json:"paused"` // Is timer paused - ProtocolVersion string `json:"version"` // Version of the server + ProtocolVersion string `json:"version"` // Version of the protocol } From 94b6786c7c8339def1bcee07c243ef066c90b74c Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 07:46:29 +0100 Subject: [PATCH 09/44] feat: implement asynchronous timer start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add new StartAsync method to Timer for non-blocking execution - use new method in RunPomodoro() 🤖 --- internal/pomodoro/pomodoro.go | 6 +++--- internal/pomodoro/timer.go | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/pomodoro/pomodoro.go b/internal/pomodoro/pomodoro.go index 8228249..a7bcc00 100644 --- a/internal/pomodoro/pomodoro.go +++ b/internal/pomodoro/pomodoro.go @@ -42,7 +42,7 @@ func RunPomodoro() { // Work shared.State.Mode = "Work" - go timer.Start(pomodoroConfig.Work) + timer.StartAsync(pomodoroConfig.Work) if !waitForTimer(timer) { break } @@ -50,13 +50,13 @@ func RunPomodoro() { // Breaks if session < pomodoroConfig.Sessions { shared.State.Mode = "ShortBreak" - go timer.Start(pomodoroConfig.ShortBreak) + timer.StartAsync(pomodoroConfig.ShortBreak) if !waitForTimer(timer) { break } } else { // last phase, prepare for finish shared.State.Mode = "LongBreak" - go timer.Start(pomodoroConfig.LongBreak) + timer.StartAsync(pomodoroConfig.LongBreak) if !waitForTimer(timer) { break } diff --git a/internal/pomodoro/timer.go b/internal/pomodoro/timer.go index 09a50ee..11b1070 100644 --- a/internal/pomodoro/timer.go +++ b/internal/pomodoro/timer.go @@ -24,7 +24,12 @@ func (t Timer) Init() Timer { } } -// Start the timer +// Start the timer (goroutine) +func (t *Timer) StartAsync(duration int) { + go t.Start(duration) +} + +// Start the timer (blocking) func (t *Timer) Start(duration int) { tick := time.NewTicker(time.Second) for timeLeft := duration; timeLeft > 0; { From bdfd5c3b8448cff5d7903ec136bfcb8d32afa2fa Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 08:09:41 +0100 Subject: [PATCH 10/44] feat: remove unused client mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove unused Mutex from `models.Client` struct - remove lock from `HandleConnection()` - replace websocket.Conn with models.Client in `handleClientCommands()` 🤖 --- internal/websocket/client_commands.go | 4 ++-- internal/websocket/handle_connection.go | 9 +++++---- pkg/models/client.go | 7 +------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index 53464dd..4ea7e7d 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -3,7 +3,6 @@ package websocket import ( "encoding/json" "github.com/charmbracelet/log" - "github.com/gorilla/websocket" "git.smsvc.net/pomodoro/GoTomato/internal/pomodoro" "git.smsvc.net/pomodoro/GoTomato/internal/shared" @@ -11,7 +10,8 @@ import ( ) // Listens for commands from a client and handles them -func handleClientCommands(ws *websocket.Conn) { +func handleClientCommands(c models.Client) { + ws := c.Conn for { var clientCommand models.ClientCommand diff --git a/internal/websocket/handle_connection.go b/internal/websocket/handle_connection.go index 5e97df2..37160d8 100644 --- a/internal/websocket/handle_connection.go +++ b/internal/websocket/handle_connection.go @@ -31,12 +31,13 @@ func HandleConnection(w http.ResponseWriter, r *http.Request) { log.Info("Client connected", "host", ws.NetConn().RemoteAddr(), "clients", len(Clients)+1) // Register the new client - mu.Lock() - Clients[ws] = &models.Client{ - Conn: ws, // Store the WebSocket connection + client := models.Client{ + Conn: ws, } + mu.Lock() + Clients[ws] = &client mu.Unlock() // Listen for commands from the connected client - handleClientCommands(ws) + handleClientCommands(client) } diff --git a/pkg/models/client.go b/pkg/models/client.go index 7a6ae21..f9eb116 100644 --- a/pkg/models/client.go +++ b/pkg/models/client.go @@ -3,7 +3,6 @@ package models import ( "github.com/charmbracelet/log" "github.com/gorilla/websocket" - "sync" ) // Represents a command from the client (start/stop) @@ -15,16 +14,12 @@ type ClientCommand struct { // Represents a single client type Client struct { - Conn *websocket.Conn // Websocket connection of the client - Mutex sync.Mutex // Mutex used to lock + Conn *websocket.Conn } // Sends a message to the websocket. // Automatically locks and unlocks the client mutex, to ensure that only one goroutine can write at a time. func (c *Client) SendMessage(messageType int, data []byte) error { - c.Mutex.Lock() - defer c.Mutex.Unlock() - err := c.Conn.WriteMessage(messageType, data) if err != nil { log.Error("Error writing to WebSocket:", "msg", err) From 2d2ea6ff7867319673895b35753d9bf226f1b382 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 08:11:54 +0100 Subject: [PATCH 11/44] feat: bump version to v0.0.5 --- internal/metadata/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/metadata/version.go b/internal/metadata/version.go index 9463896..e31bfed 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,5 @@ package metadata import "strings" -const GoTomatoVersion = "v0.0.4" // The GoTomato version +const GoTomatoVersion = "v0.0.5" // The GoTomato version var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version From ebb58a44893fb09ab57189461f13804c28534eb4 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 09:57:09 +0100 Subject: [PATCH 12/44] feat: rename `Client` model to `WebsocketClient` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 --- internal/websocket/client_commands.go | 2 +- internal/websocket/handle_connection.go | 4 ++-- pkg/models/client.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index 4ea7e7d..59e603a 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -10,7 +10,7 @@ import ( ) // Listens for commands from a client and handles them -func handleClientCommands(c models.Client) { +func handleClientCommands(c models.WebsocketClient) { ws := c.Conn for { var clientCommand models.ClientCommand diff --git a/internal/websocket/handle_connection.go b/internal/websocket/handle_connection.go index 37160d8..dc5ad1c 100644 --- a/internal/websocket/handle_connection.go +++ b/internal/websocket/handle_connection.go @@ -10,7 +10,7 @@ import ( ) // Clients is a map of connected WebSocket clients, where each client is represented by the Client struct -var Clients = make(map[*websocket.Conn]*models.Client) +var Clients = make(map[*websocket.Conn]*models.WebsocketClient) var mu sync.Mutex // Mutex to protect access to the Clients map // Upgrade HTTP requests to WebSocket connections @@ -31,7 +31,7 @@ func HandleConnection(w http.ResponseWriter, r *http.Request) { log.Info("Client connected", "host", ws.NetConn().RemoteAddr(), "clients", len(Clients)+1) // Register the new client - client := models.Client{ + client := models.WebsocketClient{ Conn: ws, } mu.Lock() diff --git a/pkg/models/client.go b/pkg/models/client.go index f9eb116..c0998f8 100644 --- a/pkg/models/client.go +++ b/pkg/models/client.go @@ -13,13 +13,13 @@ type ClientCommand struct { } // Represents a single client -type Client struct { +type WebsocketClient struct { Conn *websocket.Conn } // Sends a message to the websocket. // Automatically locks and unlocks the client mutex, to ensure that only one goroutine can write at a time. -func (c *Client) SendMessage(messageType int, data []byte) error { +func (c *WebsocketClient) SendMessage(messageType int, data []byte) error { err := c.Conn.WriteMessage(messageType, data) if err != nil { log.Error("Error writing to WebSocket:", "msg", err) From 0ee955189c7cdb4156ced500d9d582022708857d Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 10:17:24 +0100 Subject: [PATCH 13/44] feat: update client management to use local address as identifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - change Clients map to use net.Addr as the key type - update `HandleConnection()` to store clients using LocalAddr - modify `handleClientCommands()` to delete clients by LocalAddr 🤖 --- internal/websocket/client_commands.go | 2 +- internal/websocket/handle_connection.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index 59e603a..b38e55b 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -18,7 +18,7 @@ func handleClientCommands(c models.WebsocketClient) { _, message, err := ws.ReadMessage() if err != nil { log.Info("Client disconnected:", "msg", err, "host", ws.NetConn().RemoteAddr(), "clients", len(Clients)-1) - delete(Clients, ws) + delete(Clients, ws.LocalAddr()) break } diff --git a/internal/websocket/handle_connection.go b/internal/websocket/handle_connection.go index dc5ad1c..b7b240f 100644 --- a/internal/websocket/handle_connection.go +++ b/internal/websocket/handle_connection.go @@ -3,14 +3,15 @@ package websocket import ( "github.com/charmbracelet/log" "github.com/gorilla/websocket" + "net" "net/http" "sync" "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) -// Clients is a map of connected WebSocket clients, where each client is represented by the Client struct -var Clients = make(map[*websocket.Conn]*models.WebsocketClient) +// Clients is a map of connected WebSocket clients, where each client is represented by the WebsocketClient struct +var Clients = make(map[net.Addr]*models.WebsocketClient) var mu sync.Mutex // Mutex to protect access to the Clients map // Upgrade HTTP requests to WebSocket connections @@ -35,7 +36,7 @@ func HandleConnection(w http.ResponseWriter, r *http.Request) { Conn: ws, } mu.Lock() - Clients[ws] = &client + Clients[ws.LocalAddr()] = &client mu.Unlock() // Listen for commands from the connected client From f4fd37c55165a80876f4d29bae2a82c83c42c1fd Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 10:19:07 +0100 Subject: [PATCH 14/44] feat: update logging for client connect and disconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - update logging to use ws.RemoteAddr() 🤖 --- internal/websocket/client_commands.go | 2 +- internal/websocket/handle_connection.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index b38e55b..60158f2 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -17,7 +17,7 @@ func handleClientCommands(c models.WebsocketClient) { _, message, err := ws.ReadMessage() if err != nil { - log.Info("Client disconnected:", "msg", err, "host", ws.NetConn().RemoteAddr(), "clients", len(Clients)-1) + log.Info("Client disconnected:", "msg", err, "host", ws.RemoteAddr(), "clients", len(Clients)-1) delete(Clients, ws.LocalAddr()) break } diff --git a/internal/websocket/handle_connection.go b/internal/websocket/handle_connection.go index b7b240f..6abb339 100644 --- a/internal/websocket/handle_connection.go +++ b/internal/websocket/handle_connection.go @@ -29,7 +29,7 @@ func HandleConnection(w http.ResponseWriter, r *http.Request) { } defer ws.Close() - log.Info("Client connected", "host", ws.NetConn().RemoteAddr(), "clients", len(Clients)+1) + log.Info("Client connected", "host", ws.RemoteAddr(), "clients", len(Clients)+1) // Register the new client client := models.WebsocketClient{ From e7618b19ef8367c3942d7e4570c6538261439909 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 11:28:42 +0100 Subject: [PATCH 15/44] feat: validate client settings and improve logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add a function to check if Pomodoro settings are valid - log a warning for invalid client configurations - log an info message for valid client configurations - implement Stringer interface for PomodoroConfig model 🤖 --- internal/websocket/client_commands.go | 9 +++++++++ pkg/models/config.go | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index 60158f2..2fba584 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -9,6 +9,10 @@ import ( "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) +func checkSettings(settings models.PomodoroConfig) bool { + return settings.Work > 0 && settings.ShortBreak > 0 && settings.LongBreak > 0 && settings.Sessions > 0 +} + // Listens for commands from a client and handles them func handleClientCommands(c models.WebsocketClient) { ws := c.Conn @@ -50,6 +54,11 @@ func handleClientCommands(c models.WebsocketClient) { } case "updateSettings": if !pomodoro.IsPomodoroOngoing() { + if !checkSettings(clientCommand.Settings) { + log.Warn("Ignoring invalid config:", "msg", clientCommand.Settings, "host", c.Conn.RemoteAddr()) + break + } + log.Info("Client send config", "config", clientCommand.Settings, "host", c.Conn.RemoteAddr()) pomodoro.UpdateSettings(clientCommand.Settings) } } diff --git a/pkg/models/config.go b/pkg/models/config.go index 36b9dbb..1852165 100644 --- a/pkg/models/config.go +++ b/pkg/models/config.go @@ -1,5 +1,7 @@ package models +import "fmt" + // Represents the configuration of a pomodoro type PomodoroConfig struct { Work int `json:"work"` // Length of work sessions in seconds @@ -8,6 +10,11 @@ type PomodoroConfig struct { Sessions int `json:"sessions"` // Number of total sessions } +// Stringer interface for the PomodocoConfig model +func (c PomodoroConfig) String() string { + return fmt.Sprintf("{work: %d, short: %d, long: %d, sessions: %d}", c.Work, c.ShortBreak, c.LongBreak, c.Sessions) +} + // Represents the server configuration type ServerConfig struct { ListenAddress string `json:"listenAddress"` // Server listen address From dd9490bb3b8e80b0b4b272ac67ecce896b8cdb9b Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 30 Oct 2024 11:29:47 +0100 Subject: [PATCH 16/44] feat: bump version to v0.0.6 --- internal/metadata/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/metadata/version.go b/internal/metadata/version.go index e31bfed..2ab12fa 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,5 @@ package metadata import "strings" -const GoTomatoVersion = "v0.0.5" // The GoTomato version +const GoTomatoVersion = "v0.0.6" // The GoTomato version var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version From 44a64bfce4bc7435ad476071f8e0fe96e0e43d69 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Sun, 3 Nov 2024 10:00:14 +0100 Subject: [PATCH 17/44] feat: handle X-Forward-For for log output - add Gorilla handlers package for enhanced HTTP handling - refactor HTTP server to use a new ServeMux for routing - update ListenAndServe to utilize ProxyHeaders for better proxy support - add RealIP field to client model - use RealIP fild for connect/disconnect log output --- cmd/server/main.go | 6 ++++-- go.mod | 2 ++ go.sum | 4 ++++ internal/websocket/client_commands.go | 2 +- internal/websocket/handle_connection.go | 7 ++++--- pkg/models/client.go | 3 ++- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 126ec2f..c30f3e4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "github.com/charmbracelet/log" + "github.com/gorilla/handlers" "net/http" "os" @@ -43,14 +44,15 @@ func Start() { listen := fmt.Sprintf("%s:%d", serverConfig.ListenAddress, serverConfig.ListenPort) // start connection handler and broadcast - http.HandleFunc("/ws", websocket.HandleConnection) + r := http.NewServeMux() + r.HandleFunc("/ws", websocket.HandleConnection) go websocket.SendPermanentBroadCastMessage() log.Info("GoTomato started", "version", metadata.GoTomatoVersion) log.Info("Websocket listening", "address", listen) // start the listener - err := http.ListenAndServe(listen, nil) + err := http.ListenAndServe(listen, handlers.ProxyHeaders(r)) if err != nil { log.Fatal("Error starting server:", "msg", err) } diff --git a/go.mod b/go.mod index 148683e..e78b89d 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,14 @@ go 1.23 require ( github.com/charmbracelet/log v0.4.0 + github.com/gorilla/handlers v1.5.2 github.com/gorilla/websocket v1.5.3 ) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect diff --git a/go.sum b/go.sum index 9437970..77f660c 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,12 @@ github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8 github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index 2fba584..1a66198 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -21,8 +21,8 @@ func handleClientCommands(c models.WebsocketClient) { _, message, err := ws.ReadMessage() if err != nil { - log.Info("Client disconnected:", "msg", err, "host", ws.RemoteAddr(), "clients", len(Clients)-1) delete(Clients, ws.LocalAddr()) + log.Info("Client disconnected:", "msg", err, "host", c.RealIP, "clients", len(Clients)) break } diff --git a/internal/websocket/handle_connection.go b/internal/websocket/handle_connection.go index 6abb339..6ee22bc 100644 --- a/internal/websocket/handle_connection.go +++ b/internal/websocket/handle_connection.go @@ -29,16 +29,17 @@ func HandleConnection(w http.ResponseWriter, r *http.Request) { } defer ws.Close() - log.Info("Client connected", "host", ws.RemoteAddr(), "clients", len(Clients)+1) - // Register the new client client := models.WebsocketClient{ - Conn: ws, + Conn: ws, + RealIP: r.RemoteAddr, } mu.Lock() Clients[ws.LocalAddr()] = &client mu.Unlock() + log.Info("Client connected", "host", client.RealIP, "clients", len(Clients)) + // Listen for commands from the connected client handleClientCommands(client) } diff --git a/pkg/models/client.go b/pkg/models/client.go index c0998f8..aec2f3c 100644 --- a/pkg/models/client.go +++ b/pkg/models/client.go @@ -14,7 +14,8 @@ type ClientCommand struct { // Represents a single client type WebsocketClient struct { - Conn *websocket.Conn + Conn *websocket.Conn + RealIP string } // Sends a message to the websocket. From 76f395429978bc46340dd081746b7065df0577ca Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Sun, 3 Nov 2024 11:19:03 +0100 Subject: [PATCH 18/44] feat: bump version to v0.0.7 --- internal/metadata/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/metadata/version.go b/internal/metadata/version.go index 2ab12fa..84b8dd7 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,5 @@ package metadata import "strings" -const GoTomatoVersion = "v0.0.6" // The GoTomato version +const GoTomatoVersion = "v0.0.7" // The GoTomato version var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version From 600d2ed2ffffc69d5832648fbfaf544dc1774a4e Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 4 Nov 2024 20:33:25 +0100 Subject: [PATCH 19/44] feat: implement centralized logging helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove direct log usage - create a new logging helper - update all log calls to use the new Logger instance - set timestamp to ISO8601 🤖 --- cmd/server/main.go | 8 ++++---- internal/helper/logging.go | 12 ++++++++++++ internal/websocket/broadcast.go | 6 +++--- internal/websocket/client_commands.go | 10 +++++----- internal/websocket/handle_connection.go | 6 +++--- pkg/models/client.go | 5 +++-- 6 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 internal/helper/logging.go diff --git a/cmd/server/main.go b/cmd/server/main.go index c30f3e4..a109f38 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -3,11 +3,11 @@ package server import ( "flag" "fmt" - "github.com/charmbracelet/log" "github.com/gorilla/handlers" "net/http" "os" + "git.smsvc.net/pomodoro/GoTomato/internal/helper" "git.smsvc.net/pomodoro/GoTomato/internal/metadata" "git.smsvc.net/pomodoro/GoTomato/internal/shared" "git.smsvc.net/pomodoro/GoTomato/internal/websocket" @@ -48,12 +48,12 @@ func Start() { r.HandleFunc("/ws", websocket.HandleConnection) go websocket.SendPermanentBroadCastMessage() - log.Info("GoTomato started", "version", metadata.GoTomatoVersion) - log.Info("Websocket listening", "address", listen) + helper.Logger.Info("GoTomato started", "version", metadata.GoTomatoVersion) + helper.Logger.Info("Websocket listening", "address", listen) // start the listener err := http.ListenAndServe(listen, handlers.ProxyHeaders(r)) if err != nil { - log.Fatal("Error starting server:", "msg", err) + helper.Logger.Fatal("Error starting server:", "msg", err) } } diff --git a/internal/helper/logging.go b/internal/helper/logging.go new file mode 100644 index 0000000..ecb7371 --- /dev/null +++ b/internal/helper/logging.go @@ -0,0 +1,12 @@ +package helper + +import ( + "github.com/charmbracelet/log" + "os" + "time" +) + +var Logger = log.NewWithOptions(os.Stdout, log.Options{ + ReportTimestamp: true, + TimeFormat: time.RFC3339, +}) diff --git a/internal/websocket/broadcast.go b/internal/websocket/broadcast.go index 14254c0..def8309 100644 --- a/internal/websocket/broadcast.go +++ b/internal/websocket/broadcast.go @@ -2,10 +2,10 @@ package websocket import ( "encoding/json" - "github.com/charmbracelet/log" "github.com/gorilla/websocket" "time" + "git.smsvc.net/pomodoro/GoTomato/internal/helper" "git.smsvc.net/pomodoro/GoTomato/internal/shared" ) @@ -16,14 +16,14 @@ func SendPermanentBroadCastMessage() { // Marshal the message into JSON format jsonMessage, err := json.Marshal(shared.State) if err != nil { - log.Error("Error marshalling message:", "msg", err) + helper.Logger.Error("Error marshalling message:", "msg", err) return } // Iterate over all connected clients and broadcast the message for _, client := range Clients { err := client.SendMessage(websocket.TextMessage, jsonMessage) if err != nil { - log.Error("Error broadcasting to client:", "msg", err) + helper.Logger.Error("Error broadcasting to client:", "msg", err) // The client is responsible for closing itself on error } } diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index 1a66198..bdb6e7a 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -2,8 +2,8 @@ package websocket import ( "encoding/json" - "github.com/charmbracelet/log" + "git.smsvc.net/pomodoro/GoTomato/internal/helper" "git.smsvc.net/pomodoro/GoTomato/internal/pomodoro" "git.smsvc.net/pomodoro/GoTomato/internal/shared" "git.smsvc.net/pomodoro/GoTomato/pkg/models" @@ -22,14 +22,14 @@ func handleClientCommands(c models.WebsocketClient) { _, message, err := ws.ReadMessage() if err != nil { delete(Clients, ws.LocalAddr()) - log.Info("Client disconnected:", "msg", err, "host", c.RealIP, "clients", len(Clients)) + helper.Logger.Info("Client disconnected:", "msg", err, "host", c.RealIP, "clients", len(Clients)) break } // Handle incoming commands err = json.Unmarshal(message, &clientCommand) if err != nil { - log.Error("Error unmarshalling command:", "msg", err) + helper.Logger.Error("Error unmarshalling command:", "msg", err) continue } @@ -55,10 +55,10 @@ func handleClientCommands(c models.WebsocketClient) { case "updateSettings": if !pomodoro.IsPomodoroOngoing() { if !checkSettings(clientCommand.Settings) { - log.Warn("Ignoring invalid config:", "msg", clientCommand.Settings, "host", c.Conn.RemoteAddr()) + helper.Logger.Warn("Ignoring invalid config:", "msg", clientCommand.Settings, "host", c.Conn.RemoteAddr()) break } - log.Info("Client send config", "config", clientCommand.Settings, "host", c.Conn.RemoteAddr()) + helper.Logger.Info("Client send config", "config", clientCommand.Settings, "host", c.Conn.RemoteAddr()) pomodoro.UpdateSettings(clientCommand.Settings) } } diff --git a/internal/websocket/handle_connection.go b/internal/websocket/handle_connection.go index 6ee22bc..9c4b43f 100644 --- a/internal/websocket/handle_connection.go +++ b/internal/websocket/handle_connection.go @@ -1,12 +1,12 @@ package websocket import ( - "github.com/charmbracelet/log" "github.com/gorilla/websocket" "net" "net/http" "sync" + "git.smsvc.net/pomodoro/GoTomato/internal/helper" "git.smsvc.net/pomodoro/GoTomato/pkg/models" ) @@ -24,7 +24,7 @@ func HandleConnection(w http.ResponseWriter, r *http.Request) { // Upgrade initial GET request to a WebSocket ws, err := upgrader.Upgrade(w, r, nil) if err != nil { - log.Error("WebSocket upgrade error:", "msg", err) + helper.Logger.Error("WebSocket upgrade error:", "msg", err) return } defer ws.Close() @@ -38,7 +38,7 @@ func HandleConnection(w http.ResponseWriter, r *http.Request) { Clients[ws.LocalAddr()] = &client mu.Unlock() - log.Info("Client connected", "host", client.RealIP, "clients", len(Clients)) + helper.Logger.Info("Client connected", "host", client.RealIP, "clients", len(Clients)) // Listen for commands from the connected client handleClientCommands(client) diff --git a/pkg/models/client.go b/pkg/models/client.go index aec2f3c..eef9663 100644 --- a/pkg/models/client.go +++ b/pkg/models/client.go @@ -1,8 +1,9 @@ package models import ( - "github.com/charmbracelet/log" "github.com/gorilla/websocket" + + "git.smsvc.net/pomodoro/GoTomato/internal/helper" ) // Represents a command from the client (start/stop) @@ -23,7 +24,7 @@ type WebsocketClient struct { func (c *WebsocketClient) SendMessage(messageType int, data []byte) error { err := c.Conn.WriteMessage(messageType, data) if err != nil { - log.Error("Error writing to WebSocket:", "msg", err) + helper.Logger.Error("Error writing to WebSocket:", "msg", err) c.Conn.Close() // Close the connection on error } return err From d7b77890512385b8c17df8838eaa8f5b9f201ffd Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 4 Nov 2024 20:35:01 +0100 Subject: [PATCH 20/44] feat: bump version to v0.0.8 --- internal/metadata/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/metadata/version.go b/internal/metadata/version.go index 84b8dd7..8968337 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,5 @@ package metadata import "strings" -const GoTomatoVersion = "v0.0.7" // The GoTomato version +const GoTomatoVersion = "v0.0.8" // The GoTomato version var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version From cbe2e007de082c23293d4e37ab9bd07f0ebf5758 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Tue, 5 Nov 2024 23:29:24 +0100 Subject: [PATCH 21/44] break: drop "v" from version string const - removes "v" from the ProtocolVersion in the ServerMessage - remove "v" prefix from GoTomatoVersion constant - re-add "v" for for human readable output (`-version`) - update README --- README.md | 10 +++++----- cmd/server/main.go | 4 ++-- internal/metadata/version.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 15e4f08..c2d9e51 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,11 @@ The server periodically (every second) sends JSON-encoded messages to all connec | Message Type | Example | | --- | --- | -| Welcome Message | {"mode":"Idle", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":0, "time_left":1500, "ongoing":false, "paused":false, "version":"v0"} | -| Session Running | {"mode":"Work", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":1, "time_left":900, "ongoing":true, "paused":false, "version":"v0"} | -| Session Running | {"mode":"ShortBreak", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":2, "time_left":50, "ongoing":true, "paused":false, "version":"v0"} | -| Session Paused | {"mode":"Work", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":2, "time_left":456, "ongoing":true, "paused":true, "version":"v0"} | -| Session End/Reset | {"mode":"End", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":0, "time_left":0, "ongoing":false, "paused":false, "version":"v0"} | +| Welcome Message | {"mode":"Idle", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":0, "time_left":1500, "ongoing":false, "paused":false, "version":"0"} | +| Session Running | {"mode":"Work", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":1, "time_left":900, "ongoing":true, "paused":false, "version":"0"} | +| Session Running | {"mode":"ShortBreak", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":2, "time_left":50, "ongoing":true, "paused":false, "version":"0"} | +| Session Paused | {"mode":"Work", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":2, "time_left":456, "ongoing":true, "paused":true, "version":"0"} | +| Session End/Reset | {"mode":"End", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":0, "time_left":0, "ongoing":false, "paused":false, "version":"0"} | ## Testing diff --git a/cmd/server/main.go b/cmd/server/main.go index a109f38..29241f0 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -28,8 +28,8 @@ func Start() { // show server and protocl version and exit if *showVersionFlag { - fmt.Println("Server-Version:", metadata.GoTomatoVersion) - fmt.Println("Protocol-Version:", metadata.ProtocolVersion) + fmt.Printf("Server-Version: v%s\n", metadata.GoTomatoVersion) + fmt.Printf("Protocol-Version: v%s\n", metadata.ProtocolVersion) os.Exit(0) } diff --git a/internal/metadata/version.go b/internal/metadata/version.go index 8968337..f05a892 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,5 @@ package metadata import "strings" -const GoTomatoVersion = "v0.0.8" // The GoTomato version +const GoTomatoVersion = "0.0.8" // The GoTomato version var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version From b15324f6f8ca32c3ee196e23f7792d3724f22db7 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 6 Nov 2024 08:27:12 +0100 Subject: [PATCH 22/44] feat: bump version to v0.1.0 --- internal/metadata/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/metadata/version.go b/internal/metadata/version.go index f05a892..9b7f4f7 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,5 @@ package metadata import "strings" -const GoTomatoVersion = "0.0.8" // The GoTomato version +const GoTomatoVersion = "0.1.0" // The GoTomato version var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version From f346cbbcaa78a69979c413cadcf98f19b421d32e Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 6 Nov 2024 18:11:37 +0100 Subject: [PATCH 23/44] feat: add initial configuration for GoReleaser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 --- .gitignore | 2 ++ .goreleaser.yaml | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 .goreleaser.yaml diff --git a/.gitignore b/.gitignore index 2201ae4..5ee2497 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ GoTomato + +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..aa2e879 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,41 @@ +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +before: + hooks: + - rm -fr ./dist + - go mod tidy + +builds: + - goos: + - linux + goarch: + - amd64 + - arm64 + env: + - CGO_ENABLED=0 + +upx: + - enabled: true + compress: best + lzma: true + +changelog: + sort: desc + filters: + exclude: + - "bump version to" + +archives: + - format: binary + +release: + gitea: + owner: pomodoro + name: GoTomato + mode: replace + +gitea_urls: + download: http://git.smsvc.net + api: http://git.smsvc.net/api/v1 From 783d158e9231a4e95276c50decb17ecb427a4234 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Wed, 6 Nov 2024 20:44:44 +0100 Subject: [PATCH 24/44] feat: bump version to 0.1.1 --- internal/metadata/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/metadata/version.go b/internal/metadata/version.go index 9b7f4f7..22e394e 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,5 @@ package metadata import "strings" -const GoTomatoVersion = "0.1.0" // The GoTomato version +const GoTomatoVersion = "0.1.1" // The GoTomato version var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version From 93f39507c1477cd7b8853be0d40d7b441b60ccfd Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Thu, 7 Nov 2024 11:38:45 +0100 Subject: [PATCH 25/44] feat(gorleaser): set version from git tag on build - make `GoTomatoVersion` a variable and default it to "devel" - add ldflags to `.goreleaser.yaml` for version tagging - remove "v" from output of `-version` --- .goreleaser.yaml | 2 ++ cmd/server/main.go | 4 ++-- internal/metadata/version.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index aa2e879..4b315c5 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -15,6 +15,8 @@ builds: - arm64 env: - CGO_ENABLED=0 + ldflags: + - -s -w -X git.smsvc.net/pomodoro/GoTomato/internal/metadata.GoTomatoVersion={{.Version}} upx: - enabled: true diff --git a/cmd/server/main.go b/cmd/server/main.go index 29241f0..a109f38 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -28,8 +28,8 @@ func Start() { // show server and protocl version and exit if *showVersionFlag { - fmt.Printf("Server-Version: v%s\n", metadata.GoTomatoVersion) - fmt.Printf("Protocol-Version: v%s\n", metadata.ProtocolVersion) + fmt.Println("Server-Version:", metadata.GoTomatoVersion) + fmt.Println("Protocol-Version:", metadata.ProtocolVersion) os.Exit(0) } diff --git a/internal/metadata/version.go b/internal/metadata/version.go index 22e394e..ecac17b 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -2,5 +2,6 @@ package metadata import "strings" -const GoTomatoVersion = "0.1.1" // The GoTomato version +// This will be overwritten by goreleaser on build +var GoTomatoVersion = "devel" // The GoTomato version var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version From 467d90065c43d9384da3ba3da63d98435171b01b Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Thu, 7 Nov 2024 19:29:48 +0100 Subject: [PATCH 26/44] feat(goreleaser): update changelog configuration - uses the compare Gitea API - drop `sort` setting - remove the filter that excludes version bump entries --- .goreleaser.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4b315c5..a3190f6 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -24,10 +24,7 @@ upx: lzma: true changelog: - sort: desc - filters: - exclude: - - "bump version to" + use: gitea archives: - format: binary From 9852f80461172d23230000420272c48f0cce0c89 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Fri, 8 Nov 2024 14:23:54 +0100 Subject: [PATCH 27/44] fix: restore filter that excludes version bump entries --- .goreleaser.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index a3190f6..48ad016 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -25,6 +25,9 @@ upx: changelog: use: gitea + filters: + exclude: + - "chore: bump version to" archives: - format: binary From 2032688c1f9ce981d1f8675a33339783ad472cf3 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Sat, 9 Nov 2024 15:59:30 +0100 Subject: [PATCH 28/44] feat!: always retrieve version from `runtime/debug.Main.Version` or latest git tag - set version from runtime/debug.Main.Version - use latest git tag as fallback - allow GoTomatoVersion to be overwritten via ldflags - this will break `go build .` and `go install .` --- internal/metadata/version.go | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/internal/metadata/version.go b/internal/metadata/version.go index ecac17b..1d76b3e 100644 --- a/internal/metadata/version.go +++ b/internal/metadata/version.go @@ -1,7 +1,37 @@ package metadata -import "strings" +import ( + "os/exec" + "runtime/debug" + "strings" +) -// This will be overwritten by goreleaser on build -var GoTomatoVersion = "devel" // The GoTomato version -var ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] // The protocol version +var ( + GoTomatoVersion = "" // The GoTomato version + ProtocolVersion = "" // The protocol version +) + +func stripVersionPrefix(version string) string { + return strings.TrimLeft(version, "v") +} + +func getLatestTag() string { + bytes, _ := exec.Command("git", "describe", "--tags").Output() + output := strings.TrimSpace(string(bytes)) + return stripVersionPrefix(output) +} + +// set GoTomatoVersion from runtime/debug.Main.Version +// use latest git tag as fallback +// can be overwritten via ldflags (e,g. by goreleaser) +func init() { + if GoTomatoVersion == "" { + info, _ := debug.ReadBuildInfo() + if info.Main.Version != "(devel)" { + GoTomatoVersion = stripVersionPrefix(info.Main.Version) + } else { + GoTomatoVersion = getLatestTag() + } + } + ProtocolVersion = strings.Split(GoTomatoVersion, ".")[0] +} From 05a8701a263e063a6d4fa134638b91952c2f99a8 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Fri, 8 Nov 2024 14:22:54 +0100 Subject: [PATCH 29/44] feat: update `-version` output --- cmd/server/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index a109f38..2024fa2 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -28,8 +28,8 @@ func Start() { // show server and protocl version and exit if *showVersionFlag { - fmt.Println("Server-Version:", metadata.GoTomatoVersion) - fmt.Println("Protocol-Version:", metadata.ProtocolVersion) + fmt.Printf("GoTomato v%s\n", metadata.GoTomatoVersion) + fmt.Printf("Protocol-Version: %s\n", metadata.ProtocolVersion) os.Exit(0) } From 2f337bb9d69842c92f2af2c3525758b4c8943d88 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Fri, 8 Nov 2024 15:13:46 +0100 Subject: [PATCH 30/44] feat: add release management via `Task` - create `Taskfile.yml` - initial tasks - add new version tag - push to remote and run goreleaser - create snapshot via goreleaser --- Taskfile.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Taskfile.yml diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..77dfff5 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,32 @@ +# github.com/go-task/task/v3/cmd/task@latest +# Requirements: +# github.com/caarlos0/svu@latest +# github.com/goreleaser/goreleaser/v2@latest + +version: '3' + +tasks: + + release: + desc: Create and publish an new release + vars: + RELEASE: + sh: svu next + BRANCH: + sh: git branch --show-current + COMMIT: + sh: git rev-parse --short --verify {{.BRANCH}} + preconditions: + - sh: test "{{.BRANCH}}" == "main" + msg: "You must be on the main branch to release" + prompt: Create new release {{.RELEASE}} from {{.COMMIT}}@{{.BRANCH}}? + cmds: + - git tag {{.RELEASE}} + - git push + - git push origin tag {{.RELEASE}} + - goreleaser release --clean + + snapshot: + desc: Create a local snapshot release + cmds: + - goreleaser release --clean --snapshot From 13e2c2d0e406eb2fbb37b4d72a8643ac304c62d8 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Sat, 9 Nov 2024 09:51:07 +0100 Subject: [PATCH 31/44] fix(goreleaser): don't force-remove `./dist` --- .goreleaser.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 48ad016..22dc0d9 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,7 +4,6 @@ version: 2 before: hooks: - - rm -fr ./dist - go mod tidy builds: From 8683cb2a6b21d4d82b217453f18988b684243675 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Sat, 9 Nov 2024 10:42:26 +0100 Subject: [PATCH 32/44] chore: add yaml schemas to `Taskfile.yml` and `.goreleaser.yaml` --- .goreleaser.yaml | 1 + Taskfile.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 22dc0d9..9bdad83 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,4 +1,5 @@ # vim: set ts=2 sw=2 tw=0 fo=cnqoj +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json version: 2 diff --git a/Taskfile.yml b/Taskfile.yml index 77dfff5..8efbf3a 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# # github.com/go-task/task/v3/cmd/task@latest # Requirements: # github.com/caarlos0/svu@latest From 64f790bf90cb31b74d2fa9f2636e6af829236f4d Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Sat, 9 Nov 2024 22:37:52 +0100 Subject: [PATCH 33/44] feat(goreleaser): update build command to use `.ModulePath` variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - change hardcoded module path to dynamic reference 🤖 --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9bdad83..77a3e60 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -16,7 +16,7 @@ builds: env: - CGO_ENABLED=0 ldflags: - - -s -w -X git.smsvc.net/pomodoro/GoTomato/internal/metadata.GoTomatoVersion={{.Version}} + - -s -w -X {{.ModulePath}}/internal/metadata.GoTomatoVersion={{.Version}} upx: - enabled: true From b3f403cf1afc2ee4c68866731bbfd18b1df8cded Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Sun, 10 Nov 2024 16:47:57 +0100 Subject: [PATCH 34/44] fix(renovate): make Go deps always a "chore" - add package rules to set semantic commit type for gomod --- .renovaterc.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.renovaterc.json b/.renovaterc.json index ff27ef5..aceb001 100644 --- a/.renovaterc.json +++ b/.renovaterc.json @@ -2,5 +2,11 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "local>infrastructure/renovate-config" + ], + "packageRules": [ + { + "matchManagers": ["gomod"], + "semanticCommitType": "chore" + } ] } From 31179d4af4fbfe0b7a8f0c41460b489224091609 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 18 Nov 2024 08:11:39 +0100 Subject: [PATCH 35/44] break: update terminology from "Work" to "Focus" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - change mode from "Work" to "Focus" in server messages - modify pomodoro configuration to use "Focus" instead of "Work" - adjust default settings to reflect new terminology - ensure all references to work duration are updated to focus duration - update variable names in HTML and JavaScript for clarity 🤖 --- README.md | 26 +++++++++++++------------- cmd/server/main.go | 10 +++++----- index.html | 8 ++++---- internal/pomodoro/pomodoro.go | 12 ++++++------ internal/shared/configDefaults.go | 2 +- internal/shared/state.go | 2 +- internal/websocket/client_commands.go | 2 +- pkg/models/config.go | 4 ++-- pkg/models/server.go | 2 +- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index c2d9e51..596cb46 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,17 @@ Here are the available commands: | `pause` | Pauses the current session | `{"command": "pause", "password": ""}` | | `resume` | Resumes a paused session | `{"command": "resume", "password": ""}` | | `reset` | Stops and resets the current session | `{"command": "reset", "password": ""}` | -| `updateSettings` | Update Pomodoro settings | `{"command": "updateSettings", "password": "", "settings": {"work": 600, "shortBreak": 60, "longBreak": 300, "sessions": 2}}` | +| `updateSettings` | Update Pomodoro settings | `{"command": "updateSettings", "password": "", "settings": {"focus": 600, "shortBreak": 60, "longBreak": 300, "sessions": 2}}` | #### Update Settings Command (`updateSettings`) -The `updateSettings` command allows clients to modify the Pomodoro timer configuration, including the length of work sessions, short breaks, long breaks, and the total number of sessions in a cycle. This command must include a valid password to be accepted by the server. The updateSettings command can be used when the timer is stopped. +The `updateSettings` command allows clients to modify the Pomodoro timer configuration, including the length of focus sessions, short breaks, long breaks, and the total number of sessions in a cycle. This command must include a valid password to be accepted by the server. The updateSettings command can be used when the timer is stopped. - Fields in the `settings` Object: - - `work`: Length of the work session (in seconds). + - `focus`: Length of the focus session (in seconds). - `shortBreak`: Length of the short break (in seconds). - `longBreak`: Length of the long break (in seconds). - - `sessions`: Total number of work/break sessions in the Pomodoro cycle. + - `sessions`: Total number of focus/break sessions in the Pomodoro cycle. All fields are mandatory and may not be ommitted. @@ -52,14 +52,14 @@ All fields are mandatory and may not be ommitted. The server periodically (every second) sends JSON-encoded messages to all connected clients to update them on the current state of the Pomodoro session. These messages contain the following fields: -- mode: Indicates the current phase of the Pomodoro session ("Work", "ShortBreak", "LongBreak", "End" or "Idle"). +- mode: Indicates the current phase of the Pomodoro session ("Focus", "ShortBreak", "LongBreak", "End" or "Idle"). - "End" is send only once, after all sessions are finished - settings: Contains the current Pomodoro settings: - - work: Length of the work session in seconds (e.g., 1500 for 25 minutes). + - focus: Length of the focus session in seconds (e.g., 1500 for 25 minutes). - shortBreak: Length of the short break in seconds (e.g., 300 for 5 minutes). - longBreak: Length of the long break in seconds (e.g., 900 for 15 minutes). - - sessions: The total number of work/break sessions (e.g., 4). -- session: The current session number (e.g., 1 for the first work session). + - sessions: The total number of focus/break sessions (e.g., 4). +- session: The current session number (e.g., 1 for the first focus session). - time_left: The remaining time for the current mode, in seconds (e.g., 900 for 15 minutes). - ongoing: Whether a Pomodoro session is currently ongoing. - paused: Whether the timer is paused. @@ -67,11 +67,11 @@ The server periodically (every second) sends JSON-encoded messages to all connec | Message Type | Example | | --- | --- | -| Welcome Message | {"mode":"Idle", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":0, "time_left":1500, "ongoing":false, "paused":false, "version":"0"} | -| Session Running | {"mode":"Work", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":1, "time_left":900, "ongoing":true, "paused":false, "version":"0"} | -| Session Running | {"mode":"ShortBreak", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":2, "time_left":50, "ongoing":true, "paused":false, "version":"0"} | -| Session Paused | {"mode":"Work", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":2, "time_left":456, "ongoing":true, "paused":true, "version":"0"} | -| Session End/Reset | {"mode":"End", "settings":{"work":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":0, "time_left":0, "ongoing":false, "paused":false, "version":"0"} | +| Welcome Message | {"mode":"Idle", "settings":{"focus":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":0, "time_left":1500, "ongoing":false, "paused":false, "version":"0"} | +| Session Running | {"mode":"Focus", "settings":{"focus":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":1, "time_left":900, "ongoing":true, "paused":false, "version":"0"} | +| Session Running | {"mode":"ShortBreak", "settings":{"focus":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":2, "time_left":50, "ongoing":true, "paused":false, "version":"0"} | +| Session Paused | {"mode":"Focus", "settings":{"focus":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":2, "time_left":456, "ongoing":true, "paused":true, "version":"0"} | +| Session End/Reset | {"mode":"End", "settings":{"focus":1500, "shortBreak":300, "longBreak":900, "sessions":4}, "session":0, "time_left":0, "ongoing":false, "paused":false, "version":"0"} | ## Testing diff --git a/cmd/server/main.go b/cmd/server/main.go index 2024fa2..330afec 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -16,10 +16,10 @@ import ( var ( // define CLI flags - listenAddress = flag.String("listenAddress", shared.DefaultServerConfig.ListenAddress, "IP address to listen on") - listenPort = flag.Int("listenPort", shared.DefaultServerConfig.ListenPort, "Port to listen on") - password = flag.String("password", "", "Control password for pomodoro session (optional)") - showVersionFlag = flag.Bool("version", false, "Output version") + listenAddress = flag.String("listenAddress", shared.DefaultServerConfig.ListenAddress, "IP address to listen on") + listenPort = flag.Int("listenPort", shared.DefaultServerConfig.ListenPort, "Port to listen on") + password = flag.String("password", "", "Control password for pomodoro session (optional)") + showVersion = flag.Bool("version", false, "Output version") ) // Start the pomodoro server @@ -27,7 +27,7 @@ func Start() { flag.Parse() // show server and protocl version and exit - if *showVersionFlag { + if *showVersion { fmt.Printf("GoTomato v%s\n", metadata.GoTomatoVersion) fmt.Printf("Protocol-Version: %s\n", metadata.ProtocolVersion) os.Exit(0) diff --git a/index.html b/index.html index f04bf34..a75e885 100644 --- a/index.html +++ b/index.html @@ -40,8 +40,8 @@

- - + +
@@ -98,7 +98,7 @@ document.getElementById("saveButton").addEventListener("click", function () { // Get the values from the input fields var password = document.getElementById("password").value; - var work = parseInt(document.getElementById("workDuration").value); + var focus = parseInt(document.getElementById("focusDuration").value); var shortBreak = parseInt(document.getElementById("shortBreakDuration").value); var longBreak = parseInt(document.getElementById("longBreakDuration").value); var sessions = parseInt(document.getElementById("sessions").value); @@ -108,7 +108,7 @@ command: "updateSettings", password: password, settings: { - work: work, + focus: focus, shortBreak: shortBreak, longBreak: longBreak, sessions: sessions diff --git a/internal/pomodoro/pomodoro.go b/internal/pomodoro/pomodoro.go index a7bcc00..1dc0e46 100644 --- a/internal/pomodoro/pomodoro.go +++ b/internal/pomodoro/pomodoro.go @@ -26,7 +26,7 @@ func waitForTimer(t Timer) bool { } } -// RunPomodoro iterates the Pomodoro work/break sessions +// RunPomodoro iterates the Pomodoro focus/break sessions func RunPomodoro() { mu.Lock() shared.State.Ongoing = true @@ -40,9 +40,9 @@ func RunPomodoro() { shared.State.Session = session - // Work - shared.State.Mode = "Work" - timer.StartAsync(pomodoroConfig.Work) + // Focus + shared.State.Mode = "Focus" + timer.StartAsync(pomodoroConfig.Focus) if !waitForTimer(timer) { break } @@ -74,7 +74,7 @@ func RunPomodoro() { shared.State.Mode = "Idle" shared.State.Session = 0 - shared.State.TimeLeft = shared.State.Settings.Work + shared.State.TimeLeft = shared.State.Settings.Focus } func ResetPomodoro() { @@ -111,6 +111,6 @@ func IsPomodoroPaused() bool { func UpdateSettings(settings models.PomodoroConfig) { if settings != (models.PomodoroConfig{}) { shared.State.Settings = settings - shared.State.TimeLeft = settings.Work + shared.State.TimeLeft = settings.Focus } } diff --git a/internal/shared/configDefaults.go b/internal/shared/configDefaults.go index e125da8..5b668cc 100644 --- a/internal/shared/configDefaults.go +++ b/internal/shared/configDefaults.go @@ -10,7 +10,7 @@ var DefaultServerConfig = models.ServerConfig{ // The default pomodoro config if nothing else is set var DefaultPomodoroConfig = models.PomodoroConfig{ - Work: 25 * 60, + Focus: 25 * 60, ShortBreak: 5 * 60, LongBreak: 15 * 60, Sessions: 4, diff --git a/internal/shared/state.go b/internal/shared/state.go index e21d4d3..00934f5 100644 --- a/internal/shared/state.go +++ b/internal/shared/state.go @@ -10,7 +10,7 @@ var State = models.ServerMessage{ Mode: "Idle", Settings: DefaultPomodoroConfig, Session: 0, - TimeLeft: DefaultPomodoroConfig.Work, + TimeLeft: DefaultPomodoroConfig.Focus, Ongoing: false, Paused: false, ProtocolVersion: metadata.ProtocolVersion, diff --git a/internal/websocket/client_commands.go b/internal/websocket/client_commands.go index bdb6e7a..7149faa 100644 --- a/internal/websocket/client_commands.go +++ b/internal/websocket/client_commands.go @@ -10,7 +10,7 @@ import ( ) func checkSettings(settings models.PomodoroConfig) bool { - return settings.Work > 0 && settings.ShortBreak > 0 && settings.LongBreak > 0 && settings.Sessions > 0 + return settings.Focus > 0 && settings.ShortBreak > 0 && settings.LongBreak > 0 && settings.Sessions > 0 } // Listens for commands from a client and handles them diff --git a/pkg/models/config.go b/pkg/models/config.go index 1852165..b1a41ac 100644 --- a/pkg/models/config.go +++ b/pkg/models/config.go @@ -4,7 +4,7 @@ import "fmt" // Represents the configuration of a pomodoro type PomodoroConfig struct { - Work int `json:"work"` // Length of work sessions in seconds + Focus int `json:"focus"` // Length of focus sessions in seconds ShortBreak int `json:"shortBreak"` // Length of short break in seconds LongBreak int `json:"longBreak"` // Length of long break in seconds Sessions int `json:"sessions"` // Number of total sessions @@ -12,7 +12,7 @@ type PomodoroConfig struct { // Stringer interface for the PomodocoConfig model func (c PomodoroConfig) String() string { - return fmt.Sprintf("{work: %d, short: %d, long: %d, sessions: %d}", c.Work, c.ShortBreak, c.LongBreak, c.Sessions) + return fmt.Sprintf("{focus: %d, short: %d, long: %d, sessions: %d}", c.Focus, c.ShortBreak, c.LongBreak, c.Sessions) } // Represents the server configuration diff --git a/pkg/models/server.go b/pkg/models/server.go index 6aa914a..f495505 100644 --- a/pkg/models/server.go +++ b/pkg/models/server.go @@ -2,7 +2,7 @@ package models // Represents the data sent to the client via WebSocket type ServerMessage struct { - Mode string `json:"mode"` // "Idle", "Work", "ShortBreak", "LongBreak" or "End" + Mode string `json:"mode"` // "Idle", "Focus", "ShortBreak", "LongBreak" or "End" Settings PomodoroConfig `json:"settings"` // The currrent pomodoro settings Session int `json:"session"` // Current session number TimeLeft int `json:"time_left"` // Remaining time in seconds From 256837c1302032516e2d4eed1ca210a7062932bd Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 18 Nov 2024 08:57:32 +0100 Subject: [PATCH 36/44] feat: add client instructions to usage output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add custom usage message - include instructions for WebSocket client connection 🤖 --- cmd/server/main.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/server/main.go b/cmd/server/main.go index 330afec..25fc443 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -24,6 +24,13 @@ var ( // Start the pomodoro server func Start() { + // Update usage output + flag.Usage = func() { + fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", "GoTomato") + flag.PrintDefaults() + fmt.Println("\nPoint your client to 'ws://:/ws'") + } + flag.Parse() // show server and protocl version and exit From e8e65c4f3a036c1aaf2d744190fb79ae72db6569 Mon Sep 17 00:00:00 2001 From: Sebastian Mark Date: Mon, 18 Nov 2024 17:43:49 +0100 Subject: [PATCH 37/44] break: remove `/ws` from websocket path --- cmd/server/main.go | 4 ++-- index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 25fc443..d8eb8f6 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -28,7 +28,7 @@ func Start() { flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", "GoTomato") flag.PrintDefaults() - fmt.Println("\nPoint your client to 'ws://:/ws'") + fmt.Println("\nPoint your client to 'ws://:'") } flag.Parse() @@ -52,7 +52,7 @@ func Start() { // start connection handler and broadcast r := http.NewServeMux() - r.HandleFunc("/ws", websocket.HandleConnection) + r.HandleFunc("/", websocket.HandleConnection) go websocket.SendPermanentBroadCastMessage() helper.Logger.Info("GoTomato started", "version", metadata.GoTomatoVersion) diff --git a/index.html b/index.html index a75e885..1de404b 100644 --- a/index.html +++ b/index.html @@ -64,7 +64,7 @@