release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m23s
release / go (push) Successful in 1m41s
release / integration (push) Successful in 5m20s
release / android (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 5m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 27s
release / Verify release artifacts (tag releases only) (push) Skipped
- acquisition_settings.upgrade_until (default High quality lossy, Lidarr's cutoff, note #5527; "" is off), an "Upgrade until" row under Searching. - The wanted worker judges held albums hourly: tier per format from size per second (the scanner records no bitrate), the album's worst. Below the cutoff it is wanted for quality; a video rip among its tracks always. - The schedule searches upgrades weekly, after open wants; a release no better than the copy held is refused ("not_better"). - The import matches each file to the track it would replace (recording, then disc/track) and replaces it when it is a rip or the file is a better tier: over the same path in one rename, or filed beside it and the old track folded in (library.ReplaceTrack: file first, then foldTrackInto). Nothing better: the release is set aside. - Collection › Upgrades (Lidarr's Cutoff Unmet); Suspect sources says when a clean copy was looked for instead of Lidarr's search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
3.7 KiB
Go
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 (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
|
|
}
|