Refactoring complete: v0.4.0 architectural milestone #1

Merged
mix merged 48 commits from docs/refactoring-plan into main 2026-06-22 08:05:10 +03:00
8 changed files with 67 additions and 7 deletions
Showing only changes of commit 428f018fe1 - Show all commits
+1 -1
View File
@@ -278,7 +278,7 @@ Track progress here. Mark tasks complete as they land and pass review.
### Phase 5 — Hardening & docs ### Phase 5 — Hardening & docs
- [x] T5.1 — Surface errors from service + storage - [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.3 — Fill test gaps (folder filtering, cleanup, migration, concurrency)
- [ ] T5.4 — Run `go test -race ./...` clean on both platforms - [ ] T5.4 — Run `go test -race ./...` clean on both platforms
- [ ] T5.5 — Update docs (ARCHITECTURE.md, TESTS.md, README) - [ ] T5.5 — Update docs (ARCHITECTURE.md, TESTS.md, README)
-2
View File
@@ -240,8 +240,6 @@ func (s *Service) RunDue(now time.Time) {
// UpdateSettings validates and persists a new application configuration. The // UpdateSettings validates and persists a new application configuration. The
// loaded jobs are re-saved because the jobs directory may have changed, and log // loaded jobs are re-saved because the jobs directory may have changed, and log
// cleanup runs so a tightened retention policy takes effect immediately. // 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 { func (s *Service) UpdateSettings(config domain.Config) error {
if err := validateConfig(config); err != nil { if err := validateConfig(config); err != nil {
return err return err
+10 -3
View File
@@ -1,7 +1,6 @@
package app package app
import ( import (
"gitea.mixdep.ru/mix/gosentry/src/platform/autostart"
"gitea.mixdep.ru/mix/gosentry/src/platform/desktop" "gitea.mixdep.ru/mix/gosentry/src/platform/desktop"
) )
@@ -20,8 +19,12 @@ func (s *Service) AutostartStatus() (ok bool, message string) {
s.mu.Lock() s.mu.Lock()
enabled := s.store.Config.StartOnLogin enabled := s.store.Config.StartOnLogin
execPath := s.store.Paths.ExecutablePath execPath := s.store.Paths.ExecutablePath
manager := s.manager
s.mu.Unlock() 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 // 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 enabled := s.store.Config.StartOnLogin
execPath := s.store.Paths.ExecutablePath execPath := s.store.Paths.ExecutablePath
iconPath := s.store.Paths.DesktopIcon iconPath := s.store.Paths.DesktopIcon
manager := s.manager
s.mu.Unlock() s.mu.Unlock()
return autostart.SetAutostart(enabled, execPath, iconPath) if manager == nil {
return nil
}
return manager.Set(enabled, execPath, iconPath)
} }
+8 -1
View File
@@ -6,6 +6,7 @@ import (
"time" "time"
"gitea.mixdep.ru/mix/gosentry/src/domain" "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/runner"
"gitea.mixdep.ru/mix/gosentry/src/scheduler" "gitea.mixdep.ru/mix/gosentry/src/scheduler"
"gitea.mixdep.ru/mix/gosentry/src/storage" "gitea.mixdep.ru/mix/gosentry/src/storage"
@@ -58,6 +59,10 @@ type Service struct {
sched *scheduler.Scheduler sched *scheduler.Scheduler
cancel context.CancelFunc 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 // 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: // 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. // the Service must release mu before dispatching, per the locking contract.
@@ -136,7 +141,9 @@ func Open() (*Service, error) {
if err != nil { if err != nil {
return nil, err 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 // Store returns the underlying store. It is exposed so callers that still need
+9
View File
@@ -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)
}
+13
View File
@@ -13,6 +13,19 @@ import (
"gitea.mixdep.ru/mix/gosentry/src/domain" "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 autostartDesktopFileName = "gosentry.desktop"
const legacyAutostartDesktopFileName = "pysentry.desktop" const legacyAutostartDesktopFileName = "pysentry.desktop"
+13
View File
@@ -4,6 +4,19 @@ package autostart
import "fmt" 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 { func SetAutostart(enabled bool, executablePath string, iconPath string) error {
if !enabled { if !enabled {
return nil return nil
@@ -11,6 +11,19 @@ import (
"gitea.mixdep.ru/mix/gosentry/src/platform/winproc" "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 autostartName = "GoSentry"
const legacyAutostartName = "PySentry" const legacyAutostartName = "PySentry"
const startupShortcutFile = autostartName + ".lnk" const startupShortcutFile = autostartName + ".lnk"