package api import ( "context" "net/http" "sync" "time" "github.com/ponzischeme89/memby/server/internal/store" ) // quietTimeState keeps the daily policy on the hot path without turning every television // request or scheduler tick into a database read. type quietTimeState struct { mu sync.RWMutex policy store.QuietTime } func (q *quietTimeState) get() store.QuietTime { q.mu.RLock() defer q.mu.RUnlock() return q.policy } func (q *quietTimeState) set(policy store.QuietTime) { q.mu.Lock() defer q.mu.Unlock() q.policy = policy } type quietTimeStatus struct { store.QuietTime Active bool `json:"active"` TimeZone string `json:"timeZone"` } func (s *Server) quietTimeStatus(now time.Time) quietTimeStatus { policy := s.quietTime.get() return quietTimeStatus{ QuietTime: policy, Active: store.QuietTimeActive(policy, now, s.sonarrLocation()), TimeZone: s.sonarrLocation().String(), } } func (s *Server) quietTimeActive() bool { return s.quietTimeStatus(time.Now()).Active } // ActivityPaused is the shared gate used by workers constructed outside the API package. func (s *Server) ActivityPaused() bool { return s.quietTimeActive() } func (s *Server) LoadQuietTime(ctx context.Context) error { policy, err := s.store.QuietTime(ctx) if err != nil { return err } s.quietTime.set(policy) return nil } // WatchQuietTime lets another gateway instance or a direct database edit take effect // without a restart. It is control-plane work and therefore continues during quiet time. func (s *Server) WatchQuietTime(ctx context.Context, interval time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if err := s.LoadQuietTime(ctx); err != nil { s.log.Warn("quiet-time refresh failed", "component", "quiet-time", "error", err) } } } } func (s *Server) quietTimeUnavailable(w http.ResponseWriter) { status := s.quietTimeStatus(time.Now()) message := status.Message if message == "" { message = store.DefaultQuietTimeMessage } w.Header().Set("Retry-After", "300") writeJSON(w, http.StatusServiceUnavailable, map[string]any{ "error": message, "maintenance": true, "quietTime": true, "message": message, }) } func (s *Server) rejectWorkDuringQuietTime(w http.ResponseWriter) bool { if !s.quietTimeActive() { return false } s.quietTimeUnavailable(w) return true } func (s *Server) quietTimeGate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if s.rejectWorkDuringQuietTime(w) { return } next.ServeHTTP(w, r) }) }