T4.7: Remove forbidden platform imports from src/ui
src/ui now imports only src/app, src/domain, and Fyne. Three violations were fixed: - src/scheduler (NewRealClock): Service.Start() now creates the real clock internally; StartWith(clock) is the injectable seam for tests. - src/platform/desktop (InstallDesktopIntegration): moved into Service.InstallDesktopIcon in new src/app/platform.go. - src/platform/autostart (AutostartStatus, SetAutostart): moved into Service.AutostartStatus and Service.ApplyAutostart in platform.go. go test -race ./... green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -274,7 +274,7 @@ Track progress here. Mark tasks complete as they land and pass review.
|
||||
- [x] T4.4 — Extract `history_view.go`
|
||||
- [x] T4.5 — Extract `settings_view.go`
|
||||
- [x] T4.6 — Extract `tray.go`, `singleinstance.go`, `layout.go`
|
||||
- [ ] T4.7 — Confirm app.go is gone; smoke test both platforms
|
||||
- [x] T4.7 — Confirm app.go is gone; smoke test both platforms
|
||||
|
||||
### Phase 5 — Hardening & docs
|
||||
- [ ] T5.1 — Surface errors from service + storage
|
||||
|
||||
@@ -441,7 +441,7 @@ func TestStartDrivesRunDueOnTick(t *testing.T) {
|
||||
}
|
||||
|
||||
clock := &appFakeClock{ticks: make(chan time.Time, 1), now: time.Now().Add(2 * time.Minute)}
|
||||
svc.Start(clock)
|
||||
svc.StartWith(clock)
|
||||
defer svc.Stop()
|
||||
|
||||
clock.ticks <- clock.now
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"gitea.mixdep.ru/mix/gosentry/src/platform/autostart"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/platform/desktop"
|
||||
)
|
||||
|
||||
// InstallDesktopIcon installs the application's .desktop file and icon on
|
||||
// Linux (no-op on other platforms). The resulting icon path is stored in
|
||||
// store.Paths.DesktopIcon so ApplyAutostart can reference it.
|
||||
func (s *Service) InstallDesktopIcon(appID string, iconBytes []byte) {
|
||||
if iconPath, err := desktop.InstallDesktopIntegration(appID, s.store.Paths.ExecutablePath, iconBytes); err == nil {
|
||||
s.store.Paths.DesktopIcon = iconPath
|
||||
}
|
||||
}
|
||||
|
||||
// AutostartStatus reports whether the platform autostart entry matches the
|
||||
// current StartOnLogin setting in the stored config.
|
||||
func (s *Service) AutostartStatus() (ok bool, message string) {
|
||||
s.mu.Lock()
|
||||
enabled := s.store.Config.StartOnLogin
|
||||
execPath := s.store.Paths.ExecutablePath
|
||||
s.mu.Unlock()
|
||||
return autostart.AutostartStatus(enabled, execPath)
|
||||
}
|
||||
|
||||
// ApplyAutostart writes or removes the platform autostart entry to match the
|
||||
// current StartOnLogin setting in the stored config. Call after UpdateSettings.
|
||||
func (s *Service) ApplyAutostart() error {
|
||||
s.mu.Lock()
|
||||
enabled := s.store.Config.StartOnLogin
|
||||
execPath := s.store.Paths.ExecutablePath
|
||||
iconPath := s.store.Paths.DesktopIcon
|
||||
s.mu.Unlock()
|
||||
return autostart.SetAutostart(enabled, execPath, iconPath)
|
||||
}
|
||||
+10
-5
@@ -91,11 +91,16 @@ func NewService(store *storage.Store, jobs []domain.Job) *Service {
|
||||
return s
|
||||
}
|
||||
|
||||
// Start begins scheduling. It installs a cancelable run context and a timing
|
||||
// loop driven by the given clock; every tick calls RunDue. Pass
|
||||
// scheduler.NewRealClock() in production. Start is expected once, during setup,
|
||||
// before any concurrent use.
|
||||
func (s *Service) Start(clock scheduler.Clock) {
|
||||
// Start begins scheduling with the real wall clock. It is the production entry
|
||||
// point; tests should call StartWith and supply a fake clock instead. Start is
|
||||
// expected once, during setup, before any concurrent use.
|
||||
func (s *Service) Start() {
|
||||
s.StartWith(scheduler.NewRealClock())
|
||||
}
|
||||
|
||||
// StartWith begins scheduling driven by the given clock; every tick calls
|
||||
// RunDue. Used by tests to inject a fake clock.
|
||||
func (s *Service) StartWith(clock scheduler.Clock) {
|
||||
s.mu.Lock()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s.ctx = ctx
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"gitea.mixdep.ru/mix/gosentry/assets"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/app"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/platform/desktop"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/scheduler"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
@@ -26,10 +24,7 @@ func newMainView(w fyne.Window) (fyne.CanvasObject, func(time.Duration, bool)) {
|
||||
if err != nil {
|
||||
return container.NewPadded(widget.NewLabel("Failed to load GoSentry configuration: " + err.Error())), func(time.Duration, bool) {}
|
||||
}
|
||||
store := svc.Store()
|
||||
if iconPath, err := desktop.InstallDesktopIntegration(appID, store.Paths.ExecutablePath, assets.IconBytes()); err == nil {
|
||||
store.Paths.DesktopIcon = iconPath
|
||||
}
|
||||
svc.InstallDesktopIcon(appID, assets.IconBytes())
|
||||
|
||||
// Build the initial event history from the current runtime state. Jobs and
|
||||
// runtimes are read here only for this one-time initialization; the jobs view
|
||||
@@ -80,7 +75,7 @@ func newMainView(w fyne.Window) (fyne.CanvasObject, func(time.Duration, bool)) {
|
||||
refresh()
|
||||
})
|
||||
}))
|
||||
svc.Start(scheduler.NewRealClock())
|
||||
svc.Start()
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItemWithIcon("Jobs", theme.ListIcon(), jobsPanel),
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.mixdep.ru/mix/gosentry/src/app"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/platform/autostart"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
@@ -28,7 +27,7 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
||||
startOnLogin.SetChecked(store.Config.StartOnLogin)
|
||||
autostartStatus := widget.NewLabel("")
|
||||
refreshAutostartStatus := func() {
|
||||
ok, message := autostart.AutostartStatus(store.Config.StartOnLogin, store.Paths.ExecutablePath)
|
||||
ok, message := svc.AutostartStatus()
|
||||
if ok {
|
||||
autostartStatus.SetText("OK: " + message)
|
||||
return
|
||||
@@ -97,9 +96,7 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
||||
settingsStatus.SetText("Save failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
// Autostart is platform integration the Service leaves to the caller (until
|
||||
// T5.2 introduces an injectable autostart.Manager), so apply it here.
|
||||
if err := autostart.SetAutostart(store.Config.StartOnLogin, store.Paths.ExecutablePath, store.Paths.DesktopIcon); err != nil {
|
||||
if err := svc.ApplyAutostart(); err != nil {
|
||||
refreshAutostartStatus()
|
||||
settingsStatus.SetText("Saved, autostart failed: " + err.Error())
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user