95 lines
1.9 KiB
Go
95 lines
1.9 KiB
Go
// Package daemon coordinates periodic sync cycles and change hooks.
|
|
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"s3watch/internal/syncer"
|
|
)
|
|
|
|
// Config controls daemon scheduling behavior.
|
|
type Config struct {
|
|
Interval time.Duration
|
|
Once bool
|
|
}
|
|
|
|
// Syncer synchronizes remote state into local state.
|
|
type Syncer interface {
|
|
Sync(context.Context) (syncer.Result, error)
|
|
}
|
|
|
|
// HookRunner runs after a sync cycle applies changes.
|
|
type HookRunner interface {
|
|
Run(context.Context, syncer.Result) error
|
|
}
|
|
|
|
// Run executes sync cycles until the context is canceled.
|
|
func Run(ctx context.Context, cfg Config, syncService Syncer, runner HookRunner, logger *slog.Logger) error {
|
|
if cfg.Interval <= 0 {
|
|
return fmt.Errorf("interval must be greater than zero")
|
|
}
|
|
if syncService == nil {
|
|
return fmt.Errorf("syncer is required")
|
|
}
|
|
if logger == nil {
|
|
logger = slog.Default()
|
|
}
|
|
|
|
for {
|
|
if err := runCycle(ctx, syncService, runner, logger, cfg.Once); err != nil {
|
|
if cfg.Once {
|
|
return err
|
|
}
|
|
logger.Error("sync cycle failed", "error", err)
|
|
}
|
|
|
|
if cfg.Once {
|
|
return nil
|
|
}
|
|
|
|
timer := time.NewTimer(cfg.Interval)
|
|
select {
|
|
case <-ctx.Done():
|
|
if !timer.Stop() {
|
|
select {
|
|
case <-timer.C:
|
|
default:
|
|
}
|
|
}
|
|
return nil
|
|
case <-timer.C:
|
|
}
|
|
}
|
|
}
|
|
|
|
func runCycle(ctx context.Context, syncService Syncer, runner HookRunner, logger *slog.Logger, strict bool) error {
|
|
result, err := syncService.Sync(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("syncing: %w", err)
|
|
}
|
|
|
|
logger.Info("sync cycle completed",
|
|
"downloaded", result.Downloaded,
|
|
"uploaded", result.Uploaded,
|
|
"updated", result.Updated,
|
|
"deleted", result.Deleted,
|
|
"unchanged", result.Unchanged,
|
|
)
|
|
|
|
if !result.Changed() || runner == nil {
|
|
return nil
|
|
}
|
|
|
|
if err := runner.Run(ctx, result); err != nil {
|
|
if strict {
|
|
return fmt.Errorf("running hook: %w", err)
|
|
}
|
|
logger.Error("hook failed", "error", err)
|
|
}
|
|
|
|
return nil
|
|
}
|