Files
syncwarden/internal/syncthing/process.go
Axel Meyer 34a1a94502
Some checks failed
Release / build (push) Failing after 19s
Implement SyncWarden v0.1.0
Full Syncthing tray wrapper with:
- System tray with 5 icon states (idle/syncing/paused/error/disconnected)
- Syncthing REST API client with auto-discovered API key
- Long-polling event listener for real-time status
- Transfer rate monitoring, folder tracking, recent files, conflict counting
- Full context menu with folders, recent files, settings toggles
- Embedded admin panel binary (webview, requires CGO)
- OS notifications via beeep (per-event configurable)
- Syncthing process management with auto-restart
- Cross-platform installer with autostart
- CI pipeline for Linux (.deb + .tar.gz) and Windows (.zip)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 21:16:28 +01:00

150 lines
2.4 KiB
Go

package syncthing
import (
"log"
"os/exec"
"sync"
"time"
)
// Process manages the Syncthing child process lifecycle.
type Process struct {
mu sync.Mutex
cmd *exec.Cmd
stopCh chan struct{}
wg sync.WaitGroup
running bool
restarts int
}
// NewProcess creates a new Syncthing process manager.
func NewProcess() *Process {
return &Process{
stopCh: make(chan struct{}),
}
}
// Start launches Syncthing and monitors it (restarts on crash).
func (p *Process) Start() error {
p.mu.Lock()
if p.running {
p.mu.Unlock()
return nil
}
p.running = true
p.mu.Unlock()
p.wg.Add(1)
go p.supervise()
return nil
}
// Stop terminates the Syncthing process.
func (p *Process) Stop() {
p.mu.Lock()
if !p.running {
p.mu.Unlock()
return
}
p.running = false
p.mu.Unlock()
close(p.stopCh)
p.mu.Lock()
cmd := p.cmd
p.mu.Unlock()
if cmd != nil && cmd.Process != nil {
_ = cmd.Process.Kill()
}
p.wg.Wait()
}
// IsRunning returns whether the process is currently running.
func (p *Process) IsRunning() bool {
p.mu.Lock()
defer p.mu.Unlock()
return p.running && p.cmd != nil
}
func (p *Process) supervise() {
defer p.wg.Done()
backoff := 2 * time.Second
maxBackoff := 30 * time.Second
for {
select {
case <-p.stopCh:
return
default:
}
cmd := createSyncthingCmd()
if cmd == nil {
log.Println("syncthing binary not found in PATH")
select {
case <-p.stopCh:
return
case <-time.After(maxBackoff):
}
continue
}
p.mu.Lock()
p.cmd = cmd
p.mu.Unlock()
log.Printf("starting syncthing (pid will follow)")
err := cmd.Start()
if err != nil {
log.Printf("failed to start syncthing: %v", err)
select {
case <-p.stopCh:
return
case <-time.After(backoff):
}
continue
}
log.Printf("syncthing started (pid %d)", cmd.Process.Pid)
err = cmd.Wait()
p.mu.Lock()
p.cmd = nil
running := p.running
p.restarts++
p.mu.Unlock()
if !running {
return
}
if err != nil {
log.Printf("syncthing exited: %v (will restart in %v)", err, backoff)
} else {
log.Printf("syncthing exited normally (will restart in %v)", backoff)
}
select {
case <-p.stopCh:
return
case <-time.After(backoff):
}
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
}
}
func findSyncthing() string {
path, err := exec.LookPath("syncthing")
if err != nil {
return ""
}
return path
}