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..cb527c6 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,25 +24,28 @@ func (t Timer) Init() Timer { } } +// Start the timer func (t *Timer) Start(duration int) { tick := time.NewTicker(time.Second) - for timeLeft := duration; timeLeft > 0; { - select { - case <-t.stop: - t.Abort <- true - return - case <-t.wait: - <-t.resume - continue - case <-tick.C: - timeLeft-- - default: - t.TimeLeft <- timeLeft + go func() { + for timeLeft := duration; timeLeft > 0; { + select { + case <-t.stop: + t.Abort <- true + return + case <-t.wait: + <-t.resume + continue + case <-tick.C: + timeLeft-- + default: + t.TimeLeft <- timeLeft + } } - } - t.TimeLeft <- 0 + t.TimeLeft <- 0 - t.End <- true + t.End <- true + }() } func (t *Timer) Stop() { 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 }