mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-28 03:19:38 +00:00
* fix(subsonic): always emit required `created` field on AlbumID3
Strict OpenSubsonic clients (e.g. Navic via dev.zt64.subsonic) reject
search3/getAlbum/getAlbumList2 responses that omit the `created` field,
which the spec marks as required. Navidrome was dropping it whenever
the album's CreatedAt was zero.
Root cause was threefold:
1. buildAlbumID3/childFromAlbum conditionally emitted `created`, so a
zero CreatedAt became a missing JSON key.
2. ToAlbum's `older()` helper treated a zero BirthTime as the minimum,
so a single track with missing filesystem birth time could poison
the album aggregation.
3. phase_1_folders' CopyAttributes copied `created_at` from the previous
album row unconditionally, propagating an already-zero value forward
on every metadata-driven album ID change. Since sql_base_repository
drops `created_at` on UPDATE, a poisoned row could never self-heal.
Fixes:
- Always emit `created`, falling back to UpdatedAt/ImportedAt when
CreatedAt is zero. Adds albumCreatedAt() helper used by both
buildAlbumID3 and childFromAlbum.
- Guard `older()` against a zero second argument.
- Skip the CopyAttributes call in phase_1_folders when the previous
album's created_at is zero, so the freshly-computed value survives.
- New migration backfills existing broken rows from media_file.birth_time
(falling back to updated_at).
Tested against a real DB: repaired 605/6922 affected rows, no side
effects on healthy rows.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(subsonic): return albumCreatedAt by value to avoid heap escape
Returning *time.Time from albumCreatedAt caused Go escape analysis to
move the entire model.Album parameter to the heap, since the returned
pointer aliased a field of the value receiver. For hot endpoints like
getAlbumList2 and search3, this meant one full-struct heap allocation
per album result.
Return time.Time by value and let callers wrap it with gg.P() to take
the address locally. Only the small time.Time value escapes; the
model.Album struct stays on the stack. Also corrects the doc comment
to reflect the actual guarantee ("best-effort" rather than "non-zero"),
matching the test case that exercises the all-zero fallback.
---------
Signed-off-by: Deluan <deluan@navidrome.org>
|
||
|---|---|---|
| .. | ||
| album_repository.go | ||
| album_repository_test.go | ||
| artist_repository.go | ||
| artist_repository_test.go | ||
| collation_test.go | ||
| export_test.go | ||
| folder_repository.go | ||
| folder_repository_test.go | ||
| genre_repository.go | ||
| genre_repository_test.go | ||
| helpers.go | ||
| helpers_test.go | ||
| library_repository.go | ||
| library_repository_test.go | ||
| mediafile_repository.go | ||
| mediafile_repository_test.go | ||
| persistence.go | ||
| persistence_suite_test.go | ||
| persistence_test.go | ||
| player_repository.go | ||
| player_repository_test.go | ||
| playlist_repository.go | ||
| playlist_repository_test.go | ||
| playlist_track_repository.go | ||
| playqueue_repository.go | ||
| playqueue_repository_test.go | ||
| plugin_cleanup.go | ||
| plugin_cleanup_test.go | ||
| plugin_repository.go | ||
| plugin_repository_test.go | ||
| property_repository.go | ||
| property_repository_test.go | ||
| radio_repository.go | ||
| radio_repository_test.go | ||
| scrobble_buffer_repository.go | ||
| scrobble_buffer_repository_test.go | ||
| scrobble_repository.go | ||
| scrobble_repository_test.go | ||
| share_repository.go | ||
| share_repository_test.go | ||
| sql_annotations.go | ||
| sql_annotations_test.go | ||
| sql_base_repository.go | ||
| sql_base_repository_test.go | ||
| sql_bookmarks.go | ||
| sql_bookmarks_test.go | ||
| sql_participations.go | ||
| sql_restful.go | ||
| sql_restful_test.go | ||
| sql_search.go | ||
| sql_search_fts.go | ||
| sql_search_fts_test.go | ||
| sql_search_like.go | ||
| sql_search_like_test.go | ||
| sql_search_test.go | ||
| sql_tags.go | ||
| tag_library_filtering_test.go | ||
| tag_repository.go | ||
| tag_repository_test.go | ||
| transcoding_repository.go | ||
| transcoding_repository_test.go | ||
| user_props_repository.go | ||
| user_repository.go | ||
| user_repository_test.go | ||