Implement SyncWarden v0.1.0
Some checks failed
Release / build (push) Failing after 19s

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>
This commit is contained in:
Axel Meyer
2026-03-03 21:16:28 +01:00
parent 2256df9dd7
commit 34a1a94502
30 changed files with 2156 additions and 38 deletions

View File

@@ -0,0 +1,83 @@
package syncthing
import (
"log"
"sync"
"time"
)
// EventHandler is called for each batch of new events.
type EventHandler func(events []Event)
// EventListener long-polls the Syncthing event API.
type EventListener struct {
client *Client
handler EventHandler
sinceID int
stopCh chan struct{}
wg sync.WaitGroup
}
// NewEventListener creates a new event listener.
func NewEventListener(client *Client, sinceID int, handler EventHandler) *EventListener {
return &EventListener{
client: client,
handler: handler,
sinceID: sinceID,
stopCh: make(chan struct{}),
}
}
// Start begins long-polling in a goroutine.
func (el *EventListener) Start() {
el.wg.Add(1)
go el.loop()
}
// Stop stops the event listener and waits for it to finish.
func (el *EventListener) Stop() {
close(el.stopCh)
el.wg.Wait()
}
// LastEventID returns the last processed event ID.
func (el *EventListener) LastEventID() int {
return el.sinceID
}
func (el *EventListener) loop() {
defer el.wg.Done()
backoff := time.Second
maxBackoff := 30 * time.Second
for {
select {
case <-el.stopCh:
return
default:
}
events, err := el.client.Events(el.sinceID, 30)
if err != nil {
log.Printf("event poll error: %v", err)
select {
case <-el.stopCh:
return
case <-time.After(backoff):
}
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
continue
}
backoff = time.Second
if len(events) > 0 {
el.sinceID = events[len(events)-1].ID
el.handler(events)
}
}
}