The native API exposes a `path` query param on /api/song, but it was not
registered in the media file filter map. Unmapped real columns fall through
to a default LIKE predicate that emits an unqualified `path LIKE ?`. Since
the song query joins the library table (which also has a `path` column),
SQLite returned "ambiguous column name: path" and the request failed with
HTTP 500.
Register a dedicated path filter qualified to media_file.path, resolving the
ambiguity. The value is matched with startsWith semantics (LIKE arg || '%')
against the library-relative path stored in media_file.path.
To register it inline (without a one-off wrapper), startsWithFilter now takes
a bound field and returns a filterFunc, mirroring containsFilter. The two
existing callers are updated accordingly, and the now-unused withTableName
helper is removed. The user 'name' filter, which previously relied on
withTableName, is now qualified directly as user.name; tests are added to
guard that filter against the same column-ambiguity class (the user query
also joins the library table, which has a name column).
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(subsonic): implement transcode decision logic and codec handling for media files
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(subsonic): update codec limitation structure and decision logic for improved clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(transcoding): update bitrate handling to use kilobits per second (kbps) across transcode decision logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): simplify container alias handling in matchesContainer function
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(transcoding): enforce POST method for GetTranscodeDecision and handle non-POST requests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(transcoding): add enums for protocol, comparison operators, limitations, and codec profiles in transcode decision logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): streamline limitation checks and applyLimitation logic for improved readability and maintainability
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): replace strings.EqualFold with direct comparison for protocol and limitation checks
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): rename token methods to CreateTranscodeParams and ParseTranscodeParams for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): enhance logging for transcode decision process and client info conversion
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): rename TranscodeDecision to Decider and update related methods for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): enhance transcoding config lookup logic for audio codecs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): enhance transcoding options with sample rate support and improve command handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): add bit depth support for audio transcoding and enhance related logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): enhance AAC command handling and support for audio channels in streaming
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): streamline transcoding logic by consolidating stream parameter handling and enhancing alias mapping
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): update default command handling and add codec support for transcoding
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: implement noopDecider for transcoding decision handling in tests
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: address review findings for OpenSubsonic transcoding PR
Fix multiple issues identified during code review of the transcoding
extension: add missing return after error in shared stream handler
preventing nil pointer panic, replace dead r.Body nil check with
MaxBytesReader size limit, distinguish not-found from other DB errors,
fix bpsToKbps integer truncation with rounding, add "pcm" to
isLosslessFormat for consistency with model.IsLossless(), add
sampleRate/bitDepth/channels to streaming log, fix outdated test
comment, and add tests for conversion functions and GetTranscodeStream
parameter passing.
* feat(transcoding): add sourceUpdatedAt to decision and validate transcode parameters
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: small issues
Updated mock AAC transcoding command to use the new default (ipod with
fragmented MP4) matching the migration, ensuring tests exercise the same
buildDynamicArgs code path as production. Improved archiver test mock to
match on the whole StreamRequest struct instead of decomposing fields,
making it resilient to future field additions. Added named constants for
JWT claim keys in the transcode token and wrapped ParseTranscodeParams
errors with ErrTokenInvalid for consistency. Documented the IsLossless
BitDepth fallback heuristic as temporary until Codec column is populated.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(transcoding): adapt transcode claims to struct-based auth.Claims
Updated transcode token handling to use the struct-based auth.Claims
introduced on master, replacing the previous map[string]any approach.
Extended auth.Claims with transcoding-specific fields (MediaID, DirectPlay,
UpdatedAt, Channels, SampleRate, BitDepth) and added float64 fallback in
ClaimsFromToken for numeric claims that lose their Go type during JWT
string serialization. Also added the missing lyrics parameter to all
subsonic.New() calls in test files.
* feat(model): add ProbeData field and UpdateProbeData repository method
Add probe_data TEXT column to media_file for caching ffprobe results.
Add UpdateProbeData to MediaFileRepository interface and implementations.
Use hash:"ignore" tag so probe data doesn't affect MediaFile fingerprints.
* feat(ffmpeg): add ProbeAudioStream for authoritative audio metadata
Add ProbeAudioStream to FFmpeg interface, using ffprobe to extract
codec, profile, bitrate, sample rate, bit depth, and channels.
Parse bits_per_raw_sample as fallback for FLAC/ALAC bit depth.
Normalize "unknown" profile to empty string.
All parseProbeOutput tests use real ffprobe JSON from actual files.
* feat(transcoding): integrate ffprobe into transcode decisions
Add ensureProbed to probe media files on first transcode decision,
caching results in probe_data. Build SourceStream from probe data
with fallback to tag-based metadata.
Refactor decision logic to pass StreamDetails instead of MediaFile,
enabling codec profile limitations (e.g., audioProfile) to use
probe data. Add normalizeProbeCodec to map ffprobe codec names
(dsd_lsbf_planar, pcm_s16le) to internal names (dsd, pcm).
NewDecider now accepts ffmpeg.FFmpeg; wire_gen.go regenerated.
* feat(transcoding): add DevEnableMediaFileProbe config flag
Add DevEnableMediaFileProbe (default true) to allow disabling ffprobe-
based media file probing as a safety fallback. When disabled, the
decider uses tag-based metadata from the scanner instead.
* test(transcode): add ensureProbed unit tests
Test probing when ProbeData is empty, skipping when already set,
error propagation from ffprobe, and DevEnableMediaFileProbe flag.
* refactor(ffmpeg): use command constant and select_streams for ProbeAudioStream
Move ffprobe arguments to a probeAudioStreamCmd constant, following the
same pattern as extractImageCmd and probeCmd. Add -select_streams a:0 to
only probe the first audio stream, avoiding unnecessary parsing of video
and artwork streams. Derive the ffprobe binary path safely using
filepath.Dir/Base instead of replacing within the full path string.
* refactor(transcode): decouple transcode token claims from auth.Claims
Remove six transcode-specific fields (MediaID, DirectPlay, UpdatedAt,
Channels, SampleRate, BitDepth) from auth.Claims, which is shared with
session and share tokens. Transcode tokens are signed parameter-passing
tokens, not authentication tokens, so coupling them to auth created
misleading dependencies.
The transcode package now owns its own JWT claim serialization via
Decision.toClaimsMap() and paramsFromToken(), using generic
auth.EncodeToken/DecodeAndVerifyToken wrappers that keep TokenAuth
encapsulated. Wire format (JWT claim keys) is unchanged, so in-flight
tokens remain compatible.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcode): simplify code after review
Extract getIntClaim helper to eliminate repeated int/int64/float64 JWT
claim extraction pattern in paramsFromToken and ClaimsFromToken. Rewrite
checkIntLimitation as a one-liner delegating to applyIntLimitation.
Return probe result from ensureProbed to avoid redundant JSON round-trip.
Extract toResponseStreamDetails helper and mediaTypeSong constant in
the API layer, and use transcode.ProtocolHTTP constant instead of
hardcoded string.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(ffmpeg): enhance bit_rate parsing logic for audio streams
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(transcode): improve code review findings across transcode implementation
- Fix parseProbeData to return nil on JSON unmarshal failure instead of
a zero-valued struct, preventing silent degradation of source stream details
- Use probe-resolved codec for lossless detection in buildSourceStream
instead of the potentially stale scanner data
- Remove MediaFile.IsLossless() (dead code) and consolidate lossless
detection in isLosslessFormat(), using codec name only — bit depth is
not reliable since lossy codecs like ADPCM report non-zero values
- Add "wavpack" to lossless codec list (ffprobe codec_name for WavPack)
- Guard bpsToKbps against negative input values
- Fix misleading comment in buildTemplateArgs about conditional injection
- Avoid leaking internal error details in Subsonic API responses
- Add missing test for ErrNotFound branch in GetTranscodeDecision
- Add TODO for hardcoded protocol in toResponseStreamDetails
* refactor(transcode): streamline transcoding command lookup and format resolution
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(transcode): implement server-side transcoding override for player formats
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(transcode): honor bit depth and channel constraints in transcoding selection
selectTranscodingOptions only checked sample rate when deciding whether
same-format transcoding was needed, ignoring requested bit depth and
channel reductions. This caused the streamer to return raw audio when
the transcode decision requested downmix or bit-depth conversion.
* refactor(transcode): unify streaming decision engine via MakeDecision
Move transcoding decision-making out of mediaStreamer and into the
subsonic Stream/Download handlers, using transcode.Decider.MakeDecision
as the single decision engine. This eliminates selectTranscodingOptions
and the mismatch between decision and streaming code paths (decision
used LookupTranscodeCommand with built-in fallbacks, while streaming
used FindByFormat which only checked the DB).
- Add DecisionOptions with SkipProbe to MakeDecision so the legacy
streaming path never calls ffprobe
- Add buildLegacyClientInfo to translate legacy stream params (format,
maxBitRate, DefaultDownsamplingFormat) into a synthetic ClientInfo
- Add resolveStreamRequest on the subsonic Router to resolve legacy
params into a fully specified StreamRequest via MakeDecision
- Simplify DoStream to a dumb executor that receives pre-resolved params
- Remove selectTranscodingOptions entirely
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcode): move MediaStreamer into core/transcode and unify StreamRequest
Moved MediaStreamer, Stream, TranscodingCache and related types from
core/media_streamer.go into core/transcode/, eliminating the duplicate
StreamRequest type. The transcode.StreamRequest now carries all fields
(ID, Format, BitRate, SampleRate, BitDepth, Channels, Offset) and
ResolveStream returns a fully-populated value, removing manual field
copying at every call site. Also moved buildLegacyClientInfo into the
transcode package alongside ResolveStream, and unexported
ParseTranscodeParams since it was only used internally by
ValidateTranscodeParams.
* refactor(transcode): rename Decider methods and unexport Params type
Rename ResolveStream → ResolveRequest and ValidateTranscodeParams →
ResolveRequestFromToken for clarity and consistency. The new
ResolveRequestFromToken returns a StreamRequest directly (instead of
the intermediate Params type), eliminating manual Params→StreamRequest
conversion in callers. Unexport Params to params since it is now only
used internally for JWT token parsing.
* test(transcode): remove redundant tests and use constants
Remove tests that duplicate coverage from integration-level tests
(toClaimsMap, paramsFromToken round-trips, applyServerOverride direct
call, duplicate 410 handler test). Replace raw "http" strings with
ProtocolHTTP constant. Consolidate lossy -sample_fmt tests into
DescribeTable.
* refactor(transcode): split oversized files into focused modules
Split transcode.go and transcode_test.go into focused files by concern:
- decider.go: decision engine (MakeDecision, direct play/transcode evaluation, probe)
- token.go: JWT token encode/decode (params, toClaimsMap, paramsFromToken, CreateTranscodeParams, ResolveRequestFromToken)
- legacy_client.go: legacy Subsonic bridge (buildLegacyClientInfo, ResolveRequest)
- codec_test.go: isLosslessFormat and normalizeProbeCodec tests
- token_test.go: token round-trip and ResolveRequestFromToken tests
Moved the Decider interface from types.go to decider.go to keep it near
its implementation, and cleaned up types.go to contain only pure type
definitions and constants. No public API changes.
* refactor(transcode): reorder parameters in applyServerOverride function
Signed-off-by: Deluan <deluan@navidrome.org>
* test(e2e): add NewTestStream function and implement spyStreamer for testing
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(persistence): add nil guards to cursor wrapping in folder and mediafile repos
Prevent SIGSEGV panic when queryWithStableResults yields a zero-value
struct on the rows.Err() path (e.g., "database is locked" during
concurrent scanning). Extract cursor wrapping into wrapFolderCursor and
wrapMediaFileCursor with nil checks matching the existing pattern in
album_repository.go.
Fixes#5138
* fix(persistence): wrap original cursor error in nil guard messages
Use %w to preserve the underlying error (e.g., "database is locked")
so callers can use errors.Is/As for root cause analysis. Tests now
verify the original error is accessible via errors.Is.
* fix(persistence): add nil guards and error wrapping in album, folder, and mediafile cursor functions
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(subsonic): optimize search3 for high-cardinality FTS queries
Use a two-phase query strategy for FTS5 searches to avoid the
performance penalty of expensive LEFT JOINs (annotation, bookmark,
library) on high-cardinality results like "the".
Phase 1 runs a lightweight query (main table + FTS index only) to get
sorted, paginated rowids. Phase 2 hydrates only those few rowids with
the full JOINs, making them nearly free.
For queries with complex ORDER BY expressions that reference joined
tables (e.g. artist search sorted by play count), the optimization is
skipped and the original single-query approach is used.
* fix(search): update order by clauses to include 'rank' for FTS queries
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): reintroduce 'rank' in Phase 2 ORDER BY for FTS queries
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): remove 'rank' from ORDER BY in non-FTS queries and adjust two-phase query handling
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): update FTS ranking to use bm25 weights and simplify ORDER BY qualification
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): refine FTS query handling and improve comments for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): refactor full-text search handling to streamline query strategy selection and improve LIKE fallback logic.
Increase e2e coverage for search3
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: enhance FTS column definitions and relevance weights
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): refactor Search method signatures to remove offset and size parameters, streamline query handling
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): allow single-character queries in search strategies and update related tests
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): make FTS Phase 1 treat Max=0 as no limit, reorganize tests
FTS Phase 1 unconditionally called Limit(uint64(options.Max)), which
produced LIMIT 0 when Max was zero. This diverged from applyOptions
where Max=0 means no limit. Now Phase 1 mirrors applyOptions: only add
LIMIT/OFFSET when the value is positive. Also moved legacy backend
integration tests from sql_search_fts_test.go to sql_search_like_test.go
and added regression tests for the Max=0 behavior on both backends.
* refactor: simplify callSearch function by removing variadic options and directly using QueryOptions
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): implement ftsQueryDegraded function to detect significant content loss in FTS queries
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* build: add sqlite_fts5 build tag to enable FTS5 support
* feat: add SearchBackend config option (default: fts)
* feat: add buildFTS5Query for safe FTS5 query preprocessing
* feat: add FTS5 search backend with config toggle, refactor legacy search
- Add searchExprFunc type and getSearchExpr() for backend selection
- Rename fullTextExpr to legacySearchExpr
- Add ftsSearchExpr using FTS5 MATCH subquery
- Update fullTextFilter in sql_restful.go to use configured backend
* feat: add FTS5 migration with virtual tables, triggers, and search_participants
Creates FTS5 virtual tables for media_file, album, and artist with
unicode61 tokenizer and diacritic folding. Adds search_participants
column, populates from JSON, and sets up INSERT/UPDATE/DELETE triggers.
* feat: populate search_participants in PostMapArgs for FTS5 indexing
* test: add FTS5 search integration tests
* fix: exclude FTS5 virtual tables from e2e DB restore
The restoreDB function iterates all tables in sqlite_master and
runs DELETE + INSERT to reset state. FTS5 contentless virtual tables
cannot be directly deleted from. Since triggers handle FTS5 sync
automatically, simply skip tables matching *_fts and *_fts_* patterns.
* build: add compile-time guard for sqlite_fts5 build tag
Same pattern as netgo: compilation fails with a clear error if
the sqlite_fts5 build tag is missing.
* build: add sqlite_fts5 tag to reflex dev server config
* build: extract GO_BUILD_TAGS variable in Makefile to avoid duplication
* fix: strip leading * from FTS5 queries to prevent "unknown special query" error
* feat: auto-append prefix wildcard to FTS5 search tokens for broader matching
Every plain search token now gets a trailing * appended (e.g., "love" becomes
"love*"), so searching for "love" also matches "lovelace", "lovely", etc.
Quoted phrases are preserved as exact matches without wildcards. Results are
ordered alphabetically by name/title, so shorter exact matches naturally
appear first.
* fix: clarify comments about FTS5 operator neutralization
The comments said "strip" but the code lowercases operators to
neutralize them (FTS5 operators are case-sensitive). Updated comments
to accurately describe the behavior.
* fix: use fmt.Sprintf for FTS5 phrase placeholders
The previous encoding used rune('0'+index) which silently breaks with
10+ quoted phrases. Use fmt.Sprintf for arbitrary index support.
* fix: validate and normalize SearchBackend config option
Normalize the value to lowercase and fall back to "fts" with a log
warning for unrecognized values. This prevents silent misconfiguration
from typos like "FTS", "Legacy", or "fts5".
* refactor: improve documentation for build tags and FTS5 requirements
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: convert FTS5 query and search backend normalization tests to DescribeTable format
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: add sqlite_fts5 build tag to golangci configuration
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add UISearchDebounceMs configuration option and update related components
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: fall back to legacy search when SearchFullString is enabled
FTS5 is token-based and cannot match substrings within words, so
getSearchExpr now returns legacySearchExpr when SearchFullString
is true, regardless of SearchBackend setting.
* fix: add sqlite_fts5 build tag to CI pipeline and Dockerfile
* fix: add WHEN clauses to FTS5 AFTER UPDATE triggers
Added WHEN clauses to the media_file_fts_au, album_fts_au, and
artist_fts_au triggers so they only fire when FTS-indexed columns
actually change. Previously, every row update (e.g., play count, rating,
starred status) triggered an unnecessary delete+insert cycle in the FTS
shadow tables. The WHEN clauses use IS NOT for NULL-safe comparison of
each indexed column, avoiding FTS index churn for non-indexed updates.
* feat: add SearchBackend configuration option to data and insights components
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: enhance input sanitization for FTS5 by stripping additional punctuation and special characters
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add search_normalized column for punctuated name search (R.E.M., AC/DC)
Add index-time normalization and query-time single-letter collapsing to
fix FTS5 search for punctuated names. A new search_normalized column
stores concatenated forms of punctuated words (e.g., "R.E.M." → "REM",
"AC/DC" → "ACDC") and is indexed in FTS5 tables. At query time, runs of
consecutive single letters (from dot-stripping) are collapsed into OR
expressions like ("R E M" OR REM*) to match both the original tokens and
the normalized form. This enables searching by "R.E.M.", "REM", "AC/DC",
"ACDC", "A-ha", or "Aha" and finding the correct results.
* refactor: simplify isSingleUnicodeLetter to avoid []rune allocation
Use utf8.DecodeRuneInString to check for a single Unicode letter
instead of converting the entire string to a []rune slice.
* feat: define ftsSearchColumns for flexible FTS5 search column inclusion
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update collapseSingleLetterRuns to return quoted phrases for abbreviations
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement extractPunctuatedWords to handle artist/album names with embedded punctuation
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement extractPunctuatedWords to handle artist/album names with embedded punctuation
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: punctuated word handling to improve processing of artist/album names
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add CJK support for search queries with LIKE filters
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance FTS5 search by adding album version support and CJK handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: search configuration to use structured options
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance search functionality to support punctuation-only queries and update related tests
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: preserve created_at when moving songs between libraries (#5050)
When songs are moved between libraries, their creation date was being
reset to the current time, causing them to incorrectly appear in
"Recently Added". Three changes fix this:
1. Add hash:"ignore" to AlbumID in MediaFile struct so that Equals()
works for cross-library moves (AlbumID includes library prefix,
making hashes always differ between libraries)
2. Preserve album created_at in moveMatched() via CopyAttributes,
matching the pattern already used in persistAlbum() for
within-library album ID changes
3. Only set CreatedAt in Put() when it's zero (new files), and
explicitly copy missing.CreatedAt to the target in moveMatched()
as defense-in-depth for the INSERT code path
* test: add regression tests for created_at preservation (#5050)
Add tests covering the three aspects of the fix:
- Scanner: moveMatched preserves missing track's created_at
- Scanner: CopyAttributes called for album created_at on album change
- Scanner: CopyAttributes not called when album ID stays the same
- Persistence: Put sets CreatedAt to now for new files with zero value
- Persistence: Put preserves non-zero CreatedAt on insert
- Persistence: Put does not reset CreatedAt on update
Also adds CopyAttributes to MockAlbumRepo for test support.
* test: verify album created_at is updated in cross-library move test (#5050)
Added end-to-end assertion in the cross-library move test to verify that
the new album's CreatedAt field is actually set to the original value after
CopyAttributes runs, not just that the method was called. This strengthens
the test by confirming the mock correctly propagates the timestamp.
* feat: add ISRC support to similar songs matching and plugin interface
Add ISRC (International Standard Recording Code) as a high-priority
identifier in the provider matching algorithm, alongside MBID. The
matching pipeline now uses four strategies in priority order:
ID > MBID > ISRC > Title+Artist fuzzy match.
- Add ISRC field to agents.Song struct
- Add ISRC field to plugin capability SongRef (Go, Rust PDKs)
- Add loadTracksByISRC using json_tree query on tags column
- Integrate ISRC into matchSongsToLibrary, selectBestMatchingSongs,
and buildTitleQueries
https://claude.ai/code/session_01Dd4mTq1VQZag4RNjCVusiF
* chore: regenerate plugin schema after ISRC addition
Run `make gen` to update the generated YAML schema for the
metadata agent capability with the new ISRC field on SongRef.
https://claude.ai/code/session_01Dd4mTq1VQZag4RNjCVusiF
* feat(mediafile): add GetAllByTags method to MediaFileRepository for tag-based retrieval
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(provider): speed up track matching by incorporating prior matches in ISRC and MBID lookups
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Claude <noreply@anthropic.com>
* fix(db): Include items with no annotation for starred=false, handle has_rating=false
* hardcode starred instead
* test: ensure albums and artists without annotations are included in starred and has_rating filters
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: replace starred and has_rating filters with annotationBoolFilter for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: update annotationBoolFilter to handle boolean values correctly in SQL expressions
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>
* feat: skip cross-library detection for single library setup
When only one library is configured, skip the cross-library move detection stage entirely as there are no other libraries to search in. This eliminates unnecessary database queries - the primary performance issue reported by users (5-6 hour scans with 13.5k missing files).
Implementation:
- Added library count check in processCrossLibraryMoves
- Returns input unchanged when len(state.libraries) == 1
- Logs debug message for troubleshooting
* refactor: use lightweight queries for cross-library move detection
Replace selectMediaFile() with newSelect() in FindRecentFilesByMBZTrackID and FindRecentFilesByProperties. These queries only need basic media file columns for hash and path comparisons, not annotations/bookmarks.
Benefits:
- Removes unnecessary LEFT JOINs with annotation and bookmark tables
- Reduces query overhead for cross-library file matching
- Follows existing pattern used by GetMissingAndMatching
The annotation/bookmark joins are user-specific (using loggedUser context) and unused in cross-library matching logic where only Equals() and IsEquivalent() checks are performed.
* test: add coverage for single-library and multi-library cross-library detection
Add test cases to verify:
1. Single-library setup correctly skips cross-library move detection
2. Multi-library setup continues to process cross-library moves
Implementation:
- New test verifies processCrossLibraryMoves returns input unchanged for single library
- Wrapped existing multi-library tests in Context with multiple libraries setup
- Ensures no regressions in multi-library matching behavior
Tests verify:
- Single-library: no database queries, input passed through unchanged
- Multi-library: cross-library matching still works correctly
- Reduces the likelihood of introducing single-library skip bugs in future
* fix: enhance cross-library detection by introducing totalLibraryCount
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: handle cross-library relative paths in playlists
Playlists can now reference songs in other libraries using relative paths.
Previously, relative paths like '../Songs/abc.mp3' would not resolve correctly
when pointing to files in a different library than the playlist file.
The fix resolves relative paths to absolute paths first, then checks which
library they belong to using the library regex. This allows playlists to
reference files across library boundaries while maintaining backward
compatibility with existing single-library relative paths.
Fixes#4617
* fix: enhance playlist path normalization for cross-library support
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: improve handling of relative paths in playlists for cross-library compatibility
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: ensure longest library path matches first to resolve prefix conflicts in playlists
Signed-off-by: Deluan <deluan@navidrome.org>
* test: refactor tests isolation
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: enhance handling of library-qualified paths and improve cross-library playlist support
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify mocks
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: lint
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: improve path resolution for cross-library playlists and enhance error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove unnecessary path validation fallback
Remove validatePathInLibrary function and its fallback logic in
resolveRelativePath. The library matcher should always find the correct
library, including the playlist's own library. If this fails, we now
return an invalid resolution instead of attempting a fallback validation.
This simplifies the code by removing redundant validation logic that
was masking test setup issues. Also fixes test mock configuration to
properly set up library paths that match folder LibraryPath values.
* refactor: consolidate path resolution logic
Collapse resolveRelativePath and resolveAbsolutePath into a unified
resolvePath function, extracting common library matching logic into a
new findInLibraries helper method.
This eliminates duplicate code (~20 lines) while maintaining clear
separation of concerns: resolvePath handles path normalization
(relative vs absolute), and findInLibraries handles library matching.
Update tests to call resolvePath directly with appropriate parameters,
maintaining full test coverage for both absolute and relative path
scenarios.
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: add FindByPaths comment
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: enhance Unicode normalization for path comparisons in playlists. Fixes 4663
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(model): add Rated At field - #4653
Signed-off-by: zacaj <zacaj@zacaj.com>
* fix(ui): ignore empty dates in rating/love tooltips - #4653
* refactor(ui): add isDateSet util function
Signed-off-by: zacaj <zacaj@zacaj.com>
* feat: add tests for isDateSet and rated_at sort mappings
Added comprehensive tests for isDateSet and urlValidate functions in
ui/src/utils/validations.test.js covering falsy values, Go zero date handling,
valid date strings, Date objects, and edge cases.
Added rated_at sort mapping to album, artist, and mediafile repositories,
following the same pattern as starred_at (sorting by rating first, then by
timestamp). This enables proper sorting by rating date in the UI.
---------
Signed-off-by: zacaj <zacaj@zacaj.com>
Co-authored-by: zacaj <zacaj@zacaj.com>
Co-authored-by: Deluan <deluan@navidrome.org>
* fix(server): remove includeMissing from search (always false)
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(search): optimize search order by using natural order for improved performance
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(database): add user_library table and library access methods
Signed-off-by: Deluan <deluan@navidrome.org>
# Conflicts:
# tests/mock_library_repo.go
* feat(database): enhance user retrieval with library associations
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(api): implement library management and user-library association endpoints
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(api): restrict access to library and config endpoints to admin users
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(library): implement library management service and update API routes
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(database): add library filtering to album, folder, and media file queries
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor library service to use REST repository pattern and remove CRUD operations
Signed-off-by: Deluan <deluan@navidrome.org>
* add total_duration column to library and update user_library table
Signed-off-by: Deluan <deluan@navidrome.org>
* fix migration file name
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): add library management features including create, edit, delete, and list functionalities - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): enhance library validation and management with path checks and normalization - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): improve library path validation and error handling - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* use utils/formatBytes
Signed-off-by: Deluan <deluan@navidrome.org>
* simplify DeleteLibraryButton.jsx
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): enhance validation messages and error handling for library paths
Signed-off-by: Deluan <deluan@navidrome.org>
* lint
Signed-off-by: Deluan <deluan@navidrome.org>
* test(scanner): add tests for multi-library scanning and validation
Signed-off-by: Deluan <deluan@navidrome.org>
* test(scanner): improve handling of filesystem errors and ensure warnings are returned
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(controller): add function to retrieve the most recent scan time across all libraries
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): add additional fields and restructure LibraryEdit component for enhanced statistics display
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): enhance LibraryCreate and LibraryEdit components with additional props and styling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(mediafile): add LibraryName field and update queries to include library name
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(missingfiles): add library filter and display in MissingFilesList component
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): implement scanner interface for triggering library scans on create/update
Signed-off-by: Deluan <deluan@navidrome.org>
# Conflicts:
# cmd/wire_gen.go
# cmd/wire_injectors.go
# Conflicts:
# cmd/wire_gen.go
# Conflicts:
# cmd/wire_gen.go
# cmd/wire_injectors.go
* feat(library): trigger scan after successful library deletion to clean up orphaned data
Signed-off-by: Deluan <deluan@navidrome.org>
* rename migration file for user library table to maintain versioning order
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: move scan triggering logic into a helper method for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): add library path and name fields to album and mediafile models
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): add/remove watchers on demand, not only when server starts
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): streamline library handling by using state-libraries for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: track processed libraries by updating state with scan timestamps
Signed-off-by: Deluan <deluan@navidrome.org>
* prepend libraryID for track and album PIDs
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(repository): apply library filtering in CountAll methods for albums, folders, and media files
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(user): add library selection for user creation and editing
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): implement library selection functionality with reducer and UI component
Signed-off-by: Deluan <deluan@navidrome.org>
# Conflicts:
# .github/copilot-instructions.md
# Conflicts:
# .gitignore
* feat(library): add tests for LibrarySelector and library selection hooks
Signed-off-by: Deluan <deluan@navidrome.org>
* test: add unit tests for file utility functions
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): add library ID filtering for album resources
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): streamline library ID filtering in repositories and update resource filtering logic
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(repository): add table name handling in filter functions for SQL queries
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): add refresh functionality on LibrarySelector close
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(artist): add library ID filtering for artists in repository and update resource filtering logic
Signed-off-by: Deluan <deluan@navidrome.org>
# Conflicts:
# persistence/artist_repository.go
* Add library_id field support for smart playlists
- Add library_id field to smart playlist criteria system
- Supports Is and IsNot operators for filtering by library ID
- Includes comprehensive test coverage for single values and lists
- Enables creation of library-specific smart playlists
* feat(subsonic): implement user-specific library access in GetMusicFolders
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): enhance LibrarySelectionField to extract library IDs from record
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(subsonic): update GetIndexes and GetArtists method to support library ID filtering
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: ensure LibrarySelector dropdown refreshes on button close
Added refresh() call when closing the dropdown via button click to maintain
consistency with the ClickAwayListener behavior. This ensures the UI
updates properly regardless of how the dropdown is closed, fixing an
inconsistent refresh behavior between different closing methods.
The fix tracks the previous open state and calls refresh() only when
the dropdown was open and is being closed by the button click.
* refactor: simplify getUserAccessibleLibraries function and update related tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance selectedMusicFolderIds function to handle valid music folder IDs and improve fallback logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: change ArtistRepository.GetIndex to accept multiple library IDs
Updated the GetIndex method signature to accept a slice of library IDs instead of a single ID, enabling support for filtering artists across multiple libraries simultaneously.
Changes include:
- Modified ArtistRepository interface in model/artist.go
- Updated implementation in persistence/artist_repository.go with improved library filtering logic
- Refactored Subsonic API browsing.go to use new selectedMusicFolderIds helper
- Added comprehensive test coverage for multiple library scenarios
- Updated mock repository implementation for testing
This change improves flexibility for multi-library operations while maintaining backward compatibility through the selectedMusicFolderIds helper function.
* feat: add library access validation to selectedMusicFolderIds
Enhanced the selectedMusicFolderIds function to validate musicFolderId parameters
against the user's accessible libraries. Invalid library IDs (those the user
doesn't have access to) are now silently filtered out, improving security by
preventing users from accessing libraries they don't have permission for.
Changes include:
- Added validation logic to check musicFolderId parameters against user's accessible libraries
- Added slices package import for efficient validation
- Enhanced function documentation to clarify validation behavior
- Added comprehensive test cases covering validation scenarios
- Maintains backward compatibility with existing behavior
* feat: implement multi-library support for GetAlbumList and GetAlbumList2 endpoints
- Enhanced selectedMusicFolderIds helper to validate and filter library IDs
- Added ApplyLibraryFilter function in filter/filters.go for library filtering
- Updated getAlbumList to support musicFolderId parameter filtering
- Added comprehensive tests for multi-library functionality
- Supports single and multiple musicFolderId values
- Falls back to all accessible libraries when no musicFolderId provided
- Validates library access permissions for user security
* feat: implement multi-library support for GetRandomSongs, GetSongsByGenre, GetStarred, and GetStarred2
- Added multi-library filtering to GetRandomSongs endpoint using musicFolderId parameter
- Added multi-library filtering to GetSongsByGenre endpoint using musicFolderId parameter
- Enhanced GetStarred and GetStarred2 to filter artists, albums, and songs by library
- Added Options field to MockMediaFileRepo and MockArtistRepo for test compatibility
- Added comprehensive Ginkgo/Gomega tests for all new multi-library functionality
- All tests verify proper SQL filter generation and library access validation
- Supports single/multiple musicFolderId values with fallback to all accessible libraries
* refactor: optimize starred items queries with parallel execution and fix test isolation
Refactored starred items functionality by extracting common logic into getStarredItems()
method that executes artist, album, and media file queries in parallel for better performance.
This eliminates code duplication between GetStarred and GetStarred2 methods while improving
response times through concurrent database queries using run.Parallel().
Also fixed test isolation issues by adding missing auth.Init(ds) call in album lists test setup.
This resolves nil pointer dereference errors in GetStarred and GetStarred2 tests when run independently.
* fix: add ApplyArtistLibraryFilter to filter artists by associated music folders
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add library access methods to User model
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement library access filtering for artist queries based on user permissions
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance artist library filtering based on user permissions and optimize library ID retrieval
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: return error when any musicFolderId is invalid or inaccessible
Changed behavior from silently filtering invalid library IDs to returning
ErrorDataNotFound (code 70) when any provided musicFolderId parameter
is invalid or the user doesn't have access to it.
The error message includes the specific library number for better debugging.
This affects album/song list endpoints (getAlbumList, getRandomSongs,
getSongsByGenre, getStarred) to provide consistent error handling
across all Subsonic API endpoints.
Updated corresponding tests to expect errors instead of silent filtering.
* feat: add musicFolderId parameter support to Search2 and Search3 endpoints
Implemented musicFolderId parameter support for Subsonic API Search2 and Search3 endpoints, completing multi-library functionality across all Subsonic endpoints.
Key changes:
- Added musicFolderId parameter handling to Search2 and Search3 endpoints
- Updated search logic to filter results by specified library or all accessible libraries when parameter not provided
- Added proper error handling for invalid/inaccessible musicFolderId values
- Refactored SearchableRepository interface to support library filtering with variadic QueryOptions
- Updated repository implementations (Album, Artist, MediaFile) to handle library filtering in search operations
- Added comprehensive test coverage with robust assertions verifying library filtering works correctly
- Enhanced mock repositories to capture QueryOptions for test validation
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: refresh LibraryList on scan end
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: allow editing name of main library
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement SendBroadcastMessage method for event broadcasting
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add event broadcasting for library creation, update, and deletion
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add useRefreshOnEvents hook for custom refresh logic on event changes
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance library management with refresh event broadcasting
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: replace AddUserLibrary and RemoveUserLibrary with SetUserLibraries for better library management
Signed-off-by: Deluan <deluan@navidrome.org>
* chore: remove commented-out genre repository code from persistence tests
* feat: enhance library selection with master checkbox functionality
Added a master checkbox to the SelectLibraryInput component, allowing users to select or deselect all libraries at once. This improves user experience by simplifying the selection process when multiple libraries are available. Additionally, updated translations in the en.json file to include a new message for selecting all libraries, ensuring consistency in user interface messaging.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add default library assignment for new users
Introduced a new column `default_new_users` in the library table to
facilitate automatic assignment of default libraries to new regular users.
When a new user is created, they will now be assigned to libraries marked
as default, enhancing user experience by ensuring they have immediate access
to essential resources. Additionally, updated the user repository logic
to handle this new functionality and modified the user creation validation
to reflect that library selection is optional for non-admin users.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: correct updated_at assignment in library repository
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: improve cache buffering logic
Refactored the cache buffering logic to ensure thread safety when checking
the buffer length
Signed-off-by: Deluan <deluan@navidrome.org>
* fix formating
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement per-library artist statistics with automatic aggregation
Implemented comprehensive multi-library support for artist statistics that
automatically aggregates stats from user-accessible libraries. This fundamental
change moves artist statistics from global scope to per-library granularity
while maintaining backward compatibility and transparent operation.
Key changes include:
- Migrated artist statistics from global artist.stats to per-library library_artist.stats
- Added automatic library filtering and aggregation in existing Get/GetAll methods
- Updated role-based filtering to work with per-library statistics storage
- Enhanced statistics calculation to process and store stats per library
- Implemented user permission-aware aggregation that respects library access control
- Added comprehensive test coverage for library filtering and restricted user access
- Created helper functions to ensure proper library associations in tests
This enables users to see statistics that accurately reflect only the content
from libraries they have access to, providing proper multi-tenant behavior
while maintaining the existing API surface and UI functionality.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add multi-library support with per-library tag statistics - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: genre and tag repositories. add comprehensive tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add multi-library support to tag repository system
Implemented comprehensive library filtering for tag repositories to support the multi-library feature. This change ensures that users only see tags from libraries they have access to, while admin users can see all tags.
Key changes:
- Enhanced TagRepository.Add() method to accept libraryID parameter for proper library association
- Updated baseTagRepository to implement library-aware queries with proper joins
- Added library_tag table integration for per-library tag statistics
- Implemented user permission-based filtering through user_library associations
- Added comprehensive test coverage for library filtering scenarios
- Updated UI data provider to include tag filtering by selected libraries
- Modified scanner to pass library ID when adding tags during folder processing
The implementation maintains backward compatibility while providing proper isolation between libraries for tag-based operations like genres and other metadata tags.
* refactor: simplify artist repository library filtering
Removed conditional admin logic from applyLibraryFilterToArtistQuery method
and unified the library filtering approach to match the tag repository pattern.
The method now always uses the same SQL join structure regardless of user role,
with admin access handled automatically through user_library associations.
Added artistLibraryIdFilter function to properly qualify library_id column
references and prevent SQL ambiguity errors when multiple tables contain
library_id columns. This ensures the filter targets library_artist.library_id
specifically rather than causing ambiguous column name conflicts.
* fix: resolve LibrarySelectionField validation error for non-admin users
Fixed validation error 'At least one library must be selected for non-admin users' that appeared even when libraries were selected. The issue was caused by a data format mismatch between backend and frontend.
The backend sends user data with libraries as an array of objects, but the LibrarySelectionField component expects libraryIds as an array of IDs. Added data transformation in the data provider's getOne method to automatically convert libraries array to libraryIds format when fetching user records.
Also extracted validation logic into a separate userValidation module for better code organization and added comprehensive test coverage to prevent similar issues.
* refactor: remove unused library access functions and related tests
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename search_test.go to searching_test.go for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: add user context to scrobble buffer getParticipants call
Added user context handling to scrobbleBufferRepository.Next method to resolve
SQL error 'no such column: library_artist.library_id' when processing scrobble
entries in multi-library environments. The artist repository now requires user
context for proper library filtering, so we fetch the user and temporarily
inject it into the context before calling getParticipants. This ensures
background scrobbling operations work correctly with multi-library support.
* feat: add cross-library move detection for scanner
Implemented cross-library move detection for the scanner phase 2 to properly handle files moved between libraries. This prevents users from losing play counts, ratings, and other metadata when moving files across library boundaries.
Changes include:
- Added MediaFileRepository methods for two-tier matching: FindRecentFilesByMBZTrackID (primary) and FindRecentFilesByProperties (fallback)
- Extended scanner phase 2 pipeline with processCrossLibraryMoves stage that processes files unmatched within their library
- Implemented findCrossLibraryMatch with MusicBrainz Release Track ID priority and intrinsic properties fallback
- Updated producer logic to handle missing tracks without matches, ensuring cross-library processing
- Updated tests to reflect new producer behavior and cross-library functionality
The implementation uses existing moveMatched function for unified move operations, automatically preserving all user data through database foreign key relationships. Cross-library moves are detected using the same Equals() and IsEquivalent() matching logic as within-library moves for consistency.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add album annotation reassignment for cross-library moves
Implemented album annotation reassignment functionality for the scanner's missing tracks phase. When tracks move between libraries and change album IDs, the system now properly reassigns album annotations (starred status, ratings) from the old album to the new album. This prevents loss of user annotations when tracks are moved across library boundaries.
The implementation includes:
- Thread-safe annotation reassignment using mutex protection
- Duplicate reassignment prevention through processed album tracking
- Graceful error handling that doesn't fail the entire move operation
- Comprehensive test coverage for various scenarios including error conditions
This enhancement ensures data integrity and user experience continuity during cross-library media file movements.
* fix: address PR review comments for multi-library support
Fixed several issues identified in PR review:
- Removed unnecessary artist stats initialization check since the map is already initialized in PostScan()
- Improved code clarity in user repository by extracting isNewUser variable to avoid checking count == 0 twice
- Fixed library selection logic to properly handle initial library state and prevent overriding user selections
These changes address code quality and logic issues identified during the multi-library support PR review.
* feat: add automatic playlist statistics refreshing
Implemented automatic playlist statistics (duration, size, song count) refreshing
when tracks are modified. Added new refreshStats() method to recalculate
statistics from playlist tracks, and SetTracks() method to update tracks
and refresh statistics atomically. Modified all track manipulation methods
(RemoveTracks, AddTracks, AddMediaFiles) to automatically refresh statistics.
Updated playlist repository to use the new SetTracks method for consistent
statistics handling.
* refactor: rename AddTracks to AddMediaFilesByID for clarity
Renamed the AddTracks method to AddMediaFilesByID throughout the codebase
to better reflect its purpose of adding media files to a playlist by their IDs.
This change improves code readability and makes the method name more descriptive
of its actual functionality. Updated all references in playlist model, tests,
core playlist logic, and Subsonic API handlers to use the new method name.
* refactor: consolidate user context access in persistence layer
Removed duplicate helper functions userId() and isAdmin() from sql_base_repository.go and consolidated all user context access to use loggedUser(r.ctx).ID and loggedUser(r.ctx).IsAdmin consistently across the persistence layer.
This change eliminates code duplication and provides a single, consistent pattern for accessing user context information in repository methods. All functionality remains unchanged - this is purely a code cleanup refactoring.
* refactor: eliminate MockLibraryService duplication using embedded struct
- Replace 235-line MockLibraryService with 40-line embedded struct pattern
- Enhance MockLibraryRepo with service-layer methods (192→310 lines)
- Maintain full compatibility with existing tests
- All 72 nativeapi specs pass with proper error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: cleanup
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(subsonic): search by MBID functionality
Updated the search methods in the mediaFileRepository, albumRepository, and artistRepository to support searching by MBID in addition to the existing query methods. This change improves the efficiency of media file, album, and artist searches, allowing for faster retrieval of records based on MBID.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(subsonic): enhance MBID search functionality for albums and artists
Updated the search functionality to support searching by MBID for both
albums and artists. The fullTextFilter function was modified to accept
additional MBID fields, allowing for more comprehensive searches. New
tests were added to ensure that the search functionality correctly
handles MBID queries, including cases for missing entries and the
includeMissing parameter. This enhancement improves the overall search
capabilities of the application, making it easier for users to find
specific media items by their unique identifiers.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(subsonic): normalize MBID to lowercase for consistent querying
Updated the MBID handling in the SQL search logic to convert the input
to lowercase before executing the query. This change ensures that
searches are case-insensitive, improving the accuracy and reliability
of the search results when querying by MBID.
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: implement RecentlyAddedByModTime support for mediafiles
Fixes#4046 by adding recently_added sort mapping to MediaFileRepository that respects the RecentlyAddedByModTime configuration setting. Previously, this feature only worked for albums, causing inconsistent behavior when clients requested tracks sorted by 'recently added'.
Changes include:
- Add mediaFileRecentlyAddedSort() function that returns 'updated_at' when RecentlyAddedByModTime=true, 'created_at' otherwise
- Add 'recently_added' sort mapping to mediafile repository
- Add comprehensive tests to verify both configuration scenarios
This ensures consistent sorting behavior between albums and tracks when using the RecentlyAddedByModTime feature.
* fix: update createdAt field to sort by recently added
Modified the createdAt field in the SongList component to include a sortBy
attribute set to "recently_added". This change ensures that the media files
are displayed in the order they were added, improving the user experience
when browsing through recently added items.
Signed-off-by: Deluan <deluan@navidrome.org>
* better testing
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(ui,scanner,subsonic): Allow nullable replaygain and support 0.0
Resolves#4236.
Makes the replaygain columns (track/album gain/peak) nullable.
Converts the type to a pointer, allowing for 0.0 (a valid value) to be returned from Subsonic.
Updates tests for this behavior.
* small refactor
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>
* fix(ui): ensure album tracks are always ordered by disc and track number (fixes#3720)
* refactor(ui): remove obsolete release date grouping logic from SongDatagrid and AlbumSongs
* fix(ui): ensure correct album track ordering in context menu and play button
* fix: Update album sort to use album_id instead of release_date
* refactor: Adjust filters in PlayButton and AlbumContextMenu
* fix: correct typo in comment regarding participants in GetMissingAndMatching function
* fix: prevent visual separation of tracks on same disc
Removes the leftover `releaseDate` check from the `firstTracksOfDiscs` calculation in `SongDatagridBody`. This check caused unnecessary `DiscSubtitleRow` insertions when tracks on the same disc had different release dates, leading to an incorrect visual grouping that resembled a multi-disc layout.
This change ensures disc subtitles are only shown when the actual `discNumber` changes, correcting the UI presentation issue reported in issue #3720 after PR #3975.
* fix: remove remaining releaseDate references in SongDatagrid
Cleaned up leftover `releaseDate` references in `SongDatagrid.jsx`:
- Removed `releaseDate` parameter and usage from `handlePlaySubset` in `DiscSubtitleRow`.
- Removed `releaseDate` prop passed to `AlbumContextMenu` in `DiscSubtitleRow`.
- Removed `releaseDate` from the drag item data in `SongDatagridRow`.
- Removed `releaseDate` parameter and the corresponding `else` block from the `playSubset` function in `SongDatagridBody`.
This ensures the component consistently uses `discNumber` for grouping and playback actions initiated from the disc subtitle, fully resolving the inconsistencies related to issue #3720.
* fix(ui): update sort mapping for title in mediafile repository
* fix(ui): create sort mapping for username in share repository
* fix(ui): create sort mapping for owner_name in playlist repository
* fix(ui): create sort mapping for username in player repository
* fix(ui): remove sort mapping for track number in mediafile repository
* chore: add todo to change user_name
* Deterministic pagination in random albums sort
* Reseed on first random page
* Add unit tests
* Use rand in Subsonic API
* Use different seeds per user on SEEDEDRAND() SQLite3 function
* Small refactor
* Fix id mismatch
* Add seeded random to media_file (subsonic endpoint `getRandomSongs`)
* Refactor
* Remove unneeded import
---------
Co-authored-by: Deluan <deluan@navidrome.org>
* Start migration to dbx package
* Fix annotations and bookmarks bindings
* Fix tests
* Fix more tests
* Remove remaining references to beego/orm
* Add PostScanner/PostMapper interfaces
* Fix importing SmartPlaylists
* Renaming
* More renaming
* Fix artist DB mapping
* Fix playlist updates
* Remove bookmarks at the end of the test
* Remove remaining `orm` struct tags
* Fix user timestamps DB access
* Fix smart playlist evaluated_at DB access
* Fix search3
* Optimize pagination, removing offset
* For search, don't add `where` clause for empty queries
* Revert "Replace `COUNT(DISTINCT primary_key)` with `COUNT(*)`"
Genres are required as part of the count queries, so filter by genres work
* Optimize search3 query, using order by id if it is a "" query.
Also fix the optimizePagination query logic
* Allow offset optimizer threshold to be configured