mirror of
https://github.com/Xevion/HATray.git
synced 2025-12-05 23:15:09 -06:00
refactor: create App internally, promote newService(), adjust variable names
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user