mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
fix(cover): stop per-song over-fetch + log failed cover downloads (#944)
* fix(cover): stop per-song cover over-fetch (album/mf-* explosion) album_has_distinct_disc_covers returned true as soon as two tracks on the same disc had different cover ids. On Navidrome every song has its own mf-<id> coverArt, so almost every album was flagged "distinct disc covers" and backfill warmed one cover per track (~520k for ~170k tracks), filling album/ with mf-* dirs instead of ~one cover per album. Treat a release as having distinct disc covers only when each disc has a single consistent cover that differs across discs (genuine box set); per-song ids now collapse to one cover per album. Mirror the same fix in the TS albumHasDistinctDiscCovers used by on-demand warming. Adds regression tests on both sides. * docs(changelog): record per-song cover over-fetch fix (PR #944) Give the album/mf-* over-fetch fix its own [1.47.0] Fixed entry and a settingsCredits line under PR #944. * feat(cover): log failed cover downloads with album/artist name A non-200 (or network-failed) getCoverArt download was swallowed silently. Now the failure is logged with the resolved album/artist name and the server error, so a server refusing covers under backfill load (5xx/429/timeouts) is visible. - cover_resolve: describe_cover_entity() resolves a human label from the local index (album "Name" — Artist / artist "Name"), best-effort with id fallback. - cover_cache: log_cover_fetch_failure() in ensure_inner logs on the download error path; threads optional library_server_id through CoverCacheEnsureArgs so the name lookup happens only on failure. Backfill logs at normal level, on-demand misses at debug level.
This commit is contained in:
@@ -271,6 +271,7 @@ async fn ensure_one(
|
||||
username: session.username,
|
||||
password: session.password,
|
||||
library_bulk: true,
|
||||
library_server_id: Some(session.library_server_id),
|
||||
};
|
||||
let _ = CoverCacheState::ensure_inner(&st, &app, &args, Some(http_sem)).await;
|
||||
}
|
||||
|
||||
@@ -107,6 +107,10 @@ pub struct CoverCacheEnsureArgs {
|
||||
/// Library backfill: all derived tiers, no `cover:tier-ready` floods to the webview.
|
||||
#[serde(default)]
|
||||
pub library_bulk: bool,
|
||||
/// Library server id (DB key) — set by backfill so a failed fetch can be logged
|
||||
/// with the album/artist name. On-demand UI ensures leave it `None`.
|
||||
#[serde(default)]
|
||||
pub library_server_id: Option<String>,
|
||||
}
|
||||
|
||||
fn cover_dir_for_args(root: &Path, args: &CoverCacheEnsureArgs) -> PathBuf {
|
||||
@@ -266,7 +270,8 @@ impl CoverCacheState {
|
||||
} else {
|
||||
match download_cover_payload(&dir, &client, &http_sem, args).await {
|
||||
Ok(bytes) => CoverSource::Bytes(bytes),
|
||||
Err(_) => {
|
||||
Err(err) => {
|
||||
log_cover_fetch_failure(app, args, &err);
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let _ = std::fs::write(dir.join(COVER_FETCH_FAIL_MARKER), b"1");
|
||||
return Ok(CoverCacheEnsureResult {
|
||||
@@ -359,6 +364,41 @@ impl CoverCacheState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Log a non-200 / failed cover download with the album/artist name when known.
|
||||
/// Backfill fetches (`library_bulk`) log at the normal level — the user wants to
|
||||
/// see which covers a busy server refused; incidental on-demand UI misses stay at
|
||||
/// the debug level so they don't spam the normal log.
|
||||
fn log_cover_fetch_failure(app: &AppHandle, args: &CoverCacheEnsureArgs, err: &str) {
|
||||
let label = args
|
||||
.library_server_id
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.and_then(|lib_id| {
|
||||
app.try_state::<LibraryRuntime>().and_then(|rt| {
|
||||
psysonic_library::cover_resolve::describe_cover_entity(
|
||||
&rt.store,
|
||||
lib_id,
|
||||
&args.cache_kind,
|
||||
&args.cache_entity_id,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| format!("{} {}", args.cache_kind, args.cache_entity_id));
|
||||
if args.library_bulk {
|
||||
crate::app_eprintln!(
|
||||
"[cover-backfill] fetch failed for {label} (coverArtId={}, tier={}): {err}",
|
||||
args.cover_art_id,
|
||||
args.tier
|
||||
);
|
||||
} else {
|
||||
crate::app_deprintln!(
|
||||
"[cover] fetch failed for {label} (coverArtId={}, tier={}): {err}",
|
||||
args.cover_art_id,
|
||||
args.tier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_tier_ready(app: &AppHandle, args: &CoverCacheEnsureArgs, tier: u32, path: &Path) {
|
||||
let Ok(meta) = std::fs::metadata(path) else {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user