Files
wild-central/internal/dnsfilter/runner.go
Paul Payne 3172e56288 Standardize codebase consistency: naming, JSON tags, logging, error wrapping
- JSON tags: fix snake_case to camelCase in dnsmasq (configFile, domainsConfigured,
  lastRestart), crowdsec Machine (lastPush, lastHeartbeat), network (primaryIP,
  primaryInterface). cscli raw parsing structs keep snake_case to match CLI output.
- Error wrapping: fix %v to %w in enableAuthelia for proper error chain preservation
- Naming: rename dnsmasq.ConfigGenerator to dnsmasq.Manager (matches all other packages),
  rename ServiceStatus to Status in dnsmasq and haproxy (matches authelia, crowdsec, etc.)
- Logging: standardize all slog calls to use "component" key instead of message prefixes.
  Affects reconcile, dnsfilter, ddns — now consistent with dnsmasq, haproxy, nftables, sse.
2026-07-14 04:38:48 +00:00

116 lines
2.3 KiB
Go

package dnsfilter
import (
"context"
"log/slog"
"sync"
"time"
)
// Runner manages the background blocklist update goroutine.
type Runner struct {
mu sync.RWMutex
manager *Manager
cancel context.CancelFunc
trigger chan struct{}
running bool
onUpdate func() // called after lists are downloaded and compiled
}
// NewRunner creates a new DNS filter update runner.
func NewRunner(manager *Manager, onUpdate func()) *Runner {
return &Runner{
manager: manager,
trigger: make(chan struct{}, 1),
onUpdate: onUpdate,
}
}
// Start launches the background update goroutine.
// Safe to call multiple times — cancels the previous run first.
func (r *Runner) Start(parentCtx context.Context, intervalHours int) {
r.mu.Lock()
if r.cancel != nil {
r.cancel()
}
ctx, cancel := context.WithCancel(parentCtx)
r.cancel = cancel
r.running = true
r.mu.Unlock()
if intervalHours <= 0 {
intervalHours = 24
}
go r.run(ctx, intervalHours)
}
// Stop cancels the running goroutine.
func (r *Runner) Stop() {
r.mu.Lock()
defer r.mu.Unlock()
if r.cancel != nil {
r.cancel()
r.cancel = nil
}
r.running = false
}
// IsRunning returns whether the runner is active.
func (r *Runner) IsRunning() bool {
r.mu.RLock()
defer r.mu.RUnlock()
return r.running
}
// Trigger requests an immediate update (non-blocking).
func (r *Runner) Trigger() {
select {
case r.trigger <- struct{}{}:
default:
}
}
func (r *Runner) run(ctx context.Context, intervalHours int) {
interval := time.Duration(intervalHours) * time.Hour
slog.Info("runner started", "component", "dns-filter", "interval", interval)
// Immediate update on start
r.updateAndCompile(ctx)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
r.mu.Lock()
r.running = false
r.mu.Unlock()
slog.Info("runner stopped", "component", "dns-filter")
return
case <-ticker.C:
r.updateAndCompile(ctx)
case <-r.trigger:
r.updateAndCompile(ctx)
}
}
}
func (r *Runner) updateAndCompile(ctx context.Context) {
if err := r.manager.UpdateLists(ctx); err != nil {
slog.Error("update failed", "component", "dns-filter", "error", err)
}
if _, err := r.manager.Compile(); err != nil {
slog.Error("compile failed", "component", "dns-filter", "error", err)
return
}
if r.onUpdate != nil {
r.onUpdate()
}
}