package wanted import ( "context" "fmt" "time" "github.com/jackc/pgx/v5/pgtype" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" "git.fabledsword.com/bvandeusen/minstrel/internal/release" ) // Upgrades (M502 #5524): a held album stays wanted when the copy the library // has is below the quality the operator settles for (acquisition_settings. // upgrade_until), or when one of its tracks is a video rip. The search // schedule then looks for a better release weekly, and the import replaces // the old tracks with the new ones, carrying their history across. // Why a held album is wanted again, matching wants_upgrade_check. const ( UpgradeQuality = "quality" UpgradeRip = "rip" ) // upgradeCheckEvery is how often held albums are judged. Their files change // only by an import or a scan, and the check reads every held album. const upgradeCheckEvery = time.Hour // UpgradeFor says why a held album is wanted again, if it is: a video rip // among its tracks always (a rip is no copy of the album, whatever the // setting), else a copy below until. until "" switches quality upgrades off. func UpgradeFor(held release.Held, judged bool, rips int, until release.Tier) string { switch { case rips > 0: return UpgradeRip case until == "" || !judged: return "" case release.Rank(held.Tier) < release.Rank(until): return UpgradeQuality } return "" } // heldVerdict is one held want as the check judged it. type heldVerdict struct { id pgtype.UUID upgrade, tier, words string oldUpgrade, oldTier, old string } func (v heldVerdict) changed() bool { return v.upgrade != v.oldUpgrade || v.tier != v.oldTier || v.words != v.old } // checkUpgrades judges every held album, at most once an hour, and records // which are wanted for a better copy. func (s *Service) checkUpgrades(ctx context.Context, res *PassResult) error { if !s.upgradesChecked.IsZero() && s.now().Sub(s.upgradesChecked) < upgradeCheckEvery { return nil } q := dbq.New(s.pool) settings, err := q.GetAcquisitionSettings(ctx) if err != nil { return fmt.Errorf("read settings: %w", err) } rows, err := q.ListHeldWantFormats(ctx) if err != nil { return fmt.Errorf("read held albums: %w", err) } until := release.Tier(settings.UpgradeUntil) var out []heldVerdict flush := func(id pgtype.UUID, group []dbq.ListHeldWantFormatsRow) { if len(group) == 0 { return } formats := make([]release.HeldFormat, 0, len(group)) rips := 0 for _, r := range group { formats = append(formats, release.HeldFormat{Format: r.FileFormat, Bytes: r.Bytes, Ms: r.DurationMs}) rips += int(r.Rips) } held, judged := release.HeldCopy(formats) v := heldVerdict{id: id, upgrade: UpgradeFor(held, judged, rips, until), oldUpgrade: group[0].Upgrade, oldTier: group[0].HeldTier, old: group[0].HeldQuality} if judged { v.tier, v.words = string(held.Tier), held.Words } if v.upgrade != "" { res.Upgrades++ } if v.changed() { out = append(out, v) } } var group []dbq.ListHeldWantFormatsRow for _, r := range rows { if len(group) > 0 && group[0].WantID != r.WantID { flush(group[0].WantID, group) group = group[:0] } group = append(group, r) } if len(group) > 0 { flush(group[0].WantID, group) } if len(out) > 0 { p := dbq.SetWantUpgradesParams{} for _, v := range out { p.Ids = append(p.Ids, v.id) p.Upgrades = append(p.Upgrades, v.upgrade) p.HeldTiers = append(p.HeldTiers, v.tier) p.HeldQualities = append(p.HeldQualities, v.words) } if err := q.SetWantUpgrades(ctx, p); err != nil { return fmt.Errorf("record upgrades: %w", err) } } if _, err := q.ClearHeldUpgrades(ctx); err != nil { return fmt.Errorf("clear upgrades: %w", err) } s.upgradesChecked = s.now() return nil }