366692a1fcf0da011a0d4bc944cd9eceb253ac79
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
366692a1fc |
fix: stop the sync feed hiding missing files from clients — #2704
#2523 filtered missing tracks out of every path that CHOOSES music, but the client sync feed was never touched: GetTracksByIDs has no filter and the wire had no field for it. So every Android client held a cached library containing tracks whose files are gone, with no way to tell, and could queue them from any cache-first path -- the exact failure #2523 existed to prevent, reached by a different route. Ships the state rather than filtering the feed, of the two options the ticket weighed. A missing file is expected to come back: the scanner clears the mark, and adopts the row if it returns renamed (#2528). Withholding the row would mean a delete-and-recreate on every client for what is usually a transient unmount, churning caches and throwing away the identity #2528 works to preserve. Room goes to v8. No hand-written migration: the pre-v1 destructive fallback rebuilds from sync, which repopulates every row with the new column -- exactly the case that policy exists for. The interesting part was working out what "missing" means to a client, and it is NOT "unplayable". Two findings shaped the fix: Server search and album detail never filtered missing tracks either, and that turns out to be right rather than an oversight. The consistent rule the codebase already follows is that Minstrel never PICKS a missing track for you -- recommendation, discover, mixes and browse all exclude them -- but it does not hide one you went looking for by name or opened an album to find. Hiding track 4 makes an album look wrong. So the fix is to mark and to keep it out of queues, not to hide it. And a track whose server file is missing still plays perfectly if its audio is already in the device cache. ShuffleSource's offline pools filter to exactly those residents, so it now clears the mark on the way out: the bytes are local and the server's loss is irrelevant. Without that, the queue filter below would have thrown away tracks that work, turning a fix into an offline regression. The queue protection is one choke point rather than five call sites. setQueue is where playlists, album play-all, search, radio and cold-boot resume all converge. dropUnavailable is pure so the index arithmetic is pinned by tests -- removing entries ahead of the requested position would otherwise start playback on the wrong track, and asking to start on a missing track now starts the next playable one, which is the "gets skipped" behaviour the operator asked for. An entirely missing queue returns empty and the caller leaves the player alone rather than replacing what is playing with silence. |
||
|
|
03a8d12079 |
docs: stop pointing at the deleted Flutter tree — #2710
Every comment naming a path in flutter_client/ now resolves to nothing, which is the failure mode this project has already been bitten by twice -- drift #572 came from delete.go describing behaviour it no longer had, and that same docstring was still wrong when it was fixed last week. A pointer to a deleted directory is the same thing in slower motion: the reader follows it, finds nothing, and cannot tell whether the comment is stale or they are looking in the wrong place. Three treatments, per what each comment was actually doing: - Naming a concept ("mirrors db.dart's CachedTracks Drift table"): keep the concept, drop the path. The Drift table is why the entity looks as it does; the file it lived in is not. - Pure port bookkeeping ("Mirrors <path>." and nothing else): deleted. Git history records the port; the comment only restated it. - Substance introduced by a pointer (a lifecycle list, a 200 px/s threshold, an inverted control-row placement): keep the substance, drop the lead-in. The two Go comments were the valuable ones and got more than a trim. They stated a live contract -- "field names match the client's FromJson helpers exactly, or fields are silently dropped" -- against a client that no longer exists. They now name the real consumer, SyncResponseWire.kt, and say why the failure is silent there too: kotlinx.serialization skips unknown keys, so a renamed field arrives as a default value rather than an error. The ticket counted 64 files by grepping flutter_client/. A second tier turned up during the sweep: 15 more references naming bare Dart files (player_bar.dart, now_playing_screen.dart:464, auth_provider.dart) with no directory prefix. Same dead tree, same treatment, folded in here. Comments only -- verified no non-comment line is touched in the diff. |
||
|
|
27f123f7d9 |
fix(server,flutter): sync wire format + systemVariant column (#357)
Two fixes in one commit because they're entangled — the systemVariant work would have been theater otherwise. ## The wire-format bug /api/library/sync was emitting PascalCase JSON for artist / album / track / playlist upserts (raw json.Marshal of sqlc-generated structs with no JSON tags — sqlc.yaml: emit_json_tags=false). Flutter's sync_controller _*FromJson reads snake_case keys, so all metadata sync rows landed in drift with empty strings / zero ints. The like_track / like_album / like_artist / playlist_track entities work because they're hand-built `map[string]string` payloads with snake_case keys — they sidestepped the bug. The 4 raw-marshal entities did not. Existing sync test caught zero of this — it asserts on len(upserts) not field shape. Fix: server-side view structs in library_sync_views.go with proper JSON tags + pgtype-flattening (UUID → 8-4-4-4-12 hex string, Date → "2006-01-02"). Mirrors the playlistRowView pattern from /api/playlists. New library_sync_views_test.go pins the wire keys so future field-name drift breaks loud. ## systemVariant column (closes #357 plan C v1 limitation) playlistSyncView now carries `system_variant` server → wire. Flutter drift schema bumped from 1 → 2 with onUpgrade adding the `systemVariant TEXT NULL` column to cached_playlists. Cursor reset to 0 in the migration so existing rows refresh with the new field on the next sync. playlistsListProvider now filters locally by systemVariant: - kind='user' → systemVariant IS NULL (the add-to-playlist sheet's intent) - kind='system' → systemVariant IS NOT NULL - kind='all' → no filter Closes the documented v1 limitation where the add-to-playlist sheet showed system playlists alongside user-created ones. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |