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:
83
internal/syncthing/events.go
Normal file
83
internal/syncthing/events.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user