quintodrome/server/subsonic
Deluan Quintão 9b0bfc606b
fix(subsonic): always emit required created field on AlbumID3 (#5340)
* 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>
2026-04-10 19:29:20 -04:00
..
filter fix(subsonic): only use genre tag when searching by genre (#4361) 2025-07-19 21:52:29 -04:00
responses feat(subsonic): add coverArt to internetRadioStation response 2026-03-22 15:22:02 -04:00
album_lists.go feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
album_lists_test.go feat(subsonic): implement OpenSubsonic Transcoding extension (#4990) 2026-03-08 23:57:49 -04:00
api.go refactor: rename core/transcode to core/stream, simplify MediaStreamer (#5166) 2026-03-09 22:22:58 -04:00
api_suite_test.go feat:(server): support reading lyrics from filesystem (#2897) 2025-04-30 08:10:19 -04:00
api_test.go fix(subsonic): validate JSONP callback parameter 2026-02-08 10:33:46 -05:00
bookmarks.go fix(subsonic): always return required playqueue fields (#5172) 2026-03-12 08:29:37 -04:00
browsing.go feat(subsonic): append album version to names in Subsonic API (#5111) 2026-02-26 10:50:12 -05:00
browsing_test.go feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
helpers.go fix(subsonic): always emit required created field on AlbumID3 (#5340) 2026-04-10 19:29:20 -04:00
helpers_test.go fix(subsonic): always emit required created field on AlbumID3 (#5340) 2026-04-10 19:29:20 -04:00
jukebox.go refactor: small improvements and clean up (#3423) 2024-10-22 22:54:31 -04:00
library_scanning.go fix(server): return correct scanType in startScan response (#5159) 2026-03-09 14:19:53 -04:00
library_scanning_test.go fix(server): return correct scanType in startScan response (#5159) 2026-03-09 14:19:53 -04:00
media_annotation.go feat(plugins): experimental support for plugins (#3998) 2025-06-22 20:45:38 -04:00
media_annotation_test.go feat(subsonic): implement OpenSubsonic Transcoding extension (#4990) 2026-03-08 23:57:49 -04:00
media_retrieval.go fix(server): use http.TimeFormat for Last-Modified header (#5219) 2026-03-17 08:04:47 -04:00
media_retrieval_test.go feat(subsonic): implement OpenSubsonic Transcoding extension (#4990) 2026-03-08 23:57:49 -04:00
middlewares.go chore: upgrade golangci-lint to 2.11 and fix lint issues 2026-03-06 19:23:47 -05:00
middlewares_test.go feat: rename "reverse proxy authentication" to "external authentication" (#4418) 2025-12-02 12:01:48 -05:00
opensubsonic.go feat(subsonic): implement OpenSubsonic Transcoding extension (#4990) 2026-03-08 23:57:49 -04:00
opensubsonic_test.go feat(subsonic): implement OpenSubsonic Transcoding extension (#4990) 2026-03-08 23:57:49 -04:00
playlists.go fix(subsonic): strip OpenSubsonic extensions from playlists for legacy clients 2026-04-02 16:37:52 -04:00
playlists_test.go fix(subsonic): strip OpenSubsonic extensions from playlists for legacy clients 2026-04-02 16:37:52 -04:00
radio.go fix(subsonic): return proper artwork ID format in getInternetRadioStations 2026-04-02 15:44:20 -04:00
radio_test.go fix(subsonic): return proper artwork ID format in getInternetRadioStations 2026-04-02 15:44:20 -04:00
searching.go feat(subsonic): sort search3 results by relevance (#5086) 2026-02-23 08:51:54 -05:00
searching_test.go feat(subsonic): implement OpenSubsonic Transcoding extension (#4990) 2026-03-08 23:57:49 -04:00
sharing.go fix(subsonic): make Share's lastVisited optional 2025-01-09 16:10:53 -05:00
stream.go fix(server): improve transcoding failure diagnostics and error responses (#5227) 2026-03-18 12:39:03 -04:00
system.go Some cleanup, adding missing context handling 2022-12-06 19:57:47 -05:00
transcode.go fix(server): improve transcoding failure diagnostics and error responses (#5227) 2026-03-18 12:39:03 -04:00
transcode_test.go fix: use ADTS for AAC transcoding, temporarily exclude AAC from transcode decisions (#5167) 2026-03-11 09:26:32 -04:00
users.go refactor: rename EnableCoverArtUpload to EnableArtworkUpload 2026-03-27 19:33:46 -04:00
users_test.go refactor: rename EnableCoverArtUpload to EnableArtworkUpload 2026-03-27 19:33:46 -04:00