0.2.71
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user