107 lines
2.6 KiB
Go
107 lines
2.6 KiB
Go
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)
|
||
|
|
})
|
||
|
|
}
|