Sync from /srv/compose/unified-media-manager
This commit is contained in:
197
internal/worker/rss_sync.go
Normal file
197
internal/worker/rss_sync.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/TopherMayor/unified-media-manager/internal/config"
|
||||
"github.com/TopherMayor/unified-media-manager/internal/db"
|
||||
"github.com/TopherMayor/unified-media-manager/internal/service"
|
||||
)
|
||||
|
||||
type RSSSyncWorker struct {
|
||||
database *db.DB
|
||||
mediaSvc *service.MediaService
|
||||
searchSvc *service.SearchService
|
||||
dcSvc *service.DownloadClientService
|
||||
qualitySvc *service.QualityService
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewRSSSyncWorker(database *db.DB, mediaSvc *service.MediaService, searchSvc *service.SearchService, dcSvc *service.DownloadClientService, qualitySvc *service.QualityService, cfg *config.Config) *RSSSyncWorker {
|
||||
return &RSSSyncWorker{
|
||||
database: database,
|
||||
mediaSvc: mediaSvc,
|
||||
searchSvc: searchSvc,
|
||||
dcSvc: dcSvc,
|
||||
qualitySvc: qualitySvc,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *RSSSyncWorker) Name() string {
|
||||
return "rss_sync"
|
||||
}
|
||||
|
||||
func (w *RSSSyncWorker) CronExpr() string {
|
||||
return w.cfg.WorkerRSSSyncInterval
|
||||
}
|
||||
|
||||
func (w *RSSSyncWorker) Run(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
searched := 0
|
||||
grabs := 0
|
||||
errors := 0
|
||||
|
||||
seen := make(map[int64]bool)
|
||||
|
||||
missing, _, err := w.mediaSvc.SearchMissing(ctx, service.MediaFilters{PageSize: 100})
|
||||
if err != nil {
|
||||
slog.Error("failed to search missing media", "error", err)
|
||||
errors++
|
||||
}
|
||||
for i := range missing {
|
||||
seen[missing[i].ID] = true
|
||||
}
|
||||
|
||||
upgrades, _, err := w.mediaSvc.SearchUpgrades(ctx, service.MediaFilters{PageSize: 100})
|
||||
if err != nil {
|
||||
slog.Error("failed to search upgrade media", "error", err)
|
||||
errors++
|
||||
}
|
||||
for i := range upgrades {
|
||||
seen[upgrades[i].ID] = true
|
||||
}
|
||||
|
||||
var allItems []service.Media
|
||||
for _, m := range missing {
|
||||
allItems = append(allItems, m)
|
||||
}
|
||||
for _, m := range upgrades {
|
||||
if !seen[m.ID] {
|
||||
allItems = append(allItems, m)
|
||||
seen[m.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range allItems {
|
||||
var hasPending bool
|
||||
err := w.database.Pool.QueryRow(ctx,
|
||||
`SELECT EXISTS(SELECT 1 FROM download_queue WHERE media_id = $1 AND status IN ('pending', 'downloading'))`,
|
||||
item.ID).Scan(&hasPending)
|
||||
if err != nil {
|
||||
slog.Error("failed to check pending queue", "media_id", item.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
if hasPending {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = w.database.Pool.Exec(ctx,
|
||||
"UPDATE media SET last_search_at = NOW() WHERE id = $1", item.ID)
|
||||
if err != nil {
|
||||
slog.Error("failed to update last_search_at", "media_id", item.ID, "error", err)
|
||||
}
|
||||
|
||||
results, err := w.searchSvc.Search(ctx, service.SearchRequest{
|
||||
Query: item.Title,
|
||||
MediaType: item.MediaType,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("failed to search indexers", "media_id", item.ID, "title", item.Title, "error", err)
|
||||
errors++
|
||||
searched++
|
||||
continue
|
||||
}
|
||||
searched++
|
||||
|
||||
if len(results) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var filtered []service.SearchResult
|
||||
for _, r := range results {
|
||||
var blocked bool
|
||||
err := w.database.Pool.QueryRow(ctx,
|
||||
"SELECT EXISTS(SELECT 1 FROM blocklist WHERE release_title = $1)", r.Title).Scan(&blocked)
|
||||
if err != nil {
|
||||
slog.Error("failed to check blocklist", "error", err)
|
||||
continue
|
||||
}
|
||||
if blocked {
|
||||
continue
|
||||
}
|
||||
|
||||
if item.QualityProfileID != nil {
|
||||
profile, err := w.qualitySvc.GetByID(ctx, *item.QualityProfileID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get quality profile", "profile_id", *item.QualityProfileID, "error", err)
|
||||
filtered = append(filtered, r)
|
||||
continue
|
||||
}
|
||||
|
||||
if r.QualityTier != nil {
|
||||
allowed := false
|
||||
for _, name := range profile.AllowedQualities {
|
||||
if name == r.QualityTier.Name {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
|
||||
if len(filtered) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
best := filtered[0]
|
||||
for _, r := range filtered[1:] {
|
||||
bestRank := 0
|
||||
if best.QualityTier != nil {
|
||||
bestRank = best.QualityTier.Rank
|
||||
}
|
||||
rank := 0
|
||||
if r.QualityTier != nil {
|
||||
rank = r.QualityTier.Rank
|
||||
}
|
||||
if rank > bestRank || (rank == bestRank && r.Size > best.Size) {
|
||||
best = r
|
||||
}
|
||||
}
|
||||
|
||||
_, err = w.searchSvc.Grab(ctx, service.GrabRequest{
|
||||
DownloadURL: best.DownloadURL,
|
||||
Title: best.Title,
|
||||
MediaType: item.MediaType,
|
||||
Quality: best.Quality,
|
||||
IndexerName: best.IndexerName,
|
||||
MediaID: item.ID,
|
||||
}, w.dcSvc)
|
||||
if err != nil {
|
||||
slog.Error("failed to auto-grab release", "media_id", item.ID, "release", best.Title, "error", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
|
||||
status := "searching"
|
||||
if err := w.mediaSvc.Update(ctx, item.ID, item.MediaType, service.UpdateMediaRequest{Status: &status}); err != nil {
|
||||
slog.Error("failed to update media status", "media_id", item.ID, "error", err)
|
||||
}
|
||||
|
||||
slog.Info("auto-grabbed release", "media", item.Title, "release", best.Title, "quality", best.QualityTier.Name)
|
||||
grabs++
|
||||
}
|
||||
|
||||
slog.Info("rss sync completed", "searched", searched, "grabs", grabs, "errors", errors)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user