* fix(artwork): fallback mediafile cover art to disc artwork before album
Changed the mediafile cover art fallback chain to go through disc artwork
before album artwork (mediafile → disc → album). Previously, mediafiles
without embedded art fell back directly to album cover, bypassing any
disc-specific artwork. Renamed AlbumCoverArtID() to DiscCoverArtID() to
encapsulate the disc-vs-album decision in a single method, used by both
CoverArtID() and the mediafile artwork reader.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(artwork): fix cache invalidation for mediafile and album cover art
Include imagesUpdatedAt from album folders in the mediafile artwork
reader's cache key, so that when a cover image file changes on disk
(without audio metadata changes) the mediafile cache properly
invalidates. Also include CoverArtPriority unconditionally in the album
artwork reader's cache key hash, so that changing the priority order
with external services disabled correctly invalidates the album cache.
* fix(artwork): skip disc artwork resolution for single-disc albums
Single-disc albums with DiscNumber=1 were unnecessarily routed through
discArtworkReader, which does extra DB queries only to fall through to
album art anyway. Now only multi-disc albums use the disc fallback path.
* refactor(artwork): restore AlbumCoverArtID as a separate method
Extract AlbumCoverArtID back out of DiscCoverArtID so the single-disc
fallback path in reader_mediafile can reference it by name instead of
inlining the artwork ID construction.
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add shared ImageUploadService for entity image management
* feat: add UploadedImage field and methods to Artist model
* feat: add uploaded_image column to artist table
* feat: add ArtistImageFolder config option
* refactor: wire ImageUploadService and delegate playlist file ops to it
Wire ImageUploadService into the DI container and refactor the playlist
service to delegate image file operations (SetImage/RemoveImage) to the
shared ImageUploadService, removing duplicated file I/O logic. A local
ImageUploadService interface is defined in core/playlists to avoid an
import cycle between core and core/playlists.
* feat: artist artwork reader checks uploaded image first
* feat: add image-folder priority source for artist artwork
* feat: cache key invalidation for image-folder and uploaded images
* refactor: extract shared image upload HTTP helpers
* feat: add artist image upload/delete API endpoints
* refactor: playlist handlers use shared image upload helpers
* feat: add shared ImageUploadOverlay component
* feat: add i18n keys for artist image upload
* feat: add image upload overlay to artist detail pages
* refactor: playlist details uses shared ImageUploadOverlay component
* fix: add gosec nolint directive for ParseMultipartForm
* refactor: deduplicate image upload code and optimize dir scanning
- Remove dead ImageFilename methods from Artist and Playlist models
(production code uses core.imageFilename exclusively)
- Extract shared uploadedImagePath helper in model/image.go
- Extract findImageInArtistFolder to deduplicate dir-scanning logic
between fromArtistImageFolder and getArtistImageFolderModTime
- Fix fileInputRef in useCallback dependency array
* fix: include artist UpdatedAt in artwork cache key
Without this, uploading or deleting an artist image would not
invalidate the cached artwork because the cache key was only based
on album folder timestamps, not the artist's own UpdatedAt field.
* feat: add Portuguese translations for artist image upload
* refactor: use shared i18n keys for cover art upload messages
Move cover art upload/remove translations from per-entity sections
(artist, playlist) to a shared top-level "message" section, avoiding
duplication across entity types and translation files.
* refactor: move cover art i18n keys to shared message section for all languages
* refactor: simplify image upload code and eliminate redundancies
Extracted duplicate image loading/lightbox state logic from
DesktopArtistDetails and MobileArtistDetails into a shared
useArtistImageState hook. Moved entity type constants to the consts
package and replaced raw string literals throughout model, core, and
nativeapi packages. Exported model.UploadedImagePath and reused it in
core/image_upload.go to consolidate path construction. Cached the
ArtistImageFolder lookup result in artistReader to eliminate a redundant
os.ReadDir call on every artwork request.
Signed-off-by: Deluan <deluan@navidrome.org>
* style: fix prettier formatting in ImageUploadOverlay
* fix: address code review feedback on image upload error handling
- RemoveImage now returns errors instead of swallowing them
- Artist handlers distinguish not-found from other DB errors
- Defer multipart temp file cleanup after parsing
* fix: enforce hard request size limit with MaxBytesReader for image uploads
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove built-in Spotify integration
Remove the Spotify adapter and all related configuration, replacing
the built-in integration with the plugin system. This deletes the
adapters/spotify package, removes Spotify config options (ID/Secret),
updates the default agents list from "deezer,lastfm,spotify" to
"deezer,lastfm", and cleans up all references across configuration,
metrics, logging, artwork caching, and documentation. Users with
Spotify config options will now see a warning that the options are
no longer available.
* feat: add ListenBrainz to list of default agents
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(artwork): add KindDiscArtwork and ParseDiscArtworkID
Add new disc artwork kind with 'dc' prefix for per-disc cover art
support. The composite ID format is albumID:discNumber, parsed by
the new ParseDiscArtworkID helper.
* feat(conf): add DiscArtPriority configuration option
Default: 'disc*.*, cd*.*, embedded'. Controls how per-disc cover
art is resolved, following the same pattern as CoverArtPriority
and ArtistArtPriority.
* feat(artwork): implement extractDiscNumber helper
Extracts disc number from filenames based on glob patterns by
parsing leading digits from the wildcard-matched portion.
Used for matching disc-specific artwork files like disc1.jpg.
* feat(artwork): implement fromDiscExternalFile source function
Disc-aware variant of fromExternalFile that filters image files
by disc number (extracted from filename) or folder association
(for multi-folder albums).
* feat(artwork): implement discArtworkReader
Resolves disc artwork using DiscArtPriority config patterns.
Supports glob patterns with disc number extraction, embedded
images from first track, and falls back to album cover art.
Handles both multi-folder and single-folder multi-disc albums.
* feat(artwork): register disc artwork reader in dispatcher
Add KindDiscArtwork case to getArtworkReader switch, routing
disc artwork requests to the new discArtworkReader.
* feat(subsonic): add CoverArt field to DiscTitle response
Implements OpenSubsonic PR #220: optional cover art ID in
DiscTitle responses for per-disc artwork support.
* feat(subsonic): populate CoverArt in DiscTitle responses
Each DiscTitle now includes a disc artwork ID (dc-albumID:discNum)
that clients can use with getCoverArt to retrieve per-disc artwork.
* style: fix file permission in test to satisfy gosec
* feat(ui): add disc cover art display and lightbox functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify disc artwork code
- Add DiscArtworkID constructor to encapsulate the "albumID:discNumber"
format in one place
- Convert fromDiscExternalFile to a method on discArtworkReader,
reducing parameter count from 6 to 2
- Remove unused rootFolder field from discArtworkReader
* style: fix prettier formatting in subsonic index
* style(ui): move cursor style to makeStyles in SongDatagrid
* feat(artwork): add discsubtitle option to DiscArtPriority
Allow matching disc cover art by the disc's subtitle/name.
When the "discsubtitle" keyword is in the priority list, image files
whose stem matches the disc subtitle (case-insensitive) are used.
This is useful for box sets with named discs (e.g., "The Blue Disc.jpg").
* feat(configuration): update discartpriority to include cover art options
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(artwork): preserve animated image artwork during resize
Detect animated GIFs, WebPs, and APNGs via lightweight byte scanning
and preserve their animation when serving resized artwork. Animated GIFs
are converted to animated WebP via ffmpeg with optional downscaling;
animated WebP/APNG are returned as-is since ffmpeg cannot re-encode them.
Adds ConvertAnimatedImage to the FFmpeg interface for piping stdin data
through ffmpeg with animated WebP output.
* fix(artwork): address code review feedback for animated artwork
Fix ReadCloser leak where ffmpeg pipe's Close was discarded by
io.NopCloser wrapping — now preserves ReadCloser semantics when the
resized reader already supports Close. Use uint64 for PNG chunk position
to prevent potential overflow on 32-bit platforms. Add integration tests
for the animation branching logic in resizeImage.
* test(artwork): add benchmark helpers for generating test images
* test(artwork): add image decode benchmarks for JPEG/PNG at various sizes
* test(artwork): add image resize benchmarks for Lanczos at various sizes
* test(artwork): add image encode benchmarks for JPEG quality levels and PNG
* test(artwork): add full resize pipeline benchmark (decode+resize+encode)
* test(artwork): add tag extraction benchmark for embedded art
* test(cache): add file cache benchmarks for read, write, and concurrent access
* test(artwork): add E2E benchmarks for artwork.Get with cache on/off and concurrency
* fix(test): use absolute path for tag extraction benchmark fixture
* test(artwork): add resize alternatives benchmark comparing resamplers
* perf(artwork): switch to CatmullRom resampler and JPEG for square images
Replace imaging.Lanczos with imaging.CatmullRom for image resizing
(30% faster, indistinguishable quality at thumbnail sizes). Stop forcing
PNG encoding for square images when the source is JPEG — JPEG is smaller
and faster to encode. Square images from JPEG sources went from 52ms to
10ms (80% improvement). Add sync.Pool for encode buffers to reduce GC
pressure under concurrent load.
* perf(artwork): increase cache warmer concurrency from 2 to 4 workers
Resize is CPU-bound, so more workers improve throughput on multi-core
systems. Doubled worker count to better utilize available cores during
background cache warming.
* perf(artwork): switch to xdraw.ApproxBiLinear and always encode as JPEG
Replace disintegration/imaging with golang.org/x/image/draw for image
resizing. This eliminates ~92K allocations per resize (from imaging's
internal goroutine parallelism) down to ~20, reducing GC pressure under
concurrent load.
Always encode resized artwork as JPEG regardless of source format, since
cover art doesn't need transparency. This is ~5x faster than PNG encode
and produces much smaller output (e.g. 18KB JPEG vs 124KB PNG).
* perf(artwork): skip external API call when artist image URL is cached
ArtistImage() was always calling the external agent (Spotify/Last.fm)
to get the image URL, even when the artist already had URLs stored in
the database. This caused every artist image request to block on an
external API call, creating severe serialization when loading artist
grids (5-20 seconds for the first page).
Now use the stored URL directly when available. Artists with no stored
URL still fetch synchronously. Background refresh via UpdateArtistInfo
handles TTL-based URL updates.
* perf(artwork): increase getCoverArt throttle from NumCPU/3 to NumCPU
The previous default of max(2, NumCPU/3) was too aggressive for artist
images which are I/O-bound (downloading from external CDNs), not
CPU-bound. On an 8-core machine this meant only 2 concurrent requests,
causing a staircase pattern where 12 images took ~2.4s wall-clock.
Bumping to max(4, NumCPU) cuts wall-clock time by ~50% for artist image
grids while still preventing unbounded concurrency for CPU-bound resizes.
* perf(artwork): encode resized images as WebP instead of JPEG
Switch from JPEG to WebP encoding for resized artwork using gen2brain/webp
(libwebp via WASM, no CGo). WebP produces ~74% smaller output at the same
quality with only ~25% slower full-pipeline encode time (cached, so only
paid once per artwork+size).
Use NRGBA image type to preserve alpha channel in WebP output, and
transparent padding for square canvas instead of black.
Also removes the disintegration/imaging dependency entirely by replacing
imaging.Fill in playlist tile generation with a custom fillCenter function
using xdraw.ApproxBiLinear.
* perf(artwork): switch from ApproxBiLinear to BiLinear scaling for improved image processing
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(configuration): rename CoverJpegQuality to CoverArtQuality and update references
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(artwork): add DevJpegCoverArt option to control JPEG encoding for cover art
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(artwork): remove redundant transparent fill and handle encode errors in resizeImage
Removed a no-op draw.Draw call that filled the NRGBA canvas with
transparent pixels — NewNRGBA already zero-initializes to fully
transparent. Also added an early return on encode failure to avoid
allocating and copying potentially corrupt buffer data before returning
the error.
* fix(configuration): reorder default agents (deezer is faster)
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(test): resolve dogsled lint warning in tag extraction benchmark
Use all return values from runtime.Caller instead of discarding three
with blank identifiers, which triggered the dogsled linter.
* fix(artwork): revert cache key format
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(configuration): remove deprecated CoverJpegQuality field and update references to CoverArtQuality
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
When files were moved between libraries, the small channel buffers (size 1)
throughout the watcher pipeline caused backpressure that led to dropped
filesystem events. This meant only some of the affected folders were scanned,
preventing cross-library move detection from working correctly.
Increase all watcher channel buffers to 500 and switch to blocking sends
to ensure no filesystem events are silently dropped.
* fix: use ADTS format for AAC transcoding to avoid silent output on ffmpeg 8.0+
The fragmented MP4 muxer (`-f ipod -movflags frag_keyframe+empty_moov`)
produces corrupt/silent audio when ffmpeg pipes to stdout, confirmed on
ffmpeg 8.0+. The moof atom offset values are zeroed out in pipe mode,
causing AAC decoder errors. Switch to `-f adts` (raw AAC framing) which
works reliably via pipe and is widely supported by clients including
UPnP/Sonos devices.
* fix: exclude AAC from transcode decision, as it is not working for Sonos.
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
When a client requests transcoding with an explicit format (e.g.,
format=opus) but no maxBitRate, buildLegacyClientInfo was adding a
direct play profile matching the source format. Since there was no
bitrate constraint to block it, MakeDecision would match the source
against the direct play profile and return the raw file instead of
transcoding. This fix only adds the direct play profile when no
explicit format was requested (bitrate-only downsampling) or when the
requested format matches the source format (allowing direct play when
no actual transcoding is needed).
* refactor: rename core/transcode directory to core/stream
* refactor: update all imports from core/transcode to core/stream
* refactor: rename exported symbols to fit core/stream package name
* refactor: simplify MediaStreamer interface to single NewStream method
Remove the two-method interface (NewStream + DoStream) in favor of a
single NewStream(ctx, mf, req) method. Callers are now responsible for
fetching the MediaFile before calling NewStream. This removes the
implicit DB lookup from the streamer, making it a pure streaming
concern.
* refactor: update all callers from DoStream to NewStream
* chore: update wire_gen.go and stale comment for core/stream rename
* refactor: update wire command to handle GO_BUILD_TAGS correctly
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: distinguish not-found from internal errors in public stream handler
* refactor: remove unused ID field from stream.Request
* refactor: simplify ResolveRequestFromToken to receive *model.MediaFile
Move MediaFile fetching responsibility to callers, making the method
focused on token validation and request resolution. Remove ErrMediaNotFound
(no longer produced). Update GetTranscodeStream handler to fetch the
media file before calling ResolveRequestFromToken.
* refactor: extend tokenTTL from 12 to 48 hours
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(transcode): apply player MaxBitRate cap and use format-aware default bitrates
Add player MaxBitRate cap to the transcode decider so server-side player
bitrate limits are respected when making OpenSubsonic transcode decisions.
The player cap is applied only when it is more restrictive than the client's
maxAudioBitrate (or when the client has no limit).
Also replace the hardcoded 256 kbps default with a format-aware lookup that
checks the DB first (for user-customized values), then built-in defaults,
and finally falls back to 256 kbps. For lossless→lossy transcoding, prefer
maxTranscodingAudioBitrate over maxAudioBitrate when available.
* test(e2e): add tests for player MaxBitRate cap and format-aware default bitrates
Add e2e tests covering:
- Player MaxBitRate forcing transcode when source exceeds cap
- Player MaxBitRate having no effect when source is under cap
- Client limit winning when more restrictive than player MaxBitRate
- Player MaxBitRate winning when more restrictive than client limit
- Player MaxBitRate=0 having no effect
- Format-aware defaults: mp3 (192kbps), opus (128kbps) instead of hardcoded 256
- maxAudioBitrate fallback for lossless→lossy when no maxTranscodingAudioBitrate
- maxTranscodingAudioBitrate taking priority over maxAudioBitrate
- Combined player + client limits flowing correctly through decision→stream
* feat(transcode): update transcoding profiles to add flac, filter by supported codecs, and ensure mp3 fallback
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(db): ensure all default transcodings exist on upgrade
Older installations that were seeded before aac/flac were added to
DefaultTranscodings may be missing these entries. The previous migration
only added flac; this one ensures all default transcodings are present
without touching user-customized entries.
* test: remove duplication
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(artwork): search parent folders for album cover art in multi-disc layouts
When albums have tracks in subdirectories (e.g., CD1/, CD2/), Navidrome
only searched those subdirectories for cover images. This meant cover art
placed in the album's root folder (e.g., "Artist/Album/cover.jpg") was
not found. Now loadAlbumFoldersPaths also queries parent folders of the
album's media folders, so cover art in the album root is discovered.
* fix(artwork): simplify parent folder detection for album cover art lookup
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(album): propagate non-ErrNotFound errors from parent folder lookup
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Add comprehensive e2e tests for getTranscodeDecision and
getTranscodeStream endpoints covering direct play, transcoding,
error handling, and round-trip token validation. Refactor
buildPostReq to reuse buildReq for auth params, remove unused
WAV/AAC test tracks, and consolidate duplicate test assertions.
* 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>
* feat(playlists): add percentage-based limits to smart playlists
Add a new `limitPercent` JSON field to Criteria that allows smart playlist
limits to be expressed as a percentage of matching tracks rather than a
fixed number. For example, a playlist matching 450 songs with a 10% limit
returns 45 songs, scaling dynamically as the library grows.
When `limitPercent` is set, refreshSmartPlaylist runs a COUNT query first
to determine the total matching tracks, then resolves the percentage to an
absolute LIMIT before executing the main query. The fixed `limit` field
takes precedence when both are set. Values are clamped to [0, 100] during
JSON unmarshaling.
No database migration is needed since rules are stored as a JSON string.
* fix(criteria): validate percentage limit range in IsPercentageLimit method
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(criteria): ensure idempotency of ToSql method for expressions
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add lyrics provider plugin capability
Refactor the lyrics system from a static function to an interface-based
service that supports WASM plugin providers. Plugins listed in the
LyricsPriority config (alongside "embedded" and file extensions) are
now resolved through the plugin system.
Includes capability definition, Go/Rust PDK, adapter, Wire integration,
and tests for plugin fallback behavior.
* test(plugins): add lyrics capability integration test with test plugin
* fix(plugins): default lyrics language to 'xxx' when plugin omits it
Per the OpenSubsonic spec, the server must return 'und' or 'xxx' when
the lyrics language is unknown. The lyrics plugin adapter was passing
an empty string through when a plugin didn't provide a language value.
This defaults the language to 'xxx', consistent with all other callers
of model.ToLyrics() in the codebase.
* refactor(plugins): rename lyrics import to improve clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(lyrics): update TrackInfo description for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(lyrics): enhance lyrics plugin handling and case sensitivity
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update payload type to string with byte format for task data
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Introduced a typed Claims struct in core/auth to replace the raw
map[string]any approach used for JWT claims throughout the codebase.
This provides compile-time safety and better readability when creating,
validating, and extracting JWT tokens. Also upgraded lestrrat-go/jwx
from v2 to v3 and go-chi/jwtauth to v5.4.0, adapting all callers to
the new API where token accessor methods now return tuples instead of
bare values. Updated all affected handlers, middleware, and tests.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(playlist): add migration for playlist image field rename and external URL
* refactor(playlist): rename ImageFile to UploadedImage and ArtworkPath to UploadedImagePath
Rename playlist model fields and methods for clarity in preparation for
adding external image URL and sidecar image support. Add the new
ExternalImageURL field to the Playlist model.
* feat(playlist): parse #EXTALBUMARTURL directive in M3U imports
* feat(playlist): always sync ExternalImageURL on re-scan, preserve UploadedImage
* feat(artwork): add sidecar image discovery and cache invalidation for playlists
Add playlist sidecar image support to the artwork reader fallback chain.
A sidecar image (e.g., MyPlaylist.jpg next to MyPlaylist.m3u) is discovered
via case-insensitive base name matching using model.IsImageFile(). Cache
invalidation uses max(playlist.UpdatedAt, imageFile.ModTime()) to bust
stale artwork when sidecar or ExternalImageURL local files change.
* feat(artwork): add external image URL source to playlist artwork reader
Add fromPlaylistExternalImage source function that resolves playlist
cover art from ExternalImageURL, supporting both HTTP(S) URLs (via
the existing fromURL helper) and local file paths (via os.Open).
Insert it in the Reader() fallback chain between sidecar and tiled cover.
* refactor(artwork): simplify playlist artwork source functions
Extract shared fromLocalFile helper, use url.Parse for scheme check,
and collapse sidecar directory scan conditions.
* test(artwork): remove redundant fromPlaylistSidecar tests
These tests duplicated scenarios already covered by findPlaylistSidecarPath
tests combined with fromLocalFile (tested via fromPlaylistExternalImage).
After refactoring fromPlaylistSidecar to a one-liner composing those two
functions, the wrapper tests add no value.
* fix(playlist): address security review comments from PR #5131:
- Use url.PathUnescape instead of url.QueryUnescape for file:// URLs so
that '+' in filenames is preserved (not decoded as space).
- Validate all local image paths (file://, absolute, relative) against
known library boundaries via libraryMatcher, rejecting paths outside
any configured library.
- Harden #EXTALBUMARTURL against path traversal and SSRF by adding EnableM3UExternalAlbumArt config flag (default false, also
disabled by EnableExternalServices=false) to gate HTTP(S) URL storage
at parse time and fetching at read time (defense in depth).
- Log a warning when os.ReadDir fails in findPlaylistSidecarPath for
diagnosability.
- Extract resolveLocalPath helper to simplify resolveImageURL.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(playlist): implement human-friendly filename generation for uploaded playlist cover images
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(playlist): add custom playlist cover art upload - #406
Allow users to upload, view, and remove custom cover images for playlists.
Custom images take priority over the auto-generated tiled artwork.
Backend:
- Add `image_path` column to playlist table (migration with proper rollback)
- Add `SetImage`/`RemoveImage` methods to playlist service
- Add `POST/DELETE /api/playlist/{id}/image` endpoints
- Prioritize custom image in artwork reader pipeline
- Clean up image files on playlist deletion
- Use glob-based cleanup to prevent orphaned files across format changes
- Reject uploads with undetermined image type (400)
Frontend:
- Hover overlay on playlist cover with upload (camera) and remove (trash) buttons
- Lightbox for full-size cover art viewing
- Cover art thumbnails in the playlist list view
- Loading/error states and i18n strings
Closes#406
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: adrbn <128328324+adrbn@users.noreply.github.com>
* refactor: rename playlist image path migration file
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(playlist): address review feedback for cover art upload - #406
- Use httpClient instead of raw fetch for image upload/remove
- Revert glob cleanup to simple imagePath check
- Add log.Error before all error HTTP responses
- Add backend tests for SetImage and RemoveImage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: adrbn <128328324+adrbn@users.noreply.github.com>
* refactor(playlist): use Playlist.ArtworkPath() for image storage
Migrate all playlist image path handling to use the new
Playlist.ArtworkPath() method as the single source of truth. The DB now
stores only the filename (e.g. "pls-1.jpg") instead of a relative path,
and images are stored under {DataFolder}/artwork/playlist/ instead of
{DataFolder}/playlist_images/. The artwork root directory is created at
startup alongside DataFolder and CacheFolder. This also removes the
conf dependency from reader_playlist.go since path resolution is now
fully encapsulated in the model.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(playlist): streamline artwork image selection logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: move translation keys, add pt-BR translations
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(playlist): rename image_path to image_file
Rename the playlist cover art column and field from image_path/ImagePath
to image_file/ImageFile across the migration, model, service, tests, and
UI. The new name more accurately describes what the field stores (a
filename, not a path) and aligns with the existing ImageFiles/IsImageFile
naming conventions in the codebase.
---------
Signed-off-by: adrbn <128328324+adrbn@users.noreply.github.com>
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
* refactor: move playlist business logic from repositories to core.Playlists service
Move authorization, permission checks, and orchestration logic from
playlist repositories to the core.Playlists service, following the
existing pattern used by core.Share and core.Library.
Changes:
- Expand core.Playlists interface with read, mutation, track management,
and REST adapter methods
- Add playlistRepositoryWrapper for REST Save/Update/Delete with
permission checks (follows Share/Library pattern)
- Simplify persistence/playlist_repository.go: remove isWritable(),
auth checks from Delete()/Put()/updatePlaylist()
- Simplify persistence/playlist_track_repository.go: remove
isTracksEditable() and permission checks from Add/Delete/Reorder
- Update Subsonic API handlers to route through service
- Update Native API handlers to accept core.Playlists instead of
model.DataStore
* test: add coverage for playlist service methods and REST wrapper
Add 30 new tests covering the service methods added during the playlist
refactoring:
- Delete: owner, admin, denied, not found
- Create: new playlist, replace tracks, admin bypass, denied, not found
- AddTracks: owner, admin, denied, smart playlist, not found
- RemoveTracks: owner, smart playlist denied, non-owner denied
- ReorderTrack: owner, smart playlist denied
- NewRepository wrapper: Save (owner assignment, ID clearing),
Update (owner, admin, denied, ownership change, not found),
Delete (delegation with permission checks)
Expand mockedPlaylistRepo with Get, Delete, Tracks, GetWithTracks, and
rest.Persistable methods. Add mockedPlaylistTrackRepo for track
operation verification.
* fix: add authorization check to playlist Update method
Added ownership verification to the Subsonic Update endpoint in the
playlist service layer. The authorization check was present in the old
repository code but was not carried over during the refactoring to the
service layer, allowing any authenticated user to modify playlists they
don't own via the Subsonic API. Also added corresponding tests for the
Update method's permission logic.
* refactor: improve playlist permission checks and error handling, add e2e tests
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename core.Playlists to playlists package and update references
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename playlists_internal_test.go to parse_m3u_test.go and update tests; add new parse_nsp.go and rest_adapter.go files
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: block track mutations on smart playlists in Create and Update
Create now rejects replacing tracks on smart playlists (pre-existing
gap). Update now uses checkTracksEditable instead of checkWritable
when track changes are requested, restoring the protection that was
removed from the repository layer during the refactoring. Metadata-only
updates on smart playlists remain allowed.
* test: add smart playlist protection tests to ensure readonly behavior and mutation restrictions
* refactor: optimize track removal and renumbering in playlists
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement track reordering in playlists with SQL updates
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: wrap track deletion and reordering in transactions for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove unused getTracks method from playlistTrackRepository
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: optimize playlist track renumbering with CTE-based UPDATE
Replace the DELETE + re-INSERT renumbering strategy with a two-step
UPDATE approach using a materialized CTE and ROW_NUMBER() window
function. The previous approach (SELECT all IDs, DELETE all tracks,
re-INSERT in chunks of 200) required 13 SQL operations for a 2000-track
playlist. The new approach uses just 2 UPDATEs: first negating all IDs
to clear the positive space, then assigning sequential positions via
UPDATE...FROM with a CTE. This avoids the UNIQUE constraint violations
that affected the original correlated subquery while reducing per-delete
request time from ~110ms to ~12ms on a 2000-track playlist.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename New function to NewPlaylists for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: update mock playlist repository and tests for consistency
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>
Bump golangci-lint from v2.9.0 to v2.10.0, which includes a newer gosec
with additional taint-analysis rules (G117, G703, G704, G705) and a
stricter G101 check. Added inline //nolint:gosec comments to suppress
21 false positives across 19 files: struct fields flagged as secrets
(G117), w.Write calls flagged as XSS (G705), HTTP client calls flagged
as SSRF (G704), os.Stat/os.ReadFile/os.Remove flagged as path traversal
(G703), and a sort mapping flagged as hardcoded credentials (G101).
Signed-off-by: Deluan <deluan@navidrome.org>
* 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>
* refactor: integrate duration into matchScore instead of using pre-filter
Duration matching was handled as a binary pre-filter with fallback,
inconsistent with how title, specificity, and album are scored via the
matchScore system. Move duration into matchScore as a boolean field
ranked between title similarity and specificity level, making all
match criteria use the same hierarchical comparison.
https://claude.ai/code/session_01BWJ5aAzbQRvwjB7PvUcNYs
* refactor: remove findBestMatchInTracks function and integrate its logic into findBestMatch
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: use duration proximity score instead of boolean match
Replace the binary durationMatch bool with a continuous durationProximity
float64 (0.0-1.0) using 1/(1+diff). This removes the hard 3-second
tolerance cutoff, so closer durations are always preferred over farther
ones without an arbitrary cliff edge.
https://claude.ai/code/session_01BWJ5aAzbQRvwjB7PvUcNYs
* style: fix gofmt alignment in matchScore struct
https://claude.ai/code/session_01BWJ5aAzbQRvwjB7PvUcNYs
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add duration filtering for similar songs matching
Signed-off-by: Deluan <deluan@navidrome.org>
* test: refactor expectations for similar songs in provider matching tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add functions to retrieve similar songs by track, album, and artist
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): support uint32 in ndpgen
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update duration field to use seconds as float instead of milliseconds as uint32
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: add helper functions for Rust's skip_serializing_if with numeric types
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(provider): enhance track matching logic to fallback to title match when duration-filtered tracks fail
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename ArtistRadio to SimilarSongs for clarity and consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement GetSimilarSongsByTrack and related functionality for song similarity retrieval
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance GetSimilarSongsByTrack to include artist and album details and update tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance song matching by implementing title and artist filtering in loadTracksByTitleAndArtist
Signed-off-by: Deluan <deluan@navidrome.org>
* test: add unit tests for song matching functionality in provider
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: extract song matching functionality into its own file
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: clarify similarSongsFallback function description in provider.go
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: initialize result slice for songs with capacity based on response length
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify agent method calls for retrieving images and similar songs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify agent method calls for retrieving images and similar songs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove outdated comments in GetSimilarSongs methods
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: use composite key for song matches to handle duplicates by title and artist
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: consolidate expectations setup for similar songs tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add instant mix action to song context menu and update translations
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(provider): handle unknown entity types in GetSimilarSongs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: move playSimilar action to playbackActions and streamline song processing
Signed-off-by: Deluan <deluan@navidrome.org>
* format
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance instant mix functionality with loading notification and shuffle option
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement fuzzy matching for similar songs based on configurable threshold
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement track matching with multiple specificity levels
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: enhance track matching by implementing unified scoring with specificity levels
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance deezer top tracks result with album
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance track matching with fuzzy album similarity for improved scoring
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: document multi-phase song matching algorithm with detailed scoring and prioritization
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* chore(plugins): remove the old plugins system implementation
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement new plugin system with using Extism
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add capability detection for plugins based on exported functions
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add auto-reload functionality for plugins with file watcher support
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add auto-reload functionality for plugins with file watcher support
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): standardize variable names and remove superfluous wrapper functions
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): improve error handling and logging in plugin manager
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): implement plugin function call helper and refactor MetadataAgent methods
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): race condition in plugin manager
* tests(plugins): change BeforeEach to BeforeAll in MetadataAgent tests
Signed-off-by: Deluan <deluan@navidrome.org>
* tests(plugins): optimize tests
Signed-off-by: Deluan <deluan@navidrome.org>
* tests(plugins): more optimizations
Signed-off-by: Deluan <deluan@navidrome.org>
* test(plugins): ignore goroutine leaks from notify library in tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add Wikimedia plugin for Navidrome to fetch artist metadata
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): enhance plugin logging and set User-Agent header
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement scrobbler plugin with authorization and scrobbling capabilities
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): integrate logs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): clean up manifest struct and improve plugin loading logic
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add metadata agent and scrobbler schemas for bootstrapping plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(hostgen): add hostgen tool for generating Extism host function wrappers
- Implemented hostgen tool to generate wrappers from annotated Go interfaces.
- Added command-line flags for input/output directories and package name.
- Introduced parsing and code generation logic for host services.
- Created test data for various service interfaces and expected generated code.
- Added documentation for host services and annotations for code generation.
- Implemented SubsonicAPI service with corresponding generated code.
* feat(subsonicapi): update Call method to return JSON string response
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement SubsonicAPI host function integration with permissions
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(generator): error-only methods in response handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): generate client wrappers for host functions
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(generator): remove error handling for response.Error in client templates
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scheduler): add Scheduler service interface with host function wrappers for scheduling tasks
* feat(plugins): add WASI build constraints to client wrapper templates, to avoid lint errors
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scheduler): implement Scheduler service with one-time and recurring scheduling capabilities
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(manifest): remove unused ConfigPermission from permissions schema
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scheduler): add scheduler callback schema and implementation for plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scheduler): streamline scheduling logic and remove unused callback tracking
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scheduler): add Close method for resource cleanup on plugin unload
Signed-off-by: Deluan <deluan@navidrome.org>
* docs(scheduler): clarify SchedulerCallback requirement for scheduling functions
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: update wasm build rule to include all Go files in the directory
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: rewrite the wikimedia plugin using the XTP CLI
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scheduler): replace uuid with id.NewRandom for schedule ID generation
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: capabilities registration
Signed-off-by: Deluan <deluan@navidrome.org>
* test: add scheduler service isolation test for plugin instances
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: update plugin manager initialization and encapsulate logic
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add WebSocket service definitions for plugin communication
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement WebSocket service for plugin integration and connection management
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add Crypto Ticker example plugin for real-time cryptocurrency price updates via Coinbase WebSocket API
Also add the lifecycle capability
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: use context.Background() in invokeCallback for scheduled tasks
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename plugin.create() to plugin.instance()
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename pluginInstance to plugin for consistency across the codebase
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify schedule cloning in Close method and enhance plugin cleanup error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement Artwork service for generating artwork URLs in Navidrome plugins - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: moved public URL builders to avoid import cycles
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add Cache service for in-memory TTL-based caching in plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add Discord Rich Presence example plugin for Navidrome integration
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: host function wrappers to use structured request and response types
- Updated the host function signatures in `nd_host_artwork.go`, `nd_host_scheduler.go`, `nd_host_subsonicapi.go`, and `nd_host_websocket.go` to accept a single parameter for JSON requests.
- Introduced structured request and response types for various cache operations in `nd_host_cache.go`.
- Modified cache functions to marshal requests to JSON and unmarshal responses, improving error handling and code clarity.
- Removed redundant memory allocation for string parameters in favor of JSON marshaling.
- Enhanced error handling in WebSocket and cache operations to return structured error responses.
* refactor: error handling in various plugins to convert response.Error to Go errors
- Updated error handling in `nd_host_scheduler.go`, `nd_host_websocket.go`, `nd_host_artwork.go`, `nd_host_cache.go`, and `nd_host_subsonicapi.go` to convert string errors from responses into Go errors.
- Removed redundant error checks in test data plugins for cleaner code.
- Ensured consistent error handling across all plugins to improve reliability and maintainability.
* refactor: rename fake plugins to test plugins for clarity in integration tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add help target to Makefile for plugin usage instructions
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add Cover Art Archive plugin as an example of Python plugin
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update Makefile and README to clarify Go plugin usage
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: include plugin capabilities in loading log message
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add trace logging for plugin availability and error handling in agents
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add Now Playing Logger plugin to showcase calling host functions from Python plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: generate Python client wrappers for various host services
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add generated host function wrappers for Scheduler and SubsonicAPI services
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update Python plugin documentation and usage instructions for host function wrappers
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add Webhook Scrobbler plugin in Rust to send HTTP notifications on scrobble events
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enable parallel loading of plugins during startup
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: update README to include WebSocket callback schema in plugin documentation
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: extend plugin watcher with improved logging and debounce duration adjustment
Signed-off-by: Deluan <deluan@navidrome.org>
* add trace message for plugin recompiles
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement plugin cache purging functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* test: move purgeCacheBySize unit tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins UI): add plugin repository and database support
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins UI): add plugin management routes and middleware
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins UI): implement plugin synchronization with database for add, update, and remove actions
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins UI): add PluginList and PluginShow components with plugin management functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): optimize plugin change detection
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins UI): improve PluginList structure
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins UI): enhance PluginShow with author, website, and permissions display
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins UI): refactor to use MUI and RA components
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins UI): add error handling for plugin enable/disable actions
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): inject PluginManager into native API
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update GetManager to accept DataStore parameter
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add subsonicRouter to Manager and refactor host service registration
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): enhance debug logging for plugin actions and recompile logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): break manager.go into smaller, focused files
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): streamline error handling and improve plugin retrieval logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update newWebSocketService to use WebSocketPermission for allowed hosts
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): introduce ToggleEnabledSwitch for managing plugin enable/disable state
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: update READMEs
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(library): add Library service for metadata access and filesystem integration
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add Library Inspector plugin for periodic library inspection and file size logging
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: update README to reflect JSON configuration format for plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(build): update target to wasm32-wasip1 for improved WASI support
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement configuration management UI with key-value pairs support
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): adjust grid layout in InfoRow component for improved responsiveness
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): rename ErrorIndicator to EnabledOrErrorField and enhance error handling logic
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(i18n): add Portuguese translations for plugin management and notifications
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add support for .ndp plugin packages and update build process
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: update README for .ndp plugin packaging and installation instructions
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement KVStore service for persistent key-value storage
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: enhance README with Extism plugin development resources and recommendations
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): integrate event broker into plugin manager
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): update config handling in PluginShow to track last record state
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add Rust host function library and example implementation of Discord Rich Presence plugin in Rust
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): generate Rust lib.rs file to expose host function wrappers
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update JSON field names to camelCase for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: reduce cyclomatic complexity by refactoring main function
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): enhance Rust code generation with typed struct support and improved type handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add Go client library with host function wrappers and documentation
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): generate Go client stubs for non-WASM platforms
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): update client template file names for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add initial implementation of the Navidrome Plugin Development Kit code generator - Pahse 1
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 2
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 2 (2)
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 3
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 4
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 5
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): consistent naming/types across PDK
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): streamline plugin function signatures and error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update scrobbler interface to return errors directly instead of response structs
Signed-off-by: Deluan <deluan@navidrome.org>
* test: make all test plugins use the PDK
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): reorganize and sort type definitions for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update error handling for methods to return errors directly
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update function signatures to return values directly instead of response structs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update request/response types to use private naming conventions
Signed-off-by: Deluan <deluan@navidrome.org>
* build: mark .wasm files as intermediate for cleanup after building .ndp
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): consolidate PDK module path and update Go version to 1.25
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement Rust PDK
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): reorganize Rust output structure to follow standard conventions
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update Discord Rich Presence and Library Inspector plugins to use nd-pdk for service calls and implement lifecycle management
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update macro names for websocket and metadata registration to improve clarity and consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): rename scheduler callback methods for consistency and clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update export wrappers to use `//go:wasmexport` for WebAssembly compatibility
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: update plugin registration docs
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): generate host wrappers
Signed-off-by: Deluan <deluan@navidrome.org>
* test(plugins): conditionally run goleak checks based on CI environment
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: update README to reflect changes in plugin import paths
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update plugin instance creation to accept context for cancellation support
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update return types in metadata interfaces to use pointers
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): enhance type handling for Rust and XTP output in capability generation
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update IsAuthorized method to return boolean instead of response object
Signed-off-by: Deluan <deluan@navidrome.org>
* test(plugins): add unit tests for rustOutputType and isPrimitiveRustType functions
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement XTP JSONSchema validation for generated schemas
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update response types in testMetadataAgent methods to use pointers
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: update Go and Rust plugin developer sections for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: correct example link for library inspector in README
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: clarify artwork URL generation capabilities in service descriptions
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: update README to include Rust PDK crate information for plugin developers
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: handle URL parsing errors and use atomic upsert in plugin repository
Added proper error handling for url.Parse calls in PublicURL and AbsoluteURL
functions. When parsing fails, PublicURL now falls back to AbsoluteURL, and
AbsoluteURL logs the error and returns an empty string, preventing malformed
URLs from being generated.
Replaced the non-atomic UPDATE-then-INSERT pattern in plugin repository Put
method with a single atomic INSERT ... ON CONFLICT statement. This eliminates
potential race conditions and improves consistency with the upsert pattern
already used in host_kvstore.go.
* feat: implement mock service instances for non-WASM builds using testify/mock
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: Discord RPC struct to encapsulate WebSocket logic
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add support for experimental WebAssembly threads
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add PDK abstraction layer with mock support for non-WASM builds
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add unit tests for Discord plugin and RPC functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: update return types in minimalPlugin and wikimediaPlugin methods to use pointers
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: context cancellation and implement WebSocket callback timeout for improved error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: conditionally include error handling in generated client code templates
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement ConfigService for plugin configuration management
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance plugin manager to support metrics recording
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: make MockPDK private
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: update interface types to use 'any' in plugin repository methods
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename List method to Keys for clarity in configuration management
Signed-off-by: Deluan <deluan@navidrome.org>
* test: add ndpgen plugin tests in the pipeline and update Makefile
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add users permission management to plugin system
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: streamline users integration tests and enhance plugin user management
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove UserID from scrobbler request structure
Signed-off-by: Deluan <deluan@navidrome.org>
* test: add integration tests for UsersService enable gate behavior
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement user permissions for SubsonicAPI and scrobbler plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: show proper error in the UI when enabling a plugin fails
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add library permission management to plugin system
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add user permission for processing scrobbles in Discord Rich Presence plugin
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: implement dynamic loading for buffered scrobbler plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add GetAdmins method to retrieve admin users from the plugin
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update Portuguese translations for user and library permissions
Signed-off-by: Deluan <deluan@navidrome.org>
* reorder migrations
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: remove unnecessary bulkActionButtons prop from PluginList component
* feat: add manual plugin rescan functionality and corresponding UI action
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement user/library and plugin management integration with cleanup on deletion
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: replace core mock services with test-specific implementations to avoid import cycles
* feat: add ID fields to Artist and Song structs and enhance track loading logic by prioritizing ID matches
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update plugin permissions from allowedHosts to requiredHosts for better clarity and consistency
* feat: refactor plugin host permissions to use RequiredHosts directly for improved clarity
* fix: don't record metrics for plugin calls that aren't implemented at all
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: enhance connection management with improved error handling and cleanup logic
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: introduce ArtistRef struct for better artist representation and update track metadata handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update user configuration handling to use user key prefix for improved clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance ConfigCard input fields with multiline support and vertical resizing
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: rust plugin compilation error
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement IsOptionPattern method for better return type handling in Rust PDK generation
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Add ID field to Artist and Song structs in the agents package. When resolving
similar artists and top songs, the provider now uses a three-phase lookup:
1. Direct ID match (if agent returns internal Navidrome IDs)
2. MBID exact match (if MusicBrainz ID is available)
3. Fuzzy name/title match (existing behavior)
This enables agents to return more precise matches when they have access to
internal database IDs, while maintaining backward compatibility with
name-based matching.
Fixed issue #4787 where plugin scrobblers received an empty username during NowPlaying events. The async worker was passing context.Background() which lost all user information.
Changed nowPlayingEntry to store the full context (with cancellation removed via context.WithoutCancel) and pass it to dispatchNowPlaying. This ensures plugin scrobblers can extract username from the context for authorization checks.
Updated tests to verify username is properly propagated through the async workflow, matching the actual plugin adapter behavior of checking both request.UsernameFrom and request.UserFrom.
* 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(server): add option Lastfm.ScrobbleFirstAlbumArtistOnly to send only the first album artist
* fix: remove config parameter scrobbleFirstAlbumArtist
* test: add NowPlaying test for ScrobbleFirstArtistOnly
Add a test case for the NowPlaying function when ScrobbleFirstArtistOnly is enabled. This ensures that only the first artist from the Participants list is sent to Last.fm for both artist and album artist fields, matching the existing test coverage for the Scrobble function.
* refactor: consolidate getArtistForScrobble and getAlbumArtistForScrobble
Merge the separate getArtistForScrobble and getAlbumArtistForScrobble functions into a single parameterized function. This eliminates code duplication and makes the scrobble artist handling logic more maintainable. The function now accepts a role parameter and display name, allowing it to handle both artist and album artist extraction based on the ScrobbleFirstArtistOnly configuration.
---------
Co-authored-by: Deluan <deluan@navidrome.org>
* feat: make Unicode handling in external API calls configurable
- Add DevPreserveUnicodeInExternalCalls config option (default: false)
- Refactor external provider to use NameForExternal() method on auxArtist
- Remove redundant Name field from auxArtist struct
- Update all external API calls (image, URL, biography, similar, top songs, MBID) to use configurable Unicode handling
- Add comprehensive tests for both Unicode-preserving and normalized behaviors
- Refactor tests to use constants and improved structure with BeforeEach blocks
Fixes issue where Spotify integration failed to find artist images for artists with Unicode characters (e.g., en dash) in their names.
Signed-off-by: Deluan <deluan@navidrome.org>
* address comments
Signed-off-by: Deluan <deluan@navidrome.org>
* avoid calling str.Clean multiple times
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: apply Unicode handling pattern to auxAlbum
Extended the configurable Unicode handling to album names, matching the
pattern already implemented for artist names. This ensures consistent behavior
when DevPreserveUnicodeInExternalCalls is enabled for both artist and album
external API calls.
Changes:
- Removed Name field from auxAlbum struct, added Name() method with Unicode logic
- Updated getAlbum, UpdateAlbumInfo, populateAlbumInfo, and AlbumImage functions
- Added comprehensive tests for album Unicode handling (preserve and normalize)
- Fixed typo in artist image test description
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* Rename external auth options
ReverseProxyWhitelist was regularly confusing users that enabled it for
non-authenticating reverse proxy setups.
The new option name makes it clear that it's related to authentication, not
just reverse proxies.
* small refactor
Signed-off-by: Deluan <deluan@navidrome.org>
* add test
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
* feat: make NowPlaying dispatch asynchronous with worker pool
Implemented asynchronous NowPlaying dispatch using a queue worker pattern similar to cacheWarmer. Instead of dispatching NowPlaying updates synchronously during the HTTP request, they are now queued and processed by background workers at controlled intervals.
Key changes:
- Added nowPlayingEntry struct to represent queued entries
- Added npQueue map (keyed by playerId), npMu mutex, and npSignal channel to playTracker
- Implemented enqueueNowPlaying() to add entries to the queue
- Implemented nowPlayingWorker() that polls every 100ms, drains queue, and processes entries
- Changed NowPlaying() to queue dispatch instead of calling synchronously
- Renamed dispatchNowPlaying() to dispatchNowPlayingAsync() and updated it to use background context
Benefits:
- HTTP handlers return immediately without waiting for scrobbler responses
- Deduplication by key: rapid calls (seeking) only dispatch latest state
- Fire-and-forget: one-shot attempts with logged failures
- Backpressure-free: worker processes at its own pace
- Tests updated to use Eventually() assertions for async dispatch
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(play_tracker): increase timeout duration for signal handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(play_tracker): simplify queue processing by directly assigning entries
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add configurable transcoding cancellation
Implemented EnableTranscodingCancellation configuration option to control whether
FFmpeg transcoding processes can be interrupted when client requests are cancelled.
This addresses resource management issues on low-power hardware where transcoding
processes would accumulate and cause CPU spikes.
Key changes:
- Added EnableTranscodingCancellation bool to configuration (default: false)
- Added CLI flag --enabletranscodingcancellation and TOML/env support
- Modified FFmpeg package to always use exec.CommandContext for consistency
- Implemented conditional context handling in NewTranscodingCache function
- When enabled: uses request context directly (allows cancellation)
- When disabled: uses background context with request metadata preserved
- Added comprehensive tests for both FFmpeg and transcoding layers
- Maintained backward compatibility with existing behavior as default
The implementation follows proper layered architecture with policy decisions
at the media streaming layer and execution utilities remaining focused on
their core responsibilities.
Signed-off-by: Deluan <deluan@navidrome.org>
* test: refactor FFmpeg context cancellation tests for improved clarity and reliability
Signed-off-by: Deluan <deluan@navidrome.org>
* test: reset FFmpeg initialization
Signed-off-by: Deluan <deluan@navidrome.org>
* test: improve FFmpeg context cancellation tests for cross-platform compatibility
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Removed the buffer.Length() check that was causing intermittent test failures.
The background goroutine started by newBufferedScrobbler can process and
dequeue scrobble entries before the test assertion runs, leading to a race
condition where the observed length is 0 instead of 1. The Eventually block
that follows already verifies the scrobble was processed correctly.
Signed-off-by: Deluan <deluan@navidrome.org>
Previously, the insights collector would only try to get an admin user once
at startup. If no admin user existed (e.g., fresh database before first user
registration), insights collection would silently fail forever.
This change moves the admin context creation inside the collection loop so it
retries on each interval. It also updates log messages in WithAdminUser to
remove the Scanner prefix since this function is now used by other components.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(deezer): add functions to fetch related artists, biographies, and top tracks for an artist
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(deezer): add language support for Deezer API client
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(deezer): Use GraphQL API for translated biographies
The previous implementation scraped the __DZR_APP_STATE__ from HTML,
which only contained English content. The actual biography displayed
on Deezer's website comes from their GraphQL API at pipe.deezer.com,
which properly respects the Accept-Language header and returns
translated content.
This change:
- Switches from HTML scraping to the GraphQL API
- Uses Accept-Language header instead of URL path for language
- Updates tests to match the new implementation
- Removes unused HTML fixture file
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(deezer): move JWT token handling to a separate file for better organization
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(deezer): enhance JWT token handling with expiration validation
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(deezer): change log level for unknown agent warnings from Warn to Debug
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(deezer): reduce JWT token expiration buffer from 10 minutes to 1 minute
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
In `getAffectedAlbumIDs`, when one or more IDs is added, it adds a filter `"id": ids`.
This filter is ambiguous though, because the `getAll` query joins with library table, which _also_ has an `id` field.
Clarify this by adding the table name to the filter.
Note that this was not caught in testing, as it only uses mock db.
* feat: Add selective folder scanning capability
Implement targeted scanning of specific library/folder pairs without
full recursion. This enables efficient rescanning of individual folders
when changes are detected, significantly reducing scan time for large
libraries.
Key changes:
- Add ScanTarget struct and ScanFolders API to Scanner interface
- Implement CLI flag --targets for specifying libraryID:folderPath pairs
- Add FolderRepository.GetByPaths() for batch folder info retrieval
- Create loadSpecificFolders() for non-recursive directory loading
- Scope GC operations to affected libraries only (with TODO for full impl)
- Add comprehensive tests for selective scanning behavior
The selective scan:
- Only processes specified folders (no subdirectory recursion)
- Maintains library isolation
- Runs full maintenance pipeline scoped to affected libraries
- Supports both full and quick scan modes
Examples:
navidrome scan --targets "1:Music/Rock,1:Music/Jazz"
navidrome scan --full --targets "2:Classical"
* feat(folder): replace GetByPaths with GetFolderUpdateInfo for improved folder updates retrieval
Signed-off-by: Deluan <deluan@navidrome.org>
* test: update parseTargets test to handle folder names with spaces
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(folder): remove unused LibraryPath struct and update GC logging message
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(folder): enhance external scanner to support target-specific scanning
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): simplify scanner methods
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(watcher): implement folder scanning notifications with deduplication
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(watcher): add resolveFolderPath function for testability
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(watcher): implement path ignoring based on .ndignore patterns
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): implement IgnoreChecker for managing .ndignore patterns
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(ignore_checker): rename scanner to lineScanner for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): enhance ScanTarget struct with String method for better target representation
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner): validate library ID to prevent negative values
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): simplify GC method by removing library ID parameter
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scanner): update folder scanning to include all descendants of specified folders
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(subsonic): allow selective scan in the /startScan endpoint
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): update CallScan to handle specific library/folder pairs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): streamline scanning logic by removing scanAll method
Signed-off-by: Deluan <deluan@navidrome.org>
* test: enhance mockScanner for thread safety and improve test reliability
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): move scanner.ScanTarget to model.ScanTarget
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: move scanner types to model,implement MockScanner
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): update scanner interface and implementations to use model.Scanner
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(folder_repository): normalize target path handling by using filepath.Clean
Signed-off-by: Deluan <deluan@navidrome.org>
* test(folder_repository): add comprehensive tests for folder retrieval and child exclusion
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): simplify selective scan logic using slice.Filter
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): streamline phase folder and album creation by removing unnecessary library parameter
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): move initialization logic from phase_1 to the scanner itself
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(tests): rename selective scan test file to scanner_selective_test.go
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(configuration): add DevSelectiveWatcher configuration option
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(watcher): enhance .ndignore handling for folder deletions and file changes
Signed-off-by: Deluan <deluan@navidrome.org>
* docs(scanner): comments
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): enhance walkDirTree to support target folder scanning
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner, watcher): handle errors when pushing ignore patterns for folders
Signed-off-by: Deluan <deluan@navidrome.org>
* Update scanner/phase_1_folders.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* refactor(scanner): replace parseTargets function with direct call to scanner.ParseTargets
Signed-off-by: Deluan <deluan@navidrome.org>
* test(scanner): add tests for ScanBegin and ScanEnd functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(library): update PRAGMA optimize to check table sizes without ANALYZE
Signed-off-by: Deluan <deluan@navidrome.org>
* test(scanner): refactor tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): add selective scan options and update translations
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): add quick and full scan options for individual libraries
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): add Scan buttonsto the LibraryList
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scan): update scanning parameters from 'path' to 'target' for selective scans.
* refactor(scan): move ParseTargets function to model package
* test(scan): suppress unused return value from SetUserLibraries in tests
* feat(gc): enhance garbage collection to support selective library purging
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner): prevent race condition when scanning deleted folders
When the watcher detects changes in a folder that gets deleted before
the scanner runs (due to the 10-second delay), the scanner was
prematurely removing these folders from the tracking map, preventing
them from being marked as missing.
The issue occurred because `newFolderEntry` was calling `popLastUpdate`
before verifying the folder actually exists on the filesystem.
Changes:
- Move fs.Stat check before newFolderEntry creation in loadDir to
ensure deleted folders remain in lastUpdates for finalize() to handle
- Add early existence check in walkDirTree to skip non-existent target
folders with a warning log
- Add unit test verifying non-existent folders aren't removed from
lastUpdates prematurely
- Add integration test for deleted folder scenario with ScanFolders
Fixes the issue where deleting entire folders (e.g., /music/AC_DC)
wouldn't mark tracks as missing when using selective folder scanning.
* refactor(scan): streamline folder entry creation and update handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scan): add '@Recycle' (QNAP) to ignored directories list
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(log): improve thread safety in logging level management
* test(scan): move unit tests for ParseTargets function
Signed-off-by: Deluan <deluan@navidrome.org>
* review
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: deluan <deluan.quintao@mechanical-orchard.com>
* fix(reader): prioritize cover art selection by base filename without numeric suffixes
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(reader): update image file comparison to use natural sorting and prioritize files without numeric suffixes
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(reader): simplify comparison, add case-sensitivity test case
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* Adding environmental variable so that navidrome can detect
if its running as an MSI install for insights
* Renaming to be ND_PACKAGE_TYPE so we can reuse this for the
.deb/.rpm stats as well
* Packaged implies a bool, this is a description so it should
be packaging or just package imo
* wixl currently doesn't support <Environment> so I'm swapping out
to a file next-door to the configuration file, we should be
able to reuse this for deb/rpm as well
* Using a file we should be able to add support for linux like this
also
* MSI should copy the package into place for us, it's not a KeyPath
as older versions won't have it, so it's presence doesn't indicate
the installed status of the package
* OK this doesn't exist, need to find another way to do it
* package to .package and moving to the datadir
* fix(scanner): better log message when AutoImportPlaylists is disabled
Fix#3861
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner): support ID3v2 embedded images in WAV files
Fix#3867
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): show bitDepth in song info dialog
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(server): don't break if the ND_CONFIGFILE does not exist
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(docker): automatically loads a navidrome.toml file from /data, if available
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(server): custom ArtistJoiner config (#3873)
* feat(server): custom ArtistJoiner config
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(ui): organize ArtistLinkField, add tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): use display artist
* feat(ui): use display artist
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* chore: remove some BFR-related TODOs that are not valid anymore
Signed-off-by: Deluan <deluan@navidrome.org>
* chore: remove more outdated TODOs
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner): elapsed time for folder processing is wrong in the logs
Signed-off-by: Deluan <deluan@navidrome.org>
* Should be able to reuse this mechanism with deb and rpm, I think
it would be nice to know which specific one it is without guessing
based on /etc/debian_version or something; but it doesn't look like
that is exposed by goreleaser into an env or anything :/
* Need to reference the installed file and I think Id's don't require []
* Need to add into the root directory for this to work
* That was not deliberately removed
* feat: add RPM and DEB package configuration files for Navidrome
Signed-off-by: Deluan <deluan@navidrome.org>
* Don't need this as goreleaser will sort it out
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
Fixed race condition in the 'deduplicates items in buffer' test where the
background worker goroutine could process and clear the buffer before the
test could verify its contents. Added fc.SetReady(false) to keep the cache
unavailable during the test, ensuring buffered items remain in memory for
verification. This matches the pattern already used in the 'adds multiple
items to buffer' test.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add album refresh functionality after deleting missing files
Implemented RefreshAlbums method in AlbumRepository to recalculate album attributes (size, duration, song count) from their constituent media files. This method processes albums in batches to maintain efficiency with large datasets.
Added integration in deleteMissingFiles to automatically refresh affected albums in the background after deleting missing media files, ensuring album statistics remain accurate. Includes comprehensive test coverage for various scenarios including single/multiple albums, empty batches, and large batch processing.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: extract missing files deletion into reusable service layer
Extracted inline deletion logic from server/nativeapi/missing.go into a new core.MissingFiles service interface and implementation. This provides better separation of concerns and testability.
The MissingFiles service handles:
- Deletion of specific or all missing files via transaction
- Garbage collection after deletion
- Extraction of affected album IDs from missing files
- Background refresh of artist and album statistics
The deleteMissingFiles HTTP handler now simply delegates to the service, removing 70+ lines of inline logic. All deletion, transaction, and stat refresh logic is now centralized in core/missing_files.go.
Updated dependency injection to provide MissingFiles service to the native API router. Renamed receiver variable from 'n' to 'api' throughout native_api.go for consistency.
* refactor: consolidate maintenance operations into unified service
Consolidate MissingFiles and RefreshAlbums functionality into a new Maintenance service. This refactoring:
- Creates core.Maintenance interface combining DeleteMissingFiles, DeleteAllMissingFiles, and RefreshAlbums methods
- Moves RefreshAlbums logic from AlbumRepository persistence layer to core Maintenance service
- Removes MissingFiles interface and moves its implementation to maintenanceService
- Updates all references in wire providers, native API router, and handlers
- Removes RefreshAlbums interface method from AlbumRepository model
- Improves separation of concerns by centralizing maintenance operations in the core domain
This change provides a cleaner API and better organization of maintenance-related database operations.
* refactor: remove MissingFiles interface and update references
Remove obsolete MissingFiles interface and its references:
- Delete core/missing_files.go and core/missing_files_test.go
- Remove RefreshAlbums method from AlbumRepository interface and implementation
- Remove RefreshAlbums tests from AlbumRepository test suite
- Update wire providers to use NewMaintenance instead of NewMissingFiles
- Update native API router to use Maintenance service
- Update missing.go handler to use Maintenance interface
All functionality is now consolidated in the core.Maintenance service.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename RefreshAlbums to refreshAlbums and update related calls
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: optimize album refresh logic and improve test coverage
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify logging setup in tests with reusable LogHook function
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: add synchronization to logger and maintenance service for thread safety
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: handle UTF-8 BOM in lyrics and playlist files
Added UTF-8 BOM (Byte Order Mark) detection and stripping for external lyrics files and playlist files. This ensures that files with BOM markers are correctly parsed and recognized as synced lyrics or valid playlists.
The fix introduces a new ioutils package with UTF8Reader and UTF8ReadFile functions that automatically detect and remove UTF-8, UTF-16 LE, and UTF-16 BE BOMs. These utilities are now used when reading external lyrics and playlist files to ensure consistent parsing regardless of BOM presence.
Added comprehensive tests for BOM handling in both lyrics and playlists, including test fixtures with actual BOM markers to verify correct behavior.
* test: add test for UTF-16 LE encoded LRC files
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add PluginList method
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance insights collection with plugin awareness and expanded metrics
Enhanced the insights collection system to provide more comprehensive telemetry data about Navidrome installations. This update adds plugin awareness through dependency injection integration, expands configuration detection capabilities, and includes additional library metrics.
Key improvements include:
- Added PluginLoader interface integration to collect plugin information when enabled
- Enhanced configuration detection with proper credential validation for LastFM, Spotify, and Deezer
- Added new library metrics including Libraries count and smart playlist detection
- Expanded configuration insights with reverse proxy, custom PID, and custom tags detection
- Updated Wire dependency injection to support the new plugin loader requirement
- Added corresponding data structures for plugin information collection
This enhancement provides valuable insights into feature usage patterns and plugin adoption while maintaining privacy and following existing telemetry practices.
* fix: correct type assertion in plugin manager test
Fixed type mismatch in test where PluginManifestCapabilitiesElem was being
compared with string literal. The test now properly casts the string to the
correct enum type for comparison.
* refactor: move static config checks to staticData function
Moved HasCustomTags, ReverseProxyConfigured, and HasCustomPID configuration checks from the dynamic collect() function to the static staticData() function where they belong. This eliminates redundant computation on every insights collection cycle and implements the actual logic for HasCustomTags instead of the hardcoded false value.
The HasCustomTags field now properly detects if custom tags are configured by checking the length of conf.Server.Tags. This change improves performance by computing static configuration values only once rather than on every insights collection.
* feat: add granular control for insights collection
Added DevEnablePluginsInsights configuration option to allow fine-grained control over whether plugin information is collected as part of the insights data. This change enhances privacy controls by allowing users to opt-out of plugin reporting while still participating in general insights collection.
The implementation includes:
- New configuration option DevEnablePluginsInsights with default value true
- Gated plugin collection in insights.go based on both plugin enablement and permission flag
- Enhanced plugin information to include version data alongside name
- Improved code organization with clearer conditional logic for data collection
* refactor: rename PluginNames parameter from serviceName to capability
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: resolve playlist import issues with Unicode character paths
Fixes#3332 where songs with accented characters failed to import from Apple Music M3U playlists. The issue occurred because Apple Music exports use NFC Unicode normalization while macOS filesystem stores paths in NFD normalization.
Added normalizePathForComparison() function that normalizes both filesystem and M3U playlist paths to NFC form before comparison. This ensures consistent path matching regardless of the Unicode normalization form used.
Changes include comprehensive test coverage for Unicode normalization scenarios with both NFC and NFD character representations.
* address comments
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(tests): add check for unequal original Unicode paths in playlist normalization tests
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: improve URL path handling in local storage system
Enhanced the local storage implementation to properly handle URL-decoded paths
and fix issues with file paths containing special characters. Added decodedPath
field to localStorage struct to separate URL parsing concerns from file system
operations.
Key changes:
- Added decodedPath field to localStorage struct for proper URL decoding
- Modified newLocalStorage to use decoded path instead of modifying original URL
- Fixed Windows path handling to work with decoded paths
- Improved URL escaping in storage.For() to handle special characters
- Added comprehensive test suite covering URL decoding, symlink resolution,
Windows paths, and edge cases
- Refactored test extractor to use mockTestExtractor for better isolation
This ensures that file paths with spaces, hash symbols, and other special
characters are handled correctly throughout the storage system.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(tests): fix test file permissions and add missing tests.Init call
* refactor(tests): remove redundant test
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: URL building for Windows and remove redundant variable
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify URL path escaping in local storage
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: enable library access for headless processes
Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present.
Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations.
Key changes:
- Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId
- Refactored tag repository to use helper method for library filtering logic
- Fixed SQL aggregation bug in tag statistics calculation across multiple libraries
- Added comprehensive test coverage for headless process scenarios
- Updated genre repository to support proper column mappings for aggregated data
This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: resolve SQL ambiguity errors in share queries
Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR.
The Multi-library changes introduced JOINs between album and library tables,
both of which have 'id' columns, causing 'ambiguous column name: id' errors
when unqualified column references were used in WHERE clauses.
Changes made:
- Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums
- Updated persistence/share_repository.go to use 'album.id' in album share loading
- Updated persistence/sql_participations.go to use 'artist.id' for consistency
- Added regression tests to prevent future SQL ambiguity issues
This resolves HTTP 500 errors that users experienced when accessing existing
share URLs after the Multi-library feature was merged.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: improve headless library access handling
Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit)
Squashed commits:
[a28c6965b] fix: remove headless library access guard
Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering.
The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship.
* fix: simplify genre selection query in genre repository
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: enhance tag library filtering tests for headless access
Signed-off-by: Deluan <deluan@navidrome.org>
* test: add comprehensive test coverage for headless library access
Added extensive test coverage for headless library access improvements including:
- Added 17 new tests across 4 test files covering headless access scenarios
- artist_repository_test.go: Added headless process tests for GetAll, Count,
Get operations and explicit library_id filtering functionality
- genre_repository_test.go: Added library filtering tests for headless processes
including GetAll, Count, ReadAll, and Read operations
- sql_base_repository_test.go: Added applyLibraryFilter method tests covering
admin users, regular users, and headless processes with/without custom table names
- share_repository_test.go: Added headless access tests and SQL ambiguity
verification for the album.id vs id fix in loadMedia function
- Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context()
and removing unnecessary configtest.SetupConfig() calls for better test isolation
These tests ensure that headless processes (background operations without user context)
can access all libraries while respecting explicit filters, and verify that the SQL
ambiguity fixes work correctly without breaking existing functionality.
* revert: remove user context handling from scrobble buffer getParticipants
Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869.
The artist repository no longer requires user context for proper library
filtering, so the workaround of temporarily injecting user context into
the scrobbleBufferRepository.Next method is no longer needed.
This simplifies the code and removes the dependency on fetching user
information during background scrobbling operations.
* fix: improve library access filtering for artists
Enhanced artist repository filtering to properly handle library access restrictions
and prevent artists with no accessible content from appearing in results.
Backend changes:
- Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance
- Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content)
- Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering
- Added condition to exclude artists where library_artist.stats = '{}' (empty content)
Frontend changes:
- Added null-checking in getCounter function to prevent TypeError when accessing undefined records
- Improved optional chaining for safer property access in role-based statistics display
These changes ensure that users only see artists that have actual accessible content
in their permitted libraries, fixing issues where artists appeared in the list
despite having no albums or songs available to the user.
* fix: update library access logic for non-admin users and enhance test coverage
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: refine library artist query and implement cleanup for empty entries
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: consolidate artist repository tests to eliminate duplication
Significantly refactored artist_repository_test.go to reduce code duplication and
improve maintainability by ~27% (930 to 680 lines). Key improvements include:
- Added test helper functions createTestArtistWithMBID() and createUserWithLibraries()
to eliminate repetitive test data creation
- Consolidated duplicate MBID search tests using DescribeTable for parameterized testing
- Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that
duplicated functionality already covered in other test contexts
- Reorganized search tests into cohesive 'MBID and Text Search' section with proper
setup/teardown and shared test infrastructure
- Streamlined missing artist tests and moved them to dedicated section
- Maintained 100% test coverage while eliminating redundant test patterns
All tests continue to pass with identical functionality and coverage.
---------
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>
* refactor: enhance agent loading with structured data
Introduced a new struct, EnabledAgent, to encapsulate agent name and type
information (plugin or built-in). Updated the getEnabledAgentNames function
to return a slice of EnabledAgent instead of a slice of strings, allowing
for more detailed agent management. This change improves the clarity and
maintainability of the code by providing a structured approach to handling
enabled agents and their types.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove agent caching logic
Eliminated the caching mechanism for agents, including the associated
data structures and methods. This change simplifies the agent loading
process by directly retrieving agents without caching, which is no longer
necessary for the current implementation. The removal of this logic helps
reduce complexity and improve maintainability of the codebase.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: replace range with slice.Contains
Signed-off-by: Deluan <deluan@navidrome.org>
* test: simplify agent name extraction in tests
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement OnSchedulerCallback method in wasmSchedulerCallback
Added the OnSchedulerCallback method to the wasmSchedulerCallback struct, enabling it to handle scheduler callback events. This method constructs a SchedulerCallbackRequest and invokes the corresponding plugin method, facilitating better integration with the scheduling system. The changes improve the plugin's ability to respond to scheduled events, enhancing overall functionality.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update executeCallback method to use callMethod
Modified the executeCallback method to accept an additional parameter,
methodName, which specifies the callback method to be executed. This change
ensures that the correct method is called for each WebSocket event,
improving the accuracy of callback execution for plugins.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): capture OnInit metrics
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): improve logging for metrics in callMethod
Updated the logging statement in the callMethod function to include the
elapsed time as a separate key in the log output. This change enhances
the clarity of the logged metrics, making it easier to analyze the
performance of plugin requests and troubleshoot any issues that may arise.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): enhance logging for schedule callback execution
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(server): streamline scrobbler stopping logic
Refactored the logic for stopping scrobbler instances when they are removed.
The new implementation introduces a `stoppableScrobbler` interface to
simplify the type assertion process, allowing for a more concise and
readable code structure. This change ensures that any scrobbler
implementing the `Stop` method is properly stopped before removal,
improving the overall reliability of the plugin management system.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): improve plugin lifecycle management and error handling
Enhanced the plugin lifecycle management by implementing error handling in the OnInit method. The changes include the addition of specific error conditions that can be returned during plugin initialization, allowing for better management of plugin states. Additionally, the unregisterPlugin method was updated to ensure proper cleanup of plugins that fail to initialize, improving overall stability and reliability of the plugin system.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): remove unused LoadAllPlugins and related methods
Eliminated the LoadAllPlugins, LoadAllMediaAgents, and LoadAllScrobblers
methods from the manager implementation as they were not utilized in the codebase.
This cleanup reduces complexity and improves maintainability by removing
redundant code, allowing for a more streamlined plugin management process.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update logging configuration for plugins
Configured logging for multiple plugins to remove timestamps and source file/line information, while adding specific prefixes for better identification.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): clear initialization state when unregistering a plugin
Added functionality to clear the initialization state of a plugin in the
lifecycle manager when it is unregistered. This change ensures that the
lifecycle state is accurately maintained, preventing potential issues with
plugins that may be re-registered after being unregistered. The new method
`clearInitialized` was implemented to handle this state management.
Signed-off-by: Deluan <deluan@navidrome.org>
* test: add unit tests for convertError function, rename to checkErr
Added comprehensive unit tests for the convertError function to ensure
correct behavior across various scenarios, including handling nil responses,
typed nils, and responses implementing errorResponse. These tests validate
that the function returns the expected results without panicking and
correctly wraps original errors when necessary.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update plugin base implementation and method calls
Refactored the plugin base implementation by renaming `wasmBasePlugin` to `baseCapability` across multiple files. Updated method calls in the `wasmMediaAgent`, `wasmSchedulerCallback`, and `wasmScrobblerPlugin` to align with the new base structure. These changes improve code clarity and maintainability by standardizing the plugin architecture, ensuring consistent usage of the base capabilities across different plugin types.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(discord): handle failed connections and improve heartbeat checks
Added a new method to clean up failed connections, which cancels the heartbeat schedule, closes the WebSocket connection, and removes cache entries. Enhanced the heartbeat check to log failures and trigger the cleanup process on the first failure. These changes ensure better management of user connections and improve the overall reliability of the RPC system.
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Not all systems have bash at `/bin/bash`. `/bin/sh` is POSIX and should
be present on all systems making this much more portable. No bash
features are currently used in the script so this change should be safe.
* feat(agents): Add Deezer API artist image provider agent
* fix(agents): Use proper naming convention of consts
* fix(agents): Check if json test data can be read
* fix(agents): Use underscores for unused function arguments
* fix(agents): Move int literal to deezerArtistSearchLimit const
* feat: add Deezer configuration option to disable it.
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
* Add prometheus metrics to subsonic and plugins
* address feedback, do not log error if operation is not supported
* add missing timestamp and client to stats
* remove .view from subsonic route
* directly inject DataStore in Prometheus, to avoid having to pass it in every call
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>
* feat(plugins): add minimal test agent plugin with API definitions
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add plugin manager with auto-registration and unique agent names
Introduced a plugin manager that scans the plugins folder for subdirectories containing plugin.wasm files and auto-registers them as agents using the directory name as the unique agent name. Updated the configuration to support plugins with enabled/folder options, and ensured the plugin manager is started as a concurrent task during server startup. The wasmAgent now returns the plugin directory name for AgentName, ensuring each plugin agent is uniquely identifiable. This enables dynamic plugin discovery and integration with the agents orchestrator.
* test: add Ginkgo suite and test for plugin manager auto-registration
Added a Ginkgo v2 suite bootstrap (plugins_suite_test.go) for the plugins package and a test (manager_test.go) to verify that plugins in the testdata folder are auto-registered and can be loaded as agents. The test uses a mock DataStore and asserts that the agent is registered and its AgentName matches the plugin directory. Updated go.mod and go.sum for wazero dependency required by plugin WASM support.
* test(plugins): ensure test WASM plugin is always freshly built before running suite; add real-plugin Ginkgo tests. Add BeforeSuite to plugins suite to build plugins/testdata/agent/plugin.wasm using Go WASI build command, matching README instructions. Remove plugin.wasm before build to guarantee a clean build. Add full real-plugin Ginkgo/Gomega tests for wasmAgent, covering all methods and error cases. Fix manager_test.go to use pointer to Manager. This ensures plugin tests are always run against a freshly compiled WASM binary, increasing reliability and reproducibility.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement persistent compilation cache for WASM agent plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement instance pooling for wasmAgent to improve resource management
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): enhance logging for wasmAgent and plugin manager operations
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement HttpService for handling HTTP requests in WASM plugins
Also add a sample Wikimedia plugin
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): standardize error handling in wasmAgent and MinimalAgent
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: clean up wikimedia plugin code
Standardized error creation using 'errors.New' where formatting was not needed. Introduced a constant for HTTP request timeouts. Removed commented-out log statement. Improved code comments for clarity and accuracy.
* refactor: use unified SPARQLResult struct and parser for SPARQL responses
Introduced a single SPARQLResult struct to represent all possible SPARQL response fields (sitelink, wiki, comment, img). Added a parseSPARQLResult helper to unmarshal and check for empty results, simplifying all fetch functions and improving type safety and maintainability.
* feat(plugins): improve error handling in HTTP request processing
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: background plugin compilation, logging, and race safety
Implemented background WASM plugin compilation with concurrency limits, proper closure capture, and global compilation cache to avoid data races. Added debug and warning logs for plugin compilation results, including elapsed time. Ensured plugin registration is correct and all tests pass.
* perf: implement true lazy loading for agents
Changed agent instantiation to be fully lazy. The Agents struct now stores agent names in order and only instantiates each agent on first use, caching the result. This preserves agent call order, improves server startup time, and ensures thread safety. Updated all agent methods and tests to use the new pattern. No changes to agent registration or interface. All tests pass.
* fix: ensure wasm plugin instances are closed via runtime.AddCleanup
Introduced runtime.AddCleanup to guarantee that the Close method of WASM plugin instances is called, even if they are garbage collected from the sync.Pool. Modified the sync.Pool.New function in manager.go to register a cleanup function for each loaded instance that implements Close. Updated agent.go to handle the pooledInstance wrapper containing the instance and its cleanup handle. Ensured cleanup.Stop() is called before explicitly closing an instance (on error or agent shutdown) to prevent double closing. This fixes a potential resource leak where instances could be GC'd from the pool without proper cleanup.
* refactor: break down long functions in plugin manager and agent
Refactored plugins/manager.go and plugins/agent.go to improve readability and reduce function length. Extracted pool initialization logic into newPluginPool and background compilation/agent factory logic into precompilePlugin/createAgentFactory in manager.go. Extracted pool retrieval/validation and cleanup function creation into getValidPooledInstance/createPoolCleanupFunc in agent.go.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): rename wasmAgent to wasmArtistAgent
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(api): add AlbumMetadataService with AlbumInfo and AlbumImages requests
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugin): rename MinimalAgent for artist metadata service
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(api): implement wasmAlbumAgent for album metadata service with GetAlbumInfo and GetAlbumImages methods
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): simplify wasmAlbumAgent and wasmArtistAgent by using wasmBasePlugin
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add support for ArtistMetadataService and AlbumMetadataService in plugin manager
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): enhance plugin pool creation with custom runtime and precompilation support
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): implement generic plugin pool and agent factory for improved service handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): reorganize plugin management
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): improve function signatures for clarity and consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement background precompilation for plugins and agent factory creation
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): include instanceID in logging for better traceability
Signed-off-by: Deluan <deluan@navidrome.org>
* test(plugins): add tests for plugin pre-compilation and agent factory synchronization
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add minimal album test agent plugin for AlbumMetadataService
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): rename fake artist and album test agent plugins for metadata services
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(makefile): add Makefile for building plugin WASM binaries
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add FakeMultiAgent plugin implementing Artist and Album metadata services
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): remove log statements from FakeArtistAgent and FakeMultiAgent methods
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: split AlbumInfoRetriever and AlbumImageRetriever, update all usages
Split the AlbumInfoRetriever interface into two: AlbumInfoRetriever (for album metadata) and AlbumImageRetriever (for album images), to better separate concerns and simplify implementations. Updated all agents, providers, plugins, and tests to use the new interfaces and methods. Removed the now-unnecessary mockAlbumAgents in favor of the shared mockAgents. Fixed a missing images slice declaration in lastfm agent. All tests pass except for known ignored persistence tests. This change reduces code duplication, improves clarity, and keeps the codebase clean and organized.
* feat(plugins): add Cover Art Archive AlbumMetadataService plugin for album cover images
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove wasm module pooling
it was causing issues with the GC and the Close methods
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename metadata service files to adapter naming convention
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: unify album and artist method calls by introducing callMethod function
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: unify album and artist method calls by introducing callMethod function
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: handle nil values in data redaction process
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: add timeout for plugin compilation to prevent indefinite blocking
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement ScrobblerService plugin with authorization and scrobbling capabilities
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify generalization
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: tests
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: enhance plugin management by improving scanning and loading mechanisms
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: update plugin creation functions to return specific interfaces for better type safety
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: enhance wasmBasePlugin to support specific plugin types for improved type safety
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement MediaMetadataService with combined artist and album methods
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: improve MediaMetadataService plugin implementation and testing structure
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: add tests for Adapter Media Agent and improve plugin documentation
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: add README for Navidrome Plugin System with detailed architecture and usage guidelines
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: enhance agent management with plugin loading and caching
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: update agent discovery logic to include only local agent when no config is specified
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: encapsulate agent caching logic in agentCache struct\n\nReplaced direct map/mutex usage for agent caching in Agents with a dedicated agentCache struct. This improves readability, maintainability, and testability by centralizing TTL and concurrency logic. Cleaned up comments and ensured all linter and test requirements are met.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: correct file extension filter in goimports command
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: use defer to unlock the mutex
Signed-off-by: Deluan <deluan@navidrome.org>
* chore: move Cover Art Archive AlbumMetadataService plugins to an example folder
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: handle errors when creating media metadata and scrobbler service plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: increase compilation timeout to one minute
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add configurable plugin compilation timeout
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement plugin scrobbler support in PlayTracker
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add context management and Stop method to buffered scrobbler
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add username field to scrobbler requests and update logging
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: data race in test
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename http proto files to host and update references
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove unused plugin registration methods from manager
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: extend plugin manifests and implement plugin management commands
Signed-off-by: Deluan <deluan@navidrome.org>
* Update utils/files.go
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
* fix for code scanning alert no. 43: Arbitrary file access during archive extraction ("Zip Slip")
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* feat: add plugin dev workflow support
Added new CLI commands to improve plugin development workflow: 'plugin dev' to create symlinks from development directories to plugins folder, 'plugin refresh' to reload plugins without restarting Navidrome, enhanced 'plugin remove' to handle symlinked development plugins correctly, and updated 'plugin list' to display development plugins with '(dev)' indicator. These changes make the plugin development workflow more efficient by allowing developers to work on plugins in their own directories, link them to Navidrome without copying files, refresh plugins after changes without restart, and clean up safely.
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement timer service with register and cancel functionality - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement timer service with register and cancel functionality - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement timer service with register and cancel functionality - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement timer service with register and cancel functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: lint errors
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(README): update documentation to include TimerCallbackService and its functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add InitService with OnInit method and initialization tracking - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add tests for InitService and plugin initialization tracking
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): expand documentation on plugin system implementation and architecture
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: panic
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): redirect plugins' stderr to logs
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add safe accessor methods for TimerService
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add plugin-specific configuration support in InitRequest and documentation
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add TimerCallbackService plugin adapter and integration
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): rename services for consistency and clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add mutex for configuration access and clone plugin config
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(tests): remove configtest dependency to prevent data races in integration tests
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): remove PluginName method from WASM plugin implementations and update LoadPlugin to accept service type
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement instance pooling for wasmBasePlugin to improve performance - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add wasmInstancePool for managing WASM plugin instances with TTL and max size
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): correctly pass error to done function in wasmBasePlugin
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): rename service types to capabilities for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): simplify instance management in wasmBasePlugin by removing error handling in closure
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): update wasmBasePlugin and wasmInstancePool to return errors for better error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): rename InitService to LifecycleManagement for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): fix instance ID logging in wasmBasePlugin
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): extract instance ID logging to a separate function in wasmBasePlugin, to avoid vet error
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): make timers be isolated per plugin
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): make timers be isolated per plugin
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugins): rename HttpServiceImpl to httpServiceImpl for consistency and improve logging
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add config service for plugin-specific configuration management
Signed-off-by: Deluan <deluan@navidrome.org>
* Update plugins/manager.go
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
* Update plugins/manager.go
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
* feat(crontab): implement crontab service for scheduling and canceling jobs
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(singleton): fix deadlock issue when a constructor calls GetSingleton again
Signed-off-by: Deluan <deluan@navidrome.org> (+1 squashed commit)
Squashed commits:
[325a96ea2] fix(singleton): fix deadlock issue when a constructor calls GetSingleton again
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scheduler): implement Scheduler for one-time and recurring job scheduling, merging CrontabService and TimerService
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scheduler): race condition in the scheduleOneTime and scheduleRecurring methods when replacing jobs with the same ID
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scheduler): consolidate job scheduling logic into a single helper function
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(plugin): rename GetInstance method to Instantiate for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add WebSocket service for handling connections and messages
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(crypto-ticker): add WebSocket plugin for real-time cryptocurrency price tracking
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(websocket): enhance connection management and callback handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(manager): only create one adapter instance for each adapter/capability pair
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(websocket): ensure proper resource management by closing response body and use defer to unlocking mutexes
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: flaky test
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugin): refactor WebSocket service integration and improve error logging
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugin): add SchedulerCallback support and improve reconnection logic
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: test panic
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: add crypto-ticker plugin example to README
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(manager): add LoadAllPlugins and LoadAllMediaAgents methods with slice.Map integration
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(api): add Timestamp field to ScrobblerNowPlayingRequest and update related methods
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(websocket): add error field to response messages for better error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(cache): implement CacheService with string, int, float, and byte operations
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(tests): update buffered scrobbler tests for improved scrobble verification and use RWMutex in mock repo
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(cache): simplify cache service implementation and remove unnecessary synchronization
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(tests): add build step for test plugins in the test suite
Signed-off-by: Deluan <deluan@navidrome.org>
* wip
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scheduler): implement named scheduler callbacks and enhance Discord plugin integration
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(rpc): enhance activity image processing and improve error handling in Discord integration
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(discord): enhance activity state with artist list and add large text asset
Signed-off-by: Deluan <deluan@navidrome.org>
* fix tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(artwork): implement ArtworkService for retrieving artwork URLs
Signed-off-by: Deluan <deluan@navidrome.org>
* Add playback position to scrobble NowPlaying (#4089)
* test(playtracker): cover playback position
* address review comment
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix merge
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove unnecessary check for empty slice in Map function
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: update reflex.conf to include .wasm file extension
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner): normalize attribute strings and add edge case tests for PID calculation
Relates to https://github.com/navidrome/navidrome/issues/4183#issuecomment-2952729458
Signed-off-by: Deluan <deluan@navidrome.org>
* test(ui): fix warnings (#4187)
* fix(ui): address test warnings
* ignore lint error in test
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(server): optimize top songs lookup (#4189)
* optimize top songs lookup
* Optimize title matching queries
* refactor: simplify top songs matching
* improve error handling and logging in track loading functions
Signed-off-by: Deluan <deluan@navidrome.org>
* test: add cases for fallback to title matching and combined MBID/title matching
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(ui): playlist details overflow in spotify-based themes (#4184)
* test: ensure playlist details width
* fix(test): simplify expectation for minWidth in NDPlaylistDetails
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(test): test all themes
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* chore(deps): update TagLib to version 2.1 (#4185)
* chore: update cross-taglib
* fix(taglib): add logging for TagLib version
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* test: verify agents fallback (#4191)
* build(docker): downgrade Alpine version from 3.21 to 3.19, oldest supported version.
This is to reduce the image size, as we don't really need the latest.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(runtime): implement pooled WASM runtime and module for better instance management
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(discord-plugin): adjust timer delay calculation for track completion
Signed-off-by: Deluan <deluan@navidrome.org>
* resolve PR comments
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): implement cache cleanup by size functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(manager): return error from getCompilationCache and handle it in ScanPlugins
Signed-off-by: Deluan <deluan@navidrome.org>
* fix possible rce condition
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(docs): update README to include Cache and Artwork services
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(manager): add permissions support for host services in custom runtime - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(manifest): add permissions field to plugin manifests - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* test(permissions): implement permission validation and testing for plugins - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add unauthorized_plugin to test permission enforcement - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(docs): add Plugin Permission System section to README - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(manifest): add detailed reasons for permissions in plugin manifests - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(permissions): implement granular HTTP permissions for plugins - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(permissions): implement HTTP and WebSocket permissions for plugins - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: unexport all plugins package private symbols
Signed-off-by: Deluan <deluan@navidrome.org>
* update docs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename plugin_lifecycle_manager
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: add discord-rich-presence plugin example to README
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add support for PATCH, HEAD, and OPTIONS HTTP methods
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: use folder names as unique identifiers for plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: read config just once, to avoid data race in tests
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename pluginName to pluginID for consistency across services
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: use symlink name instead of folder name for plugin registration
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update plugin output format to include ID and enhance README with symlink usage
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement shared plugin discovery function to streamline plugin scanning and error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: show plugin permissions in `plugin info`
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add JSON schema for Navidrome Plugin manifest and generate corresponding Go types - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement typed permissions for plugins to enhance permission handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: refactor plugin permissions to use typed schema and improve validation - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update HTTP permissions handling to use typed schema for allowed URLs - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: remove unused JSON schema validation for plugin manifests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: remove unused fields from PluginPackage struct in package.go
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: update file permissions in tests and remove unused permission parsing function
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: refactor test plugin creation to use typed permissions and remove legacy helper
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: add website field to plugin manifests and update test cases
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: permission schema to use basePermission structure for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance host service management by adding permission checks for each service
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: reorganize code files
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify custom runtime creation by removing compilation cache parameter
Signed-off-by: Deluan <deluan@navidrome.org>
* doc: add WebSocketService and update ConfigService for plugin-specific configuration
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: implement WASM loading optimization to enhance plugin instance creation speed
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename custom runtime functions and update related tests for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: enhance plugin structure with compilation handling and error reporting
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: improve logging and context tracing in runtime and wasm base plugin
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: enhance runtime management with scoped runtime and caching improvements
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement EnsureCompiled method for improved plugin compilation handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement cached module management with TTL for improved performance
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: replace map with sync.Map
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: adjust time tolerance in scrobble buffer repository tests to avoid flakiness
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: enhance image processing with fallback mechanism for improved error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* docs: review test plugins readme
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: set default timeout for HTTP client to 10 seconds
Signed-off-by: Deluan <deluan@navidrome.org>
* feat: enhance wasm instance pool with concurrency limits and timeout settings
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(discordrp): implement caching for processed image URLs with configurable TTL
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* feat(ui): add Now Playing panel and integrate now playing count updates
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: check return value in test to satisfy linter
* fix: format React code with prettier
* fix: resolve race condition in play tracker test
* fix: log error when fetching now playing data fails
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): refactor Now Playing panel with new components and error handling
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(ui): adjust padding and height in Now Playing panel for improved layout
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(cache): add automatic cleanup to prevent goroutine leak on cache garbage collection
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(configuration.go, mpv.go): Jukebox mode doesn't include MusicFolder in mpv command - #4066
The call to createMPVCommand is not including the MusicFolder path in
mpv command causing it to fail with file not found errors.
Updated default command template and createMPVCommand to use additional
substitution with conf.server.MusicFolder
Signed-off-by: Pat <patso.oshea@gmail.com>
* Revert config.go change, use filepath.Join for cross platform
* Update track.go with mf.AbsolutePath()
---------
Signed-off-by: Pat <patso.oshea@gmail.com>
Co-authored-by: Deluan <deluan@navidrome.org>
* fix: enhance artist folder detection with directory traversal
Enhanced fromArtistFolder function to implement directory traversal fallback for finding artist images. The original implementation only searched in the calculated artist folder, which failed for single album artists where artist.jpg files were not detected.
Changes: Modified fromArtistFolder to search up to 3 directory levels (artist folder + 2 parent levels), extracted findImageInFolder helper function for cleaner code organization, added proper boundary checks to prevent infinite traversal, maintained backward compatibility with existing functionality.
This fix ensures artist.jpg files are properly detected for single album artists while preserving all existing behavior for multi-album artists.
* refactor: address PR review suggestions
Applied review suggestions from gemini-code-assist bot:
- Added maxArtistFolderTraversalDepth constant instead of hardcoded value 3
- Updated error message to mention that parent directories were also searched
- Enhanced test assertion to verify the improved error message
* fix: improve artist folder traversal logic and enhance error logging
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: remove test for special glob characters in artist folder detection
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: add logging for artist image search in folder
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>