From 0aba8b4bfac0177cfb38d765675ed2d9c686e5d0 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 22 Jun 2025 18:58:01 -0500 Subject: [PATCH] refactor: create App internally, promote newService(), adjust variable names --- cmd/main.go | 6 +- internal/app/app.go | 48 +++++++-------- internal/service/service.go | 10 +--- internal/service/windows.go | 113 +++++++++++++++++++----------------- 4 files changed, 86 insertions(+), 91 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 37f010b..dcfed11 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" - "ha-tray/internal/app" "ha-tray/internal/service" ) @@ -26,9 +25,8 @@ func main() { } }() - // Create app layer and service layer - appLayer := app.NewApp(logger) - svc := service.NewService(logger, appLayer) + // Create service layer + svc := service.NewService(logger) logger.Info("HATray initialized, running service") diff --git a/internal/app/app.go b/internal/app/app.go index abbdc81..9dbd4b9 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -30,13 +30,13 @@ func NewApp(logger *slog.Logger) *App { } // Pause disconnects from the server and ceases any background tasks -func (a *App) Pause() error { - a.mu.Lock() - defer a.mu.Unlock() +func (app *App) Pause() error { + app.mu.Lock() + defer app.mu.Unlock() - a.logger.Info("Pausing application", + app.logger.Info("Pausing application", "action", "pause", - "previous_state", a.state, + "previous_state", app.state, "new_state", StatePaused) // TODO: Implement actual pause logic @@ -44,23 +44,23 @@ func (a *App) Pause() error { // - Stop background tasks // - Pause sensor monitoring - a.state = StatePaused + app.state = StatePaused - a.logger.Info("Application paused successfully", + app.logger.Info("Application paused successfully", "action", "pause", - "state", a.state) + "state", app.state) return nil } // Resume connects to the server and initiates background tasks -func (a *App) Resume() error { - a.mu.Lock() - defer a.mu.Unlock() +func (app *App) Resume() error { + app.mu.Lock() + defer app.mu.Unlock() - a.logger.Info("Resuming application", + app.logger.Info("Resuming application", "action", "resume", - "previous_state", a.state, + "previous_state", app.state, "new_state", StateRunning) // TODO: Implement actual resume logic @@ -68,11 +68,11 @@ func (a *App) Resume() error { // - Start background tasks // - Resume sensor monitoring - a.state = StateRunning + app.state = StateRunning - a.logger.Info("Application resumed successfully", + app.logger.Info("Application resumed successfully", "action", "resume", - "state", a.state) + "state", app.state) return nil } @@ -124,13 +124,13 @@ func (a *App) GetState() AppState { } // Stop stops the application completely -func (a *App) Stop() error { - a.mu.Lock() - defer a.mu.Unlock() +func (app *App) Stop() error { + app.mu.Lock() + defer app.mu.Unlock() - a.logger.Info("Stopping application", + app.logger.Info("Stopping application", "action", "stop", - "previous_state", a.state, + "previous_state", app.state, "new_state", StateStopped) // TODO: Implement actual stop logic @@ -138,11 +138,11 @@ func (a *App) Stop() error { // - Clean up resources // - Stop all background tasks - a.state = StateStopped + app.state = StateStopped - a.logger.Info("Application stopped successfully", + app.logger.Info("Application stopped successfully", "action", "stop", - "state", a.state) + "state", app.state) return nil } diff --git a/internal/service/service.go b/internal/service/service.go index a3bf822..ec10263 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1,17 +1,9 @@ package service -import ( - "ha-tray/internal/app" - "log/slog" -) - // This is an intentionally very-simple interface as the main program entrypoint needs to know very little about the service layer. // The service layer is completely responsible for the lifecycle of the application, implemented per-platform. type Service interface { Run() error } -// NewService creates a new service instance for the current platform -func NewService(logger *slog.Logger, appLayer *app.App) Service { - return newService(logger, appLayer) -} +// You create a service using the NewService() function, implemented per-platform. If you don't have a NewService() function, you can't create a service on your platform. diff --git a/internal/service/windows.go b/internal/service/windows.go index 7b96470..fed9683 100644 --- a/internal/service/windows.go +++ b/internal/service/windows.go @@ -9,9 +9,9 @@ import ( "ha-tray/internal/app" - "golang.org/x/sys/windows/svc" - "golang.org/x/sys/windows/svc/debug" - "golang.org/x/sys/windows/svc/eventlog" + winsvc "golang.org/x/sys/windows/svc" + winsvcDebug "golang.org/x/sys/windows/svc/debug" + winsvcEventlog "golang.org/x/sys/windows/svc/eventlog" ) const serviceName = "HATray" @@ -19,131 +19,136 @@ const serviceName = "HATray" // WindowsService implements the Service interface for Windows type WindowsService struct { logger *slog.Logger - elog debug.Log + elog winsvcDebug.Log isDebug bool app *app.App } // newService creates a new Windows service instance -func newService(logger *slog.Logger, appLayer *app.App) Service { +func NewService(logger *slog.Logger) Service { return &WindowsService{ logger: logger, - app: appLayer, + app: app.NewApp(logger), } } // Run implements the Service interface for Windows -func (w *WindowsService) Run() error { +func (svc *WindowsService) Run() error { // Determine if we're running as a Windows service - isService, err := svc.IsWindowsService() + isService, err := winsvc.IsWindowsService() if err != nil { return fmt.Errorf("failed to determine if running as Windows service: %v", err) } - w.isDebug = !isService + svc.isDebug = !isService if isService { - return w.runAsService() + return svc.runAsService() } // Interactive mode - return w.runInteractive() + return svc.runInteractive() } // runAsService runs the application as a Windows service -func (w *WindowsService) runAsService() error { +func (svc *WindowsService) runAsService() error { var err error - if w.isDebug { - w.elog = debug.New(serviceName) + if svc.isDebug { + svc.elog = winsvcDebug.New(serviceName) } else { - w.elog, err = eventlog.Open(serviceName) + svc.elog, err = winsvcEventlog.Open(serviceName) if err != nil { return fmt.Errorf("failed to open event log: %v", err) } } - defer w.elog.Close() + defer svc.elog.Close() - w.elog.Info(1, fmt.Sprintf("starting %s service", serviceName)) + svc.elog.Info(1, fmt.Sprintf("starting %s service", serviceName)) - run := svc.Run - if w.isDebug { - run = debug.Run + run := winsvc.Run + if svc.isDebug { + run = winsvcDebug.Run } - err = run(serviceName, &windowsServiceHandler{ - service: w, + err = run(serviceName, &serviceHandler{ + service: svc, }) if err != nil { - w.elog.Error(1, fmt.Sprintf("%s service failed: %v", serviceName, err)) + svc.elog.Error(1, fmt.Sprintf("%s service failed: %v", serviceName, err)) return err } - w.elog.Info(1, fmt.Sprintf("%s service stopped", serviceName)) + svc.elog.Info(1, fmt.Sprintf("%s service stopped", serviceName)) return nil } // runInteractive runs the application in interactive mode -func (w *WindowsService) runInteractive() error { - w.logger.Info("Application starting in interactive mode") +func (svc *WindowsService) runInteractive() error { + svc.logger.Info("Application starting in interactive mode") // Simple interactive loop ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { - w.logger.Debug("Application heartbeat") + svc.logger.Debug("Application heartbeat") } return nil } -// windowsServiceHandler implements the Windows service handler interface -type windowsServiceHandler struct { +// serviceHandler implements the Windows service handler interface +type serviceHandler struct { service *WindowsService } -func (h *windowsServiceHandler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { - const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue - changes <- svc.Status{State: svc.StartPending} +func (handler *serviceHandler) Execute(args []string, r <-chan winsvc.ChangeRequest, changes chan<- winsvc.Status) (ssec bool, errno uint32) { + const cmdsAccepted = winsvc.AcceptStop | winsvc.AcceptShutdown | winsvc.AcceptPauseAndContinue + changes <- winsvc.Status{State: winsvc.StartPending} - h.service.logger.Info("Service starting") - changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + handler.service.logger.Info("Service starting") + changes <- winsvc.Status{State: winsvc.Running, Accepts: cmdsAccepted} - // Main service loop - ticker := time.NewTicker(5 * time.Second) + // Service heartbeat + ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case c := <-r: switch c.Cmd { - case svc.Interrogate: + case winsvc.Interrogate: changes <- c.CurrentStatus - case svc.Stop, svc.Shutdown: - h.service.logger.Info("Service stopping") - changes <- svc.Status{State: svc.StopPending} - if err := h.service.app.Stop(); err != nil { - h.service.logger.Error("Failed to stop app layer", "error", err) + case winsvc.Stop, winsvc.Shutdown: + changes <- winsvc.Status{State: winsvc.StopPending} + + handler.service.logger.Info("Service stopping") + if err := handler.service.app.Stop(); err != nil { + handler.service.logger.Error("Failed to stop app layer", "error", err) } return - case svc.Pause: - h.service.logger.Info("Service pausing") - changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} - if err := h.service.app.Pause(); err != nil { - h.service.logger.Error("Failed to pause app layer", "error", err) + case winsvc.Pause: + changes <- winsvc.Status{State: winsvc.Paused, Accepts: cmdsAccepted} + + handler.service.logger.Info("Service pausing") + if err := handler.service.app.Pause(); err != nil { + handler.service.logger.Error("Failed to pause app layer", "error", err) } - case svc.Continue: - h.service.logger.Info("Service continuing") - changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} - if err := h.service.app.Resume(); err != nil { - h.service.logger.Error("Failed to resume app layer", "error", err) + case winsvc.Continue: + changes <- winsvc.Status{State: winsvc.Running, Accepts: cmdsAccepted} + + handler.service.logger.Info("Service continuing") + if err := handler.service.app.Resume(); err != nil { + handler.service.logger.Error("Failed to resume app layer", "error", err) } default: - h.service.elog.Error(uint32(1), fmt.Sprintf("unexpected control request #%d", c)) + // Log the error to the event log & service logger + handler.service.logger.Error("unexpected control request", "request", c) + handler.service.elog.Error(uint32(1), fmt.Sprintf("unexpected control request #%d", c)) } case <-ticker.C: - h.service.logger.Debug("Service heartbeat") + handler.service.logger.Debug("heartbeat") } } }