Refactoring complete: v0.4.0 architectural milestone #1
+1
-1
@@ -245,7 +245,7 @@ Track progress here. Mark tasks complete as they land and pass review.
|
||||
|
||||
### Phase 1 — Split flat `core` package
|
||||
- [x] T1.1 — Create `src/domain`; move Job/RunRecord/Config/etc
|
||||
- [ ] T1.2 — Create `src/platform/winproc`; move `configureHiddenWindow`
|
||||
- [x] T1.2 — Create `src/platform/winproc`; move `configureHiddenWindow`
|
||||
- [ ] T1.3 — Create `src/runner`; move runner logic
|
||||
- [ ] T1.4 — Create `src/scheduler`; move scheduler
|
||||
- [ ] T1.5 — Create `src/storage`; move store/paths
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/platform/winproc"
|
||||
)
|
||||
|
||||
const autostartName = "GoSentry"
|
||||
@@ -107,7 +108,7 @@ func createStartupShortcut(shortcutPath string, executablePath string, iconPath
|
||||
"GOSENTRY_WORKING_DIRECTORY="+workingDirectory,
|
||||
"GOSENTRY_ICON_PATH="+iconPath,
|
||||
)
|
||||
configureHiddenWindow(command)
|
||||
winproc.ConfigureHiddenWindow(command)
|
||||
if output, err := command.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("create startup shortcut: %w: %s", err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
@@ -125,7 +126,7 @@ func readShortcut(shortcutPath string) (string, string, error) {
|
||||
script := `[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); $shell = New-Object -ComObject WScript.Shell; $shortcut = $shell.CreateShortcut($env:GOSENTRY_SHORTCUT_PATH); [Console]::Out.Write($shortcut.TargetPath + [Environment]::NewLine + $shortcut.Arguments)`
|
||||
command := exec.Command("powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script)
|
||||
command.Env = append(os.Environ(), "GOSENTRY_SHORTCUT_PATH="+shortcutPath)
|
||||
configureHiddenWindow(command)
|
||||
winproc.ConfigureHiddenWindow(command)
|
||||
output, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("read startup shortcut: %w: %s", err, strings.TrimSpace(string(output)))
|
||||
@@ -155,7 +156,7 @@ func removeIfExists(path string) error {
|
||||
func cleanupLegacyRegistryAutostart() error {
|
||||
for _, name := range []string{legacyAutostartName, autostartName} {
|
||||
command := exec.Command("reg.exe", "delete", `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, "/v", name, "/f")
|
||||
configureHiddenWindow(command)
|
||||
winproc.ConfigureHiddenWindow(command)
|
||||
_ = command.Run()
|
||||
}
|
||||
return nil
|
||||
@@ -164,7 +165,7 @@ func cleanupLegacyRegistryAutostart() error {
|
||||
func legacyRegistryAutostartExists() bool {
|
||||
for _, name := range []string{legacyAutostartName, autostartName} {
|
||||
command := exec.Command("reg.exe", "query", `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, "/v", name)
|
||||
configureHiddenWindow(command)
|
||||
winproc.ConfigureHiddenWindow(command)
|
||||
if command.Run() == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
+3
-2
@@ -15,6 +15,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/platform/winproc"
|
||||
)
|
||||
|
||||
const commandTimeout = 30 * time.Second
|
||||
@@ -42,7 +43,7 @@ func RunJob(ctx context.Context, job *domain.Job, trigger string, logsDir string
|
||||
command := invocation.command
|
||||
command.WaitDelay = commandWaitDelay
|
||||
if invocation.hideWindow {
|
||||
configureHiddenWindow(command)
|
||||
winproc.ConfigureHiddenWindow(command)
|
||||
}
|
||||
command.Stdout = &stdout
|
||||
command.Stderr = &stderr
|
||||
@@ -176,7 +177,7 @@ func sanitizeFileName(name string) string {
|
||||
func startJobOnly(invocation commandInvocation, job domain.Job, started time.Time) (string, string, string) {
|
||||
command := invocation.command
|
||||
if invocation.hideWindow {
|
||||
configureHiddenWindow(command)
|
||||
winproc.ConfigureHiddenWindow(command)
|
||||
}
|
||||
err := command.Start()
|
||||
duration := time.Since(started).Round(time.Millisecond)
|
||||
|
||||
@@ -13,8 +13,3 @@ func shellCommand(ctx context.Context, command string) *exec.Cmd {
|
||||
return exec.CommandContext(ctx, "sh", "-c", command)
|
||||
}
|
||||
|
||||
func configureHiddenWindow(command *exec.Cmd) {
|
||||
// Non-Windows platforms do not create a new console window for sh -c from a
|
||||
// desktop process in the same way Windows does, so no extra process attribute
|
||||
// is required here.
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||
"gitea.mixdep.ru/mix/gosentry/src/platform/winproc"
|
||||
)
|
||||
|
||||
func TestRunJobLogFileAllHeaders(t *testing.T) {
|
||||
@@ -377,7 +378,7 @@ func TestShellCommandHidesWindow(t *testing.T) {
|
||||
if !invocation.hideWindow {
|
||||
t.Fatal("shell command should request hidden startup window")
|
||||
}
|
||||
configureHiddenWindow(invocation.command)
|
||||
winproc.ConfigureHiddenWindow(invocation.command)
|
||||
if invocation.command.SysProcAttr == nil || !invocation.command.SysProcAttr.HideWindow {
|
||||
t.Fatal("expected shell command to be hidden")
|
||||
}
|
||||
@@ -389,7 +390,7 @@ func TestShellCommandUsesWindowsSafeQuoting(t *testing.T) {
|
||||
}
|
||||
|
||||
command := shellCommand(context.Background(), `"C:\Program Files\FreeFileSync\FreeFileSync.exe" "D:\Local\Programs\FreeFileSync\Jobs\Auto.ffs_batch"`)
|
||||
configureHiddenWindow(command)
|
||||
winproc.ConfigureHiddenWindow(command)
|
||||
|
||||
want := `cmd.exe /S /C ""C:\Program Files\FreeFileSync\FreeFileSync.exe" "D:\Local\Programs\FreeFileSync\Jobs\Auto.ffs_batch""`
|
||||
if command.SysProcAttr == nil {
|
||||
|
||||
@@ -57,13 +57,3 @@ func startsWithWindowsRootedPath(command string) bool {
|
||||
(command[2] == '\\' || command[2] == '/')
|
||||
}
|
||||
|
||||
func configureHiddenWindow(command *exec.Cmd) {
|
||||
// GoSentry is a GUI scheduler, so child commands should not flash a console
|
||||
// window on Windows. CREATE_NO_WINDOW keeps cmd.exe and simple console tools
|
||||
// quiet while stdout/stderr are still captured through pipes.
|
||||
if command.SysProcAttr == nil {
|
||||
command.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
command.SysProcAttr.CreationFlags |= 0x08000000
|
||||
command.SysProcAttr.HideWindow = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
//go:build !windows
|
||||
|
||||
package winproc
|
||||
|
||||
import "os/exec"
|
||||
|
||||
// ConfigureHiddenWindow is a no-op on non-Windows platforms: launching sh -c
|
||||
// from a desktop process does not create a new console window in the same way
|
||||
// Windows does.
|
||||
func ConfigureHiddenWindow(command *exec.Cmd) {}
|
||||
@@ -0,0 +1,18 @@
|
||||
package winproc
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ConfigureHiddenWindow suppresses the console window that Windows would
|
||||
// otherwise flash when running a child process from a GUI application.
|
||||
// CREATE_NO_WINDOW keeps cmd.exe and simple console tools quiet while
|
||||
// stdout/stderr are still captured through pipes.
|
||||
func ConfigureHiddenWindow(command *exec.Cmd) {
|
||||
if command.SysProcAttr == nil {
|
||||
command.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
command.SysProcAttr.CreationFlags |= 0x08000000
|
||||
command.SysProcAttr.HideWindow = true
|
||||
}
|
||||
Reference in New Issue
Block a user