package api import ( "encoding/json" "errors" "net/http" "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/library" ) // loudnessCoverageResp is the wire shape for GET /api/admin/library/loudness // (M464 #4995). measured + silent + unreadable + pending = total; missing // tracks are not counted. Enabled travels with the counts because with // analysis off, pending never shrinks, and a gauge implying progress would be // promising work nothing is doing. type loudnessCoverageResp struct { Total int64 `json:"total"` Measured int64 `json:"measured"` Silent int64 `json:"silent"` Unreadable int64 `json:"unreadable"` Pending int64 `json:"pending"` Enabled bool `json:"enabled"` } // handleGetLoudnessCoverage implements GET /api/admin/library/loudness: how // far the loudness backfill has got. Always 200; zeros on an empty library. func (h *handlers) handleGetLoudnessCoverage(w http.ResponseWriter, r *http.Request) { row, err := library.LoudnessCoverage(r.Context(), h.pool) if err != nil { writeErrWithLog(w, h.logger, "admin: get loudness coverage", apierror.InternalMsg("lookup failed", err)) return } writeJSON(w, http.StatusOK, loudnessCoverageResp{ Total: row.Total, Measured: row.Measured, Silent: row.Silent, Unreadable: row.Unreadable, Pending: row.Pending, Enabled: h.loudnessSettings.Get().Enabled, }) } // loudnessSettingsBody is the wire shape for GET and PUT // /api/admin/library/loudness-settings. type loudnessSettingsBody struct { Enabled bool `json:"enabled"` BackfillConcurrency int32 `json:"backfill_concurrency"` LeveledCacheMB int32 `json:"leveled_cache_mb"` } func loudnessSettingsBodyOf(s library.LoudnessSettings) loudnessSettingsBody { return loudnessSettingsBody{ Enabled: s.Enabled, BackfillConcurrency: s.BackfillConcurrency, LeveledCacheMB: s.LeveledCacheMB, } } // handleGetLoudnessSettings implements GET /api/admin/library/loudness-settings. func (h *handlers) handleGetLoudnessSettings(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, loudnessSettingsBodyOf(h.loudnessSettings.Get())) } // handleUpdateLoudnessSettings implements PUT /api/admin/library/loudness-settings. // A whole-row write: a body that leaves out the concurrency decodes it as zero, // which is refused rather than saved. func (h *handlers) handleUpdateLoudnessSettings(w http.ResponseWriter, r *http.Request) { var req loudnessSettingsBody if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeErr(w, apierror.BadRequest("invalid_body", "malformed JSON")) return } saved, err := h.loudnessSettings.Set(r.Context(), library.LoudnessSettings{ Enabled: req.Enabled, BackfillConcurrency: req.BackfillConcurrency, LeveledCacheMB: req.LeveledCacheMB, }) if err != nil { if errors.Is(err, library.ErrLoudnessSettingOutOfRange) { writeErr(w, apierror.BadRequest("invalid_setting", err.Error())) return } writeErrWithLog(w, h.logger, "admin loudness settings: update failed", apierror.Internal(err)) return } writeJSON(w, http.StatusOK, loudnessSettingsBodyOf(saved)) }