Files

107 lines
2.6 KiB
Go
Raw Permalink Normal View History

2026-08-17 07:34:23 +12:00
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)
})
}