Home

dev / openkara

publicthedavidweng/OpenKara· sync paused
Overview Code History Branches Pull requestsIssuesInsights
main
HomeOverview Code PRsIssues

perf(library): serve cover-art thumbnails through the asset protocol (#271)

2 months ago

7cd7c73
Authored
Davy7/26/2026, 10:54:17 PM
* perf(library): serve cover-art thumbnails through the asset protocol

The library grid fetched every 80px WebP derivative over IPC and pushed
the bytes through a ref-counted blob-URL cache: one round trip, one
Vec<u8> copy, one Blob allocation, and one cache entry per visible row,
repeated as rows recycle during scrolling.

Serve the derivative file directly instead. Song gains
artwork_thumb_path, and CoverArtThumbnail renders
convertFileSrc(path). The grid now issues zero IPC calls for cover art.

What makes this safe is that derivative filenames are content-addressed
(thumb_<sha256>_80.webp), so a replaced cover arrives as a different
path - the WebView may cache the image freely with no busting scheme.

get_cover_art stays: it is still the only source for the SQLite BLOB
original, and it remains the lazy repair path. An <img> that fails to
load falls back to it, which is what regenerates a missing derivative.

Only the thumb migrates. Carrying a second path for the 256px preview
would add ~126 bytes per song to every library payload to save one IPC
call per *song change* - the preview backs a single ambience image, not
a grid, so it keeps the existing path.

The asset scope is granted where the paths are handed out rather than
at library activation: the scope is in-memory and the library root is
user-relocatable, so a future activation path cannot forget it, and
allow_directory inserts into a HashSet so repeating it is free.

No loading="lazy": f4bc3ca removed it because desktop WebViews left
blob-backed thumbnails unpainted with deferred decoding, and the
library list is virtualized, so off-screen rows are not in the DOM to
lazy-load anyway. That rationale comment was lost in a later refactor
and is restored.

Closes #264

* fix(library): absolutize thumbnail paths in every Song-returning command

Only get_library and search_library ran the rewrite, so a song returned
by import, cover-art extraction, a metadata edit, or an instrumental or
language toggle carried a still-relative path into the store. The grid
then issued an asset request the app cannot serve and fell back to IPC -
correct image, wasted request, and the optimization silently off for
that song until the next library load.

Parentab0f004

51 files changed
  • docs/references/contracts/library.md+6−0
  • packaging/flatpak/generated/cargo-sources.json+13−0
  • src-tauri/Cargo.lock+7−0
  • src-tauri/Cargo.toml+1−1
  • src-tauri/src/cache/lyrics.rs+1−0
  • src-tauri/src/cache/mod.rs+12−4
  • src-tauri/src/cache/waveforms.rs+1−0
  • src-tauri/src/commands/import/mod.rs+147
−22
  • src-tauri/src/commands/lyrics.rs+1−0
  • src-tauri/src/library/artwork.rs+1−0
  • src-tauri/src/library/import/ingest.rs+4−0
  • src-tauri/src/library/integrity.rs+1−0
  • src-tauri/src/library/mod.rs+9−0
  • src-tauri/src/lyrics/fetch.rs+1−0
  • src-tauri/src/remote/mutation.rs+1−0
  • src-tauri/src/remote/webdav.rs+1−0
  • src-tauri/src/services/next_track.rs+2−0
  • src-tauri/src/services/playback_source.rs+1−0
  • src-tauri/src/services/separation.rs+1−0
  • src-tauri/src/services/waveform.rs+2−0
  • src-tauri/tauri.conf.json+12−2
  • src-tauri/tests/asset_protocol_config_guard.rs+91−0
  • src-tauri/tests/phase1_cache.rs+1−0
  • src-tauri/tests/phase3_job.rs+1−0
  • src-tauri/tests/phase3_stems_cache.rs+1−0
  • src-tauri/tests/phase4_commands.rs+4−0
  • src-tauri/tests/phase4_fetch.rs+3−0
  • src-tauri/tests/phase4_lyrics_cache.rs+1−0
  • src-tauri/tests/phase5_remote_schema.rs+2−0
  • src/components/Layout/GlobalProgressBar.test.tsx+2−0
  • src/components/Layout/Sidebar.preview.test.tsx+1−0
  • src/components/Layout/Sidebar.test.tsx+4−0
  • src/components/Library/SongList.test.tsx+12−0
  • src/components/Library/SongListItem.test.tsx+11−0
  • src/components/Library/SongListItem.tsx+1−0
  • src/components/Library/song-list-item-context-menu-build.test.ts+1−0
  • src/components/Playback/PlaybackStage.test.tsx+4−0
  • src/components/Player/NowPlayingInfo.test.tsx+1−0
  • src/components/Player/NowPlayingInfo.tsx+1−0
  • src/components/Player/QueuePanel.test.tsx+9−0
  • src/components/Shared/CoverArtThumbnail.asset.test.tsx+99−0
  • src/components/Shared/CoverArtThumbnail.tsx+42−9
  • src/hooks/use-cdg-sync.render.test.tsx+1−0
  • src/lib/alphabet-index.test.ts+1−0
  • src/lib/song-media.test.ts+1−0
  • src/lib/song-sort.test.ts+1−0
  • src/mock/preview-songs.ts+6−0
  • src/stores/library-store.test.ts+11−0
  • src/types/ipc-contract.test.ts+3−0
  • src/types/ipc.test.ts+1−0
  • src/types/ipc.ts+7−0