feat(server/coverart): album sidecar falls back to managed cache
When the album sidecar location isn't writable (e.g. read-only music mount in a container), the album-cover write previously failed and left cover_art_source NULL forever — every scan retried the same provider call and re-failed at the same os.WriteFile. With a managed data_dir available, we fall back to <DataDir>/album-art/<album_id>/ cover.jpg and record that path on the album row. The cover-serving HTTP path already serves whatever cover_art_path stores, so the client sees the art uniformly regardless of where it landed. DataDir is opt-in via the Enricher field (cmd/minstrel/main.go sets it from cfg.Storage.DataDir on the production path; tests leave it empty and behave as before).
This commit is contained in:
@@ -19,10 +19,17 @@ import (
|
||||
// for one or many albums. Owns no DB pool itself; receives one per
|
||||
// call so the boot backfill, post-scan trigger, and admin endpoints
|
||||
// share state.
|
||||
//
|
||||
// DataDir is the optional managed-cache root. When set, sidecar writes
|
||||
// that fail (e.g. read-only music mount) fall back to
|
||||
// <DataDir>/album-art/<album_id>/cover.jpg so enrichment isn't gated
|
||||
// on the music directory being writable. Empty disables fallback;
|
||||
// tests use empty since they operate on writable tempdirs.
|
||||
type Enricher struct {
|
||||
pool *pgxpool.Pool
|
||||
logger *slog.Logger
|
||||
settings *SettingsService
|
||||
DataDir string
|
||||
}
|
||||
|
||||
// NewEnricher constructs an Enricher.
|
||||
@@ -103,7 +110,22 @@ func (e *Enricher) EnrichAlbum(ctx context.Context, albumID pgtype.UUID) error {
|
||||
}
|
||||
albumDir := filepath.Dir(*row.TrackFilePath)
|
||||
dest := filepath.Join(albumDir, "cover.jpg")
|
||||
if werr := os.WriteFile(dest, body, 0o644); werr != nil {
|
||||
werr := os.WriteFile(dest, body, 0o644)
|
||||
if werr != nil && e.DataDir != "" {
|
||||
// Sidecar location isn't writable (e.g. RO music mount).
|
||||
// Fall back to the managed cache so enrichment isn't
|
||||
// gated on the library being writable.
|
||||
fallbackDir := filepath.Join(e.DataDir, "album-art", uuidString(albumID))
|
||||
if mkErr := os.MkdirAll(fallbackDir, 0o755); mkErr != nil {
|
||||
e.logger.Error("coverart: managed-cache mkdir failed; leaving NULL",
|
||||
"album_id", uuidString(albumID), "fallback_dir", fallbackDir,
|
||||
"sidecar_err", werr, "mkdir_err", mkErr)
|
||||
return nil
|
||||
}
|
||||
dest = filepath.Join(fallbackDir, "cover.jpg")
|
||||
werr = os.WriteFile(dest, body, 0o644)
|
||||
}
|
||||
if werr != nil {
|
||||
e.logger.Error("coverart: write file failed; leaving NULL",
|
||||
"album_id", uuidString(albumID), "path", dest,
|
||||
"provider", provider.ID(), "err", werr)
|
||||
|
||||
Reference in New Issue
Block a user