mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-10 18:59:56 +00:00
v3 library: Albums view — the client half of the album-condense work (#716)
* v3 library: Albums view — the client half of the album-condense work Follow-up to the query_albums endpoint: the UI that consumes it, plus the track-order plumbing the endpoint's track list needs. - Albums view (a fourth view toggle next to grid/tree/folder): album cards (cover / title / artist / track count) from /api/library/albums, respecting the active filter drawer; clicking one opens the track list with per-track play and a Play-album button that feeds the play queue (falls back to plain playSong when the queue plugin is absent). - Track order: the scanner now reads the feedpak `track`/`disc` fields (spec 1.12.0) into new nullable songs columns (idempotent ALTERs), and the album track list orders by the new `track` sort — disc, then track number, unauthored charts to the bottom by title. Charts without authored numbers keep working; they just sort alphabetically. - The albums view persists like the other view choices. 3 new tests: manifest track/disc extraction (and unauthored -> None), the disc->track->title sort order over /api/library, and the put() round-trip. Full-suite failure set matches the known env baseline (one tuner-config name swapped inside the suite-ordering flake family — the file passes 25/25 in isolation on clean main). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nm7tHs1Yvjjtnnu4nzJgdN * v3 albums: honour Genre/Match filters in album grid + detail (review fixes) The Albums view only partially respected the filter drawer: - /api/library/albums silently dropped the `genre` and `match` params the client sends via queryParams(), so with a Genre or Match filter active the album grid surfaced albums with zero matching tracks. Thread match_states/ genre through the endpoint -> query_albums -> _build_where, mirroring the /api/library grid route. (SmartCollection/pass-through providers keep their existing kwarg handling.) - The album-detail track list built its own params (provider/artist/album/ sort only), so it ignored ALL active filters — the track list and the Play-album queue could include songs the user had filtered out. Reuse queryParams({...}, {catalog: true}) so detail honours the same filters as the grid while pinning this album's artist/album and track order. +1 regression test (albums endpoint honours the Genre filter). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
co-authored by
Claude Fable 5
byrongamatos
parent
2e4383524f
commit
0a8c8945ea
@@ -551,7 +551,9 @@ class MetadataDB:
|
||||
tuning_name TEXT DEFAULT '',
|
||||
tuning_sort_key INTEGER DEFAULT 0,
|
||||
tuning_offsets TEXT DEFAULT '',
|
||||
genre TEXT DEFAULT ''
|
||||
genre TEXT DEFAULT '',
|
||||
track_number INTEGER,
|
||||
disc INTEGER
|
||||
)
|
||||
""")
|
||||
# Idempotent migrations for installs that predate each column.
|
||||
@@ -573,6 +575,11 @@ class MetadataDB:
|
||||
# Primary genre from the feedpak `genres` list (spec 1.12.0). Cache;
|
||||
# repopulated on rescan.
|
||||
"ALTER TABLE songs ADD COLUMN genre TEXT DEFAULT ''",
|
||||
# Album track order from the feedpak `track`/`disc` fields (spec
|
||||
# 1.12.0). NULL when the pack doesn't author them; the album view
|
||||
# falls back to title order. Cache; repopulated on rescan.
|
||||
"ALTER TABLE songs ADD COLUMN track_number INTEGER",
|
||||
"ALTER TABLE songs ADD COLUMN disc INTEGER",
|
||||
):
|
||||
try:
|
||||
self.conn.execute(ddl)
|
||||
@@ -2553,8 +2560,8 @@ class MetadataDB:
|
||||
self.conn.execute(
|
||||
"INSERT OR REPLACE INTO songs "
|
||||
"(filename, mtime, size, title, artist, album, year, duration, tuning, arrangements, "
|
||||
"has_lyrics, format, stem_count, stem_ids, tuning_name, tuning_sort_key, tuning_offsets, genre) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"has_lyrics, format, stem_count, stem_ids, tuning_name, tuning_sort_key, tuning_offsets, genre, track_number, disc) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(filename, mtime, size, meta.get("title", ""), meta.get("artist", ""),
|
||||
meta.get("album", ""), meta.get("year", ""), meta.get("duration", 0),
|
||||
meta.get("tuning", ""), json.dumps(meta.get("arrangements", [])),
|
||||
@@ -2565,7 +2572,9 @@ class MetadataDB:
|
||||
meta.get("tuning_name", "") or "",
|
||||
int(meta.get("tuning_sort_key", 0) or 0),
|
||||
meta.get("tuning_offsets", "") or "",
|
||||
meta.get("genre", "") or ""),
|
||||
meta.get("genre", "") or "",
|
||||
meta.get("track_number"),
|
||||
meta.get("disc")),
|
||||
)
|
||||
self.conn.commit()
|
||||
# A song's identity may have changed → the grouping read-model is stale.
|
||||
@@ -3538,6 +3547,10 @@ class MetadataDB:
|
||||
# '2005' rather than alphabetic.
|
||||
"year": "(year = '') ASC, CAST(year AS INTEGER) ASC",
|
||||
"year-desc": "(year = '') ASC, CAST(year AS INTEGER) DESC",
|
||||
# Album track order: authored track number (disc, then track); songs
|
||||
# with no number fall to the bottom, ordered by title. Used by the
|
||||
# album detail view. Alpha-by-title is the fallback when unauthored.
|
||||
"track": "(track_number IS NULL) ASC, COALESCE(disc, 1), track_number, title COLLATE NOCASE",
|
||||
# Mastery = best accuracy across a song's arrangements, from the
|
||||
# separate song_stats table (so via a correlated subquery — this sort
|
||||
# drops to OFFSET paging, like tuning/year). Unscored ("not started")
|
||||
@@ -3817,6 +3830,7 @@ class MetadataDB:
|
||||
arrangements_has=None, arrangements_lacks=None,
|
||||
stems_has=None, stems_lacks=None,
|
||||
has_lyrics=None, tunings=None, mastery=None,
|
||||
match_states=None, genre=None,
|
||||
naming_mode="legacy", page=0, size=120):
|
||||
"""Distinct (artist, album) groups with a track count + a representative
|
||||
cover song, for the album-condensed browse (paged by album). Rows with no
|
||||
@@ -3828,6 +3842,7 @@ class MetadataDB:
|
||||
arrangements_has=arrangements_has, arrangements_lacks=arrangements_lacks,
|
||||
stems_has=stems_has, stems_lacks=stems_lacks,
|
||||
has_lyrics=has_lyrics, tunings=tunings, mastery=mastery,
|
||||
match_states=match_states, genre=genre,
|
||||
naming_mode=naming_mode,
|
||||
)
|
||||
awhere = where + " AND album IS NOT NULL AND album != ''"
|
||||
@@ -7161,6 +7176,7 @@ async def list_library_albums(q: str = "", page: int = 0, size: int = 120,
|
||||
arrangements_has: str = "", arrangements_lacks: str = "",
|
||||
stems_has: str = "", stems_lacks: str = "",
|
||||
has_lyrics: str = "", tunings: str = "", mastery: str = "",
|
||||
match: str = "", genre: str = "",
|
||||
provider: str = "local"):
|
||||
"""Album-condensed browse: distinct (artist, album) groups with a track count
|
||||
and a representative cover song. Paged by album. Same filters as /api/library."""
|
||||
@@ -7170,6 +7186,7 @@ async def list_library_albums(q: str = "", page: int = 0, size: int = 120,
|
||||
albums, total = await _call_library_provider_async(
|
||||
library_provider, "query_albums",
|
||||
page=page, size=size, mastery=_split_csv(mastery),
|
||||
match_states=_split_csv(match), genre=_split_csv(genre),
|
||||
**_library_filter_args(
|
||||
q=q, favorites=favorites, format=format, artist=artist, album=album,
|
||||
arrangements_has=arrangements_has, arrangements_lacks=arrangements_lacks,
|
||||
|
||||
Reference in New Issue
Block a user