Files
minstrel/internal/wanted/upgrade.go
T
bvandeusenandClaude Opus 5.5 fcf5b7e5a8 docs: wanted, dbtest, recommendation and playlists comments say what the code does (#6277)
The same rewrite as d191a282, for four packages whose comments carried the
most history: tuning stories, quotes from the operator, single-install
measurements in library_scale.go, and 'used to' accounts of bugs. The
constraint each story protected stays as a present-tense reason (why tier 3
is reduced not removed, why the randomised arms take an order seed, why the
seed artist is eligible in Songs-like). Comments naming system_cron.go and
config.RecommendationConfig, neither of which holds what they claimed, now
name the scheduler and internal/recsettings.

Checked: non-comment tokens identical to HEAD; gofmt clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:19:15 -04:00

126 lines
3.7 KiB
Go

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: a held album stays wanted when the copy the library has is below
// the quality the install 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
}