Refactoring complete: v0.4.0 architectural milestone #1
+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.4 — Extract `history_view.go`
|
||||||
- [x] T4.5 — Extract `settings_view.go`
|
- [x] T4.5 — Extract `settings_view.go`
|
||||||
- [x] T4.6 — Extract `tray.go`, `singleinstance.go`, `layout.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
|
### Phase 5 — Hardening & docs
|
||||||
- [ ] T5.1 — Surface errors from service + storage
|
- [ ] 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)}
|
clock := &appFakeClock{ticks: make(chan time.Time, 1), now: time.Now().Add(2 * time.Minute)}
|
||||||
svc.Start(clock)
|
svc.StartWith(clock)
|
||||||
defer svc.Stop()
|
defer svc.Stop()
|
||||||
|
|
||||||
clock.ticks <- clock.now
|
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
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start begins scheduling. It installs a cancelable run context and a timing
|
// Start begins scheduling with the real wall clock. It is the production entry
|
||||||
// loop driven by the given clock; every tick calls RunDue. Pass
|
// point; tests should call StartWith and supply a fake clock instead. Start is
|
||||||
// scheduler.NewRealClock() in production. Start is expected once, during setup,
|
// expected once, during setup, before any concurrent use.
|
||||||
// before any concurrent use.
|
func (s *Service) Start() {
|
||||||
func (s *Service) Start(clock scheduler.Clock) {
|
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()
|
s.mu.Lock()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
s.ctx = ctx
|
s.ctx = ctx
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import (
|
|||||||
"gitea.mixdep.ru/mix/gosentry/assets"
|
"gitea.mixdep.ru/mix/gosentry/assets"
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/app"
|
"gitea.mixdep.ru/mix/gosentry/src/app"
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
"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"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
@@ -26,10 +24,7 @@ func newMainView(w fyne.Window) (fyne.CanvasObject, func(time.Duration, bool)) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return container.NewPadded(widget.NewLabel("Failed to load GoSentry configuration: " + err.Error())), func(time.Duration, bool) {}
|
return container.NewPadded(widget.NewLabel("Failed to load GoSentry configuration: " + err.Error())), func(time.Duration, bool) {}
|
||||||
}
|
}
|
||||||
store := svc.Store()
|
svc.InstallDesktopIcon(appID, assets.IconBytes())
|
||||||
if iconPath, err := desktop.InstallDesktopIntegration(appID, store.Paths.ExecutablePath, assets.IconBytes()); err == nil {
|
|
||||||
store.Paths.DesktopIcon = iconPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the initial event history from the current runtime state. Jobs and
|
// 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
|
// 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()
|
refresh()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
svc.Start(scheduler.NewRealClock())
|
svc.Start()
|
||||||
|
|
||||||
tabs := container.NewAppTabs(
|
tabs := container.NewAppTabs(
|
||||||
container.NewTabItemWithIcon("Jobs", theme.ListIcon(), jobsPanel),
|
container.NewTabItemWithIcon("Jobs", theme.ListIcon(), jobsPanel),
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/app"
|
"gitea.mixdep.ru/mix/gosentry/src/app"
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/platform/autostart"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
@@ -28,7 +27,7 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|||||||
startOnLogin.SetChecked(store.Config.StartOnLogin)
|
startOnLogin.SetChecked(store.Config.StartOnLogin)
|
||||||
autostartStatus := widget.NewLabel("")
|
autostartStatus := widget.NewLabel("")
|
||||||
refreshAutostartStatus := func() {
|
refreshAutostartStatus := func() {
|
||||||
ok, message := autostart.AutostartStatus(store.Config.StartOnLogin, store.Paths.ExecutablePath)
|
ok, message := svc.AutostartStatus()
|
||||||
if ok {
|
if ok {
|
||||||
autostartStatus.SetText("OK: " + message)
|
autostartStatus.SetText("OK: " + message)
|
||||||
return
|
return
|
||||||
@@ -97,9 +96,7 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|||||||
settingsStatus.SetText("Save failed: " + err.Error())
|
settingsStatus.SetText("Save failed: " + err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Autostart is platform integration the Service leaves to the caller (until
|
if err := svc.ApplyAutostart(); err != nil {
|
||||||
// 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 {
|
|
||||||
refreshAutostartStatus()
|
refreshAutostartStatus()
|
||||||
settingsStatus.SetText("Saved, autostart failed: " + err.Error())
|
settingsStatus.SetText("Saved, autostart failed: " + err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user