Files
Psychotoxical-psysonic/src/cover/diskSrcLookup.ts
T
Psychotoxical 184501744b fix(cover): restore full-resolution album and artist covers (#1205)
* fix(cover): build cover tiers from the full-resolution source

Derive the larger tiers from the decoded download instead of re-reading the just-written smaller tier (resize never upscales, so 512/800 were stored at the small resolution). Resolve the full-res 2000 tier exactly so it is actually downloaded and cached. Bump the cache layout stamp to drop already-poisoned tiers.

* fix(cover): open the cover lightbox at full resolution

Require the exact requested tier for the full-res helper so a smaller warmed tier no longer pins the lightbox to a downscaled image. Race the 2000 fetch against a 500ms opening window, show the 800 tier meanwhile, and persist 2000 for the next open. Adds an opening animation (reduced-motion aware) and tests.

* docs(changelog): note full-resolution cover fix (#1205)

* fix(cover): keep full-res peek exact in peek_batch and the grid seeder

Review follow-up: ensure-path peek alone left a hole — cover_cache_peek_batch still laddered a 2000 request down to a smaller tier, and the in-memory grid seeder wrote that smaller path under the 2000 key, so Hero/fullscreen/lightbox surfaces (which peek 2000 before ensure) still showed a downscaled cover. Share one exact-2000 rule (peek_plain_cover_tier) across ensure and peek_batch, and never seed the full-res key from a smaller tier file.
2026-06-28 04:23:12 +02:00

87 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { getDiskSrc, rememberDiskSrc } from './diskSrcCache';
import { hasCoverDiskReadyListeners, notifyCoverDiskReady } from './diskHandoff';
import { coverStorageKeyFromRef } from './storageKeys';
import type { CoverArtRef, CoverArtTier } from './types';
/** Tier embedded in a cover file path (`…/512.webp`, `…/2000-fanart.webp`). */
function coverPathTier(fsPath: string): number | null {
const m = /(\d+)(?:-[a-z0-9]+)?\.webp$/i.exec(fsPath);
return m ? Number(m[1]) : null;
}
/**
* Never seed the full-res (≥2000) key from a smaller tier's file. The grid lookup
* order intentionally cross-seeds smaller display keys, but pinning a downscaled
* image under the 2000 key would make Hero / fullscreen / the lightbox show a
* small cover (they read the 2000 key before running ensure). Mirrors the Rust
* `peek_plain_cover_tier` exact-only rule for full-res.
*/
function skipFullResSeedTier(tier: CoverArtTier, fsPath: string): boolean {
if (tier < 2000) return false;
const src = coverPathTier(fsPath);
return src == null || src < 2000;
}
/** Dense grids: prefer a larger on-disk tier (800) before tiny thumbs when the ideal tier is missing. */
export function gridDiskSrcLookupOrder(want: CoverArtTier): CoverArtTier[] {
const out: CoverArtTier[] = [want];
if (want >= 256 && want < 800) out.push(800);
const ladder: CoverArtTier[] = [128, 256, 512, 800];
for (let i = ladder.length - 1; i >= 0; i -= 1) {
const t = ladder[i]!;
if (t !== want && t < want && !out.includes(t)) out.push(t);
}
if (want < 800 && !out.includes(800)) out.push(800);
return out;
}
/** Synchronous hit from `diskSrcCache` — any tier already warmed/peeked for this cover. */
export function getDiskSrcForGrid(ref: CoverArtRef, wantTier: CoverArtTier): string {
for (const tier of gridDiskSrcLookupOrder(wantTier)) {
const src = getDiskSrc(coverStorageKeyFromRef(ref, tier));
if (src) return src;
}
return '';
}
/** Seed lookup-order tier keys (512 + 800 fallback path, etc.) — no subscriber wakeups. */
export function seedGridDiskSrcCache(ref: CoverArtRef, wantTier: CoverArtTier, fsPath: string): boolean {
if (!fsPath) return false;
let hit = false;
for (const tier of gridDiskSrcLookupOrder(wantTier)) {
if (skipFullResSeedTier(tier, fsPath)) continue;
if (rememberDiskSrc(coverStorageKeyFromRef(ref, tier), fsPath)) hit = true;
}
return hit;
}
/**
* After peek/ensure: seed cache and wake mounted cells once (avoids 4× notify / re-render storms).
*/
export function rememberGridDiskSrc(ref: CoverArtRef, wantTier: CoverArtTier, fsPath: string): boolean {
const hit = seedGridDiskSrcCache(ref, wantTier, fsPath);
if (!hit) return false;
const wantKey = coverStorageKeyFromRef(ref, wantTier);
if (hasCoverDiskReadyListeners(wantKey)) {
notifyCoverDiskReady(wantKey, fsPath);
}
return true;
}
/** Rust `cover:tier-ready` — seed ladder keys so sparse cells see 800.webp when they want 128. */
export function rememberDiskSrcLadder(
serverIndexKey: string,
ref: Pick<CoverArtRef, 'cacheKind' | 'cacheEntityId'>,
wantTier: CoverArtTier,
fsPath: string,
): boolean {
if (!serverIndexKey || !ref.cacheEntityId || !fsPath) return false;
let hit = false;
for (const tier of gridDiskSrcLookupOrder(wantTier)) {
if (skipFullResSeedTier(tier, fsPath)) continue;
const key = `${serverIndexKey}:cover:${ref.cacheKind}:${ref.cacheEntityId}:${tier}`;
if (rememberDiskSrc(key, fsPath)) hit = true;
}
return hit;
}