feat: Minstrel no longer talks to Lidarr: its connection, Delete via Lidarr and the unmonitor options are gone, and the track reports are plain quarantine (M502 #5525)
release / govulncheck (push) Successful in 15s
release / web (push) Failing after 17s
release / go (push) Successful in 1m13s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m9s
release / Build signed APK (releases and dev) (push) Successful in 5m19s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 15s
release / web (push) Failing after 17s
release / go (push) Successful in 1m13s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m9s
release / Build signed APK (releases and dev) (push) Successful in 5m19s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Migration 0091 drops lidarr_config and renames the quarantine tables, types, indexes and the history column off the Lidarr name. The Integrations Lidarr card, the Reports "Delete via Lidarr" action, the merge and remove-track unmonitor options, the Lidarr audit actions and error codes go, on the server, web and Android. The README describes acquisition as it is now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,6 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -20,13 +19,11 @@ import (
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/coverart"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/eventbus"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/library"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarr"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarrconfig"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarrquarantine"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/mailer"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/netsettings"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/playevents"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/playlists"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/quarantine"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/reacquisition"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/recsettings"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/requests"
|
||||
@@ -36,27 +33,6 @@ import (
|
||||
"git.fabledsword.com/bvandeusen/minstrel/web"
|
||||
)
|
||||
|
||||
// lidarrUnmonitorAdapter wires the per-call lidarrClientFn factory pattern
|
||||
// (used elsewhere in this package so admin Lidarr config edits take effect
|
||||
// without restart) into the tracks.LidarrUnmonitorer interface that
|
||||
// internal/tracks expects. When the factory returns nil (Lidarr disabled)
|
||||
// we surface a sentinel error — tracks.Service treats UnmonitorTrack
|
||||
// failures as non-fatal, so this collapses to a `lidarr_unmonitor_failed`
|
||||
// flag in the response and the destructive part still runs.
|
||||
type lidarrUnmonitorAdapter struct {
|
||||
fn func() *lidarr.Client
|
||||
}
|
||||
|
||||
var errLidarrDisabled = errors.New("lidarr disabled")
|
||||
|
||||
func (a lidarrUnmonitorAdapter) UnmonitorTrack(ctx context.Context, trackMbid, albumMbid string) error {
|
||||
c := a.fn()
|
||||
if c == nil {
|
||||
return errLidarrDisabled
|
||||
}
|
||||
return c.UnmonitorTrack(ctx, trackMbid, albumMbid)
|
||||
}
|
||||
|
||||
// ScanTrigger is the subset of the scanner the HTTP handler needs. Kept as an
|
||||
// interface so tests can stub it without touching the DB. The progressCb
|
||||
// parameter (added in m7-scan-progress) lets the orchestrator drive partial-
|
||||
@@ -84,7 +60,7 @@ type Server struct {
|
||||
LibraryScanner *library.Scanner
|
||||
ScanCfg library.RunScanConfig
|
||||
// Bus is the live-event bus shared with background workers (the
|
||||
// lidarr reconciler, scan workers) constructed in cmd/minstrel/main.go.
|
||||
// request reconciler, scan workers) constructed in cmd/minstrel/main.go.
|
||||
// When nil, Router() constructs a local fallback (test contexts).
|
||||
Bus *eventbus.Bus
|
||||
// PlaylistScheduler fires per-user daily system-playlist builds at
|
||||
@@ -159,18 +135,6 @@ func (s *Server) Router() http.Handler {
|
||||
s.EventsCfg.SkipMaxCompletionRatio,
|
||||
s.EventsCfg.SkipMaxDurationPlayedMs,
|
||||
)
|
||||
lidarrCfg := lidarrconfig.New(s.Pool)
|
||||
// Per-call client factory: re-reads config so an admin save in
|
||||
// Admin › Acquisition takes effect immediately without restart.
|
||||
// Returns nil when Lidarr is disabled, which the Service-layer
|
||||
// methods translate to ErrLidarrDisabled.
|
||||
lidarrClientFn := func() *lidarr.Client {
|
||||
cfg, err := lidarrCfg.Get(context.Background())
|
||||
if err != nil || !cfg.Enabled || cfg.BaseURL == "" || cfg.APIKey == "" {
|
||||
return nil
|
||||
}
|
||||
return lidarr.NewClient(cfg.BaseURL, cfg.APIKey)
|
||||
}
|
||||
requestSvc := requests.NewService(s.Pool)
|
||||
reacqSettings := s.ReacqSettings
|
||||
if reacqSettings == nil {
|
||||
@@ -185,13 +149,13 @@ func (s *Server) Router() http.Handler {
|
||||
s.Logger.Warn("reacquisition settings unavailable; serving defaults", "err", raErr)
|
||||
}
|
||||
}
|
||||
lidarrQuar := lidarrquarantine.NewService(s.Pool, lidarrCfg, lidarrClientFn, s.DataDir)
|
||||
tracksSvc := tracks.NewService(s.Pool, s.Logger, lidarrUnmonitorAdapter{fn: lidarrClientFn}, s.DataDir)
|
||||
quarantineSvc := quarantine.NewService(s.Pool, s.DataDir)
|
||||
tracksSvc := tracks.NewService(s.Pool, s.Logger, s.DataDir)
|
||||
playlistsSvc := playlists.NewService(s.Pool, s.Logger, s.DataDir)
|
||||
smtpSender := mailer.NewSMTPSender(s.Pool, s.Logger.With("component", "mailer"))
|
||||
// Live-event bus for SSE subscribers (#392). Constructed per-process;
|
||||
// producers in playevents / requests / scanner publish into
|
||||
// the same instance. Background workers (lidarr reconciler, scan
|
||||
// the same instance. Background workers (request reconciler, scan
|
||||
// scheduler) live in cmd/minstrel/main.go where they're constructed
|
||||
// before Router() runs; they read s.Bus directly so they share this
|
||||
// process's bus. When Router() runs before main set the bus (tests,
|
||||
@@ -237,7 +201,7 @@ func (s *Server) Router() http.Handler {
|
||||
}
|
||||
acoustIDLookup = library.NewAcoustIDLookupWorker(s.Pool, s.Logger, acoustIDSettings, nil)
|
||||
}
|
||||
api.Mount(r, s.Pool, s.Logger, writer, s.RecommendationCfg, recSettings, lidarrCfg, requestSvc, lidarrQuar, tracksSvc, playlistsSvc, s.CoverEnricher, s.CoverSettings, s.TagSettings, s.LibraryScanner, s.ScanCfg, s.DataDir, smtpSender, bus, s.PlaylistScheduler, s.StreamSecret, netSettings, reacqSettings, fpSettings, loudSettings, acoustIDLookup)
|
||||
api.Mount(r, s.Pool, s.Logger, writer, s.RecommendationCfg, recSettings, requestSvc, quarantineSvc, tracksSvc, playlistsSvc, s.CoverEnricher, s.CoverSettings, s.TagSettings, s.LibraryScanner, s.ScanCfg, s.DataDir, smtpSender, bus, s.PlaylistScheduler, s.StreamSecret, netSettings, reacqSettings, fpSettings, loudSettings, acoustIDLookup)
|
||||
// /api/admin/scan is the only admin route owned by the server package
|
||||
// (it needs the Scanner). Register it as a single inline-middleware
|
||||
// route — using r.Route("/api/admin", ...) here would create a second
|
||||
|
||||
@@ -155,7 +155,7 @@ func TestRouter_RestPathNotSwallowedBySPA(t *testing.T) {
|
||||
// in server.go was creating a second chi subtree at the same prefix as the
|
||||
// nested admin Route inside api.Mount. chi.Walk shows both subtrees as
|
||||
// registered, but runtime routing dispatch only matches one — every admin
|
||||
// endpoint registered by api.Mount (Lidarr config, quarantine, etc.)
|
||||
// endpoint registered by api.Mount (summary, quarantine, etc.)
|
||||
// silently 404'd for authenticated callers in production.
|
||||
//
|
||||
// chi.Walk enumeration would NOT have caught this (both branches are in
|
||||
@@ -212,7 +212,7 @@ func TestRouter_AdminSubtreeNotShadowed(t *testing.T) {
|
||||
ts := httptest.NewServer(s.Router())
|
||||
defer ts.Close()
|
||||
|
||||
// Each path is registered by a different package: lidarr/config from
|
||||
// Each path is registered by a different package: admin/summary from
|
||||
// api.Mount and admin/scan from server.Router. If chi runtime dispatch
|
||||
// only routes one /api/admin subtree, one of these returns 404 — that
|
||||
// IS the bug we shipped in 06a1fe1.
|
||||
@@ -221,9 +221,9 @@ func TestRouter_AdminSubtreeNotShadowed(t *testing.T) {
|
||||
path string
|
||||
owner string
|
||||
}{
|
||||
{http.MethodGet, "/api/admin/lidarr/config", "api.Mount"},
|
||||
{http.MethodGet, "/api/admin/summary", "api.Mount"},
|
||||
// /api/admin/scan would actually trigger a real library scan via
|
||||
// stubScanner; we don't include it. The lidarr/config path is the
|
||||
// stubScanner; we don't include it. The admin/summary path is the
|
||||
// canonical regression check.
|
||||
}
|
||||
for _, tc := range cases {
|
||||
|
||||
Reference in New Issue
Block a user