198 lines
4.9 KiB
Go
198 lines
4.9 KiB
Go
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
|
|
}
|