diff --git a/docs/REFACTORING.md b/docs/REFACTORING.md index 8ff3c48..37a6d36 100644 --- a/docs/REFACTORING.md +++ b/docs/REFACTORING.md @@ -278,7 +278,7 @@ Track progress here. Mark tasks complete as they land and pass review. ### Phase 5 — Hardening & docs - [x] T5.1 — Surface errors from service + storage -- [ ] T5.2 — Introduce `autostart.Manager` interface +- [x] T5.2 — Introduce `autostart.Manager` interface - [ ] T5.3 — Fill test gaps (folder filtering, cleanup, migration, concurrency) - [ ] T5.4 — Run `go test -race ./...` clean on both platforms - [ ] T5.5 — Update docs (ARCHITECTURE.md, TESTS.md, README) diff --git a/src/app/operations.go b/src/app/operations.go index 2b1f8ab..a932e7e 100644 --- a/src/app/operations.go +++ b/src/app/operations.go @@ -240,8 +240,6 @@ func (s *Service) RunDue(now time.Time) { // UpdateSettings validates and persists a new application configuration. The // loaded jobs are re-saved because the jobs directory may have changed, and log // cleanup runs so a tightened retention policy takes effect immediately. -// Autostart is intentionally left to the caller until T5.2 introduces an -// injectable autostart.Manager. func (s *Service) UpdateSettings(config domain.Config) error { if err := validateConfig(config); err != nil { return err diff --git a/src/app/platform.go b/src/app/platform.go index e4680c3..deedf6f 100644 --- a/src/app/platform.go +++ b/src/app/platform.go @@ -1,7 +1,6 @@ package app import ( - "gitea.mixdep.ru/mix/gosentry/src/platform/autostart" "gitea.mixdep.ru/mix/gosentry/src/platform/desktop" ) @@ -20,8 +19,12 @@ func (s *Service) AutostartStatus() (ok bool, message string) { s.mu.Lock() enabled := s.store.Config.StartOnLogin execPath := s.store.Paths.ExecutablePath + manager := s.manager s.mu.Unlock() - return autostart.AutostartStatus(enabled, execPath) + if manager == nil { + return false, "autostart not available" + } + return manager.Status(enabled, execPath) } // ApplyAutostart writes or removes the platform autostart entry to match the @@ -31,6 +34,10 @@ func (s *Service) ApplyAutostart() error { enabled := s.store.Config.StartOnLogin execPath := s.store.Paths.ExecutablePath iconPath := s.store.Paths.DesktopIcon + manager := s.manager s.mu.Unlock() - return autostart.SetAutostart(enabled, execPath, iconPath) + if manager == nil { + return nil + } + return manager.Set(enabled, execPath, iconPath) } diff --git a/src/app/service.go b/src/app/service.go index cb69439..5ebf990 100644 --- a/src/app/service.go +++ b/src/app/service.go @@ -6,6 +6,7 @@ import ( "time" "gitea.mixdep.ru/mix/gosentry/src/domain" + "gitea.mixdep.ru/mix/gosentry/src/platform/autostart" "gitea.mixdep.ru/mix/gosentry/src/runner" "gitea.mixdep.ru/mix/gosentry/src/scheduler" "gitea.mixdep.ru/mix/gosentry/src/storage" @@ -58,6 +59,10 @@ type Service struct { sched *scheduler.Scheduler cancel context.CancelFunc + // manager is the platform autostart implementation. It is nil in tests that + // do not exercise autostart; Open() wires it via autostart.New(). + manager autostart.Manager + // observers and their guard live in events.go. dispatchMu is separate from mu // so that emitting an event never requires (or is held under) the state lock: // the Service must release mu before dispatching, per the locking contract. @@ -136,7 +141,9 @@ func Open() (*Service, error) { if err != nil { return nil, err } - return NewService(store, jobs), nil + svc := NewService(store, jobs) + svc.manager = autostart.New() + return svc, nil } // Store returns the underlying store. It is exposed so callers that still need diff --git a/src/platform/autostart/autostart.go b/src/platform/autostart/autostart.go new file mode 100644 index 0000000..5139f16 --- /dev/null +++ b/src/platform/autostart/autostart.go @@ -0,0 +1,9 @@ +package autostart + +// Manager controls platform autostart for the application. +type Manager interface { + // Set writes or removes the platform autostart entry to match enabled. + Set(enabled bool, executablePath, iconPath string) error + // Status reports whether the platform autostart entry matches expectedEnabled. + Status(expectedEnabled bool, executablePath string) (ok bool, message string) +} diff --git a/src/platform/autostart/autostart_linux.go b/src/platform/autostart/autostart_linux.go index 8ccb8c1..5d4dd6d 100644 --- a/src/platform/autostart/autostart_linux.go +++ b/src/platform/autostart/autostart_linux.go @@ -13,6 +13,19 @@ import ( "gitea.mixdep.ru/mix/gosentry/src/domain" ) +type linuxManager struct{} + +// New returns the Linux autostart Manager. +func New() Manager { return linuxManager{} } + +func (linuxManager) Set(enabled bool, executablePath, iconPath string) error { + return SetAutostart(enabled, executablePath, iconPath) +} + +func (linuxManager) Status(expectedEnabled bool, executablePath string) (bool, string) { + return AutostartStatus(expectedEnabled, executablePath) +} + const autostartDesktopFileName = "gosentry.desktop" const legacyAutostartDesktopFileName = "pysentry.desktop" diff --git a/src/platform/autostart/autostart_other.go b/src/platform/autostart/autostart_other.go index 5eab3f7..96a1010 100644 --- a/src/platform/autostart/autostart_other.go +++ b/src/platform/autostart/autostart_other.go @@ -4,6 +4,19 @@ package autostart import "fmt" +type otherManager struct{} + +// New returns the stub autostart Manager for unsupported platforms. +func New() Manager { return otherManager{} } + +func (otherManager) Set(enabled bool, executablePath, iconPath string) error { + return SetAutostart(enabled, executablePath, iconPath) +} + +func (otherManager) Status(expectedEnabled bool, executablePath string) (bool, string) { + return AutostartStatus(expectedEnabled, executablePath) +} + func SetAutostart(enabled bool, executablePath string, iconPath string) error { if !enabled { return nil diff --git a/src/platform/autostart/autostart_windows.go b/src/platform/autostart/autostart_windows.go index 813ef21..9493b96 100644 --- a/src/platform/autostart/autostart_windows.go +++ b/src/platform/autostart/autostart_windows.go @@ -11,6 +11,19 @@ import ( "gitea.mixdep.ru/mix/gosentry/src/platform/winproc" ) +type windowsManager struct{} + +// New returns the Windows autostart Manager. +func New() Manager { return windowsManager{} } + +func (windowsManager) Set(enabled bool, executablePath, iconPath string) error { + return SetAutostart(enabled, executablePath, iconPath) +} + +func (windowsManager) Status(expectedEnabled bool, executablePath string) (bool, string) { + return AutostartStatus(expectedEnabled, executablePath) +} + const autostartName = "GoSentry" const legacyAutostartName = "PySentry" const startupShortcutFile = autostartName + ".lnk"