0.2.52
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/sonarr"
|
||||
"github.com/ponzischeme89/memby/server/internal/store"
|
||||
)
|
||||
|
||||
// WatchSonarrLifecycle seeds the durable status history on startup, then refreshes it
|
||||
// daily. History stores changes rather than identical daily snapshots: it still records
|
||||
// the complete lifecycle while making an active-to-cancelled transition unambiguous.
|
||||
func (s *Server) WatchSonarrLifecycle(ctx context.Context, interval time.Duration) {
|
||||
if s.sonarr == nil || interval <= 0 {
|
||||
return
|
||||
}
|
||||
scan := func() {
|
||||
if err := s.scanSonarrLifecycle(ctx); err != nil && ctx.Err() == nil {
|
||||
s.log.Warn("Sonarr lifecycle scan failed", "error", err)
|
||||
}
|
||||
}
|
||||
scan()
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
scan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) scanSonarrLifecycle(ctx context.Context) error {
|
||||
series, err := s.sonarr.Series(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read Sonarr series: %w", err)
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
observations := make([]store.SonarrSeriesStatus, 0, len(series))
|
||||
for _, item := range series {
|
||||
key := sonarrSeriesStatusKey(item)
|
||||
if key == "" || strings.TrimSpace(item.Status) == "" {
|
||||
continue
|
||||
}
|
||||
observations = append(observations, store.SonarrSeriesStatus{
|
||||
SeriesKey: key, SonarrSeriesID: item.ID, TVDBID: item.TVDBID,
|
||||
Title: item.Title, Year: item.Year, Status: item.Status, ObservedAt: now,
|
||||
})
|
||||
}
|
||||
changes, err := s.store.RecordSonarrSeriesStatuses(ctx, observations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cancellations := make([]store.SonarrSeriesStatusChange, 0, len(changes))
|
||||
for _, change := range changes {
|
||||
if sonarrBecameCancelled(change.PreviousStatus, change.Current.Status) {
|
||||
cancellations = append(cancellations, change)
|
||||
}
|
||||
}
|
||||
if len(cancellations) == 0 {
|
||||
s.log.Info("Sonarr lifecycle scan complete", "series", len(observations), "changes", len(changes))
|
||||
return nil
|
||||
}
|
||||
|
||||
users, err := s.store.KnownUsers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
preferences := map[string]store.NotificationPreferences{}
|
||||
preferenceErrors := map[string]bool{}
|
||||
notifications := 0
|
||||
for _, change := range cancellations {
|
||||
for _, user := range users {
|
||||
prefs, ok := preferences[user.ID]
|
||||
if !ok && !preferenceErrors[user.ID] {
|
||||
prefs, err = s.store.NotificationPreferences(ctx, user.ID)
|
||||
if err != nil {
|
||||
preferenceErrors[user.ID] = true
|
||||
s.log.Warn("notification preferences unavailable during Sonarr lifecycle scan",
|
||||
"user", user.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
preferences[user.ID] = prefs
|
||||
}
|
||||
if preferenceErrors[user.ID] || !prefs.Enabled {
|
||||
continue
|
||||
}
|
||||
eventAt := change.Current.ObservedAt
|
||||
sourceKey := fmt.Sprintf("show-cancelled:%s:%d", change.Current.SeriesKey, change.HistoryID)
|
||||
message := change.Current.Title + " is now listed as cancelled in Sonarr."
|
||||
if err := s.store.UpsertNotification(
|
||||
ctx, user.ID, sourceKey, "show-cancelled", "",
|
||||
"Show cancelled", message, &eventAt,
|
||||
); err != nil {
|
||||
s.log.Warn("Sonarr cancellation notification failed",
|
||||
"user", user.ID, "show", change.Current.Title, "error", err)
|
||||
continue
|
||||
}
|
||||
notifications++
|
||||
}
|
||||
}
|
||||
s.log.Info("Sonarr lifecycle scan complete",
|
||||
"series", len(observations), "changes", len(changes),
|
||||
"cancelled", len(cancellations), "notifications", notifications)
|
||||
return nil
|
||||
}
|
||||
|
||||
func sonarrSeriesStatusKey(series sonarr.Series) string {
|
||||
if series.TVDBID > 0 {
|
||||
return "tvdb:" + strconv.Itoa(series.TVDBID)
|
||||
}
|
||||
if series.ID > 0 {
|
||||
return "sonarr:" + strconv.Itoa(series.ID)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func sonarrBecameCancelled(previous, current string) bool {
|
||||
previous = strings.ToLower(strings.TrimSpace(previous))
|
||||
current = strings.ToLower(strings.TrimSpace(current))
|
||||
active := previous == "continuing" || previous == "upcoming"
|
||||
cancelled := current == "ended" || current == "deleted" ||
|
||||
current == "cancelled" || current == "canceled"
|
||||
return active && cancelled
|
||||
}
|
||||
Reference in New Issue
Block a user