Commit Graph

537 Commits

Author SHA1 Message Date
aa9f0d0345 fix: use unified download structure for cache and permanent files
- Cache mode now uses downloads/cache/ instead of cache/Music/
- Permanent mode now uses downloads/permanent/ instead of downloads/
- Kept files already use downloads/kept/
- All download paths now unified under downloads/ base directory
2026-02-08 01:38:14 -05:00
b0e07404c9 refactor: unified download folder structure
- Changed from separate paths to unified structure under downloads/
- Structure: downloads/{permanent,cache,kept}/
- Removed Library:KeptPath config, now uses downloads/kept/
- Updated AdminController and JellyfinController to use new paths
- Web UI will now correctly show kept tracks in Active Playlists tab
- Matches user's actual folder structure on server
2026-02-08 01:25:24 -05:00
8dbf37f6a3 refactor: remove sync window logic from Spotify Import
- Simplified SpotifyMissingTracksFetcher to remove complex sync window timing
- Now fetches on startup if cache missing, then checks every 5 minutes for stale cache (>24h)
- Removed SYNC_START_HOUR, SYNC_START_MINUTE, SYNC_WINDOW_HOURS from config
- Updated README and .env.example to reflect simpler configuration
- Sync window was only relevant for legacy Jellyfin plugin scraping method
- When using sp_dc cookie method (recommended), this service is dormant anyway
- Deleted MIGRATION.md (local-only file, not for repo)
2026-02-08 01:21:45 -05:00
baab1e88a5 docs: update README and .env.example with new download structure
- Reorganize downloads into downloads/{permanent,cache,kept}
- Update Spotify Import configuration (keep sync window settings)
- Expand Jellyfin API endpoints documentation (primary focus)
- Move Jellyfin backend section before Subsonic
- Simplify Spotify Import documentation
- Add all manual API trigger endpoints
- Update download folder structure diagram
- Add MIGRATION.md guide for existing installations
2026-02-08 01:17:07 -05:00
972756159d feat: add quick health checks before trying endpoints
- Health checks run in parallel with 3 second timeout
- Results cached for 30 seconds to avoid excessive checks
- Healthy endpoints tried first, unhealthy ones as fallback
- Prevents wasting time on dead endpoints (no more 5 min waits)
- Failed requests mark endpoint as unhealthy in cache
- Significantly improves response time when some endpoints are down
2026-02-08 00:05:27 -05:00
f59f265ad4 feat: increase SquidWTF download service timeout to 5 minutes
- Download service was still using default 100s timeout
- Large artist responses and slow endpoints were timing out
- Now both MetadataService and DownloadService use 5 minute timeout
- Fixes 'The request was canceled due to the configured HttpClient.Timeout of 100 seconds' errors
2026-02-08 00:02:21 -05:00
bc0467b1ff feat: increase HttpClient timeout to 5 minutes for large artist responses
- Some artists have 100+ albums causing large API responses
- Default 100s timeout was insufficient
- Now set to 5 minutes to handle even the largest artist catalogs
- Prevents timeouts when fetching artists like Taylor Swift, etc.
2026-02-08 00:01:08 -05:00
e057f365f4 feat: prefetch lyrics immediately after Odesli conversion
- After Odesli converts Tidal ID to Spotify ID, immediately fetch lyrics
- Lyrics are cached and ready when client requests them
- Happens in background, doesn't block streaming
- Ensures best user experience with instant lyrics availability
2026-02-07 23:52:49 -05:00
e8eb095a23 feat: move Odesli conversion to background after streaming starts
- Override ConvertToSpotifyIdAsync in SquidWTFDownloadService
- Odesli API call now happens AFTER stream starts returning to client
- Reduces initial streaming latency by ~3-4 seconds
- Lyrics still work - Spotify ID is cached for on-demand lyrics requests
- Background conversion happens just-in-case for future lyrics needs
2026-02-07 23:51:03 -05:00
591fd5e8e1 feat: add 6 new SquidWTF endpoints and optimize Odesli conversion
- Added 6 new monochrome.tf endpoints (eu-central, us-west, arran, api, samidy)
- Added https variant of hund.qqdl.site (was only http before)
- Total endpoints increased from 10 to 16 for better load distribution
- Optimized Odesli/Spotify ID conversion with 2-second timeout
- If Odesli is slow, download proceeds without waiting (Spotify ID added in background)
- This reduces download time by up to 2 seconds when Odesli is slow
- Spotify ID still obtained for lyrics, just doesn't block streaming

Performance improvement: Downloads that took 9.7s may now complete in 7.7s
2026-02-07 23:47:36 -05:00
3e840f987b fix: improve download speed and handle concurrent requests better
- Reduced wait interval from 500ms to 100ms when waiting for in-progress downloads
- Added cancellation token checks during wait loops to handle client timeouts immediately
- Added detailed timing logs to track download performance
- Better error messages when downloads fail or are cancelled
- Prevents OperationCanceledException when client times out (typically 10 seconds)

This fixes the issue where concurrent requests for the same track would timeout
because they were waiting too long for the first download to complete.
2026-02-07 23:34:04 -05:00
56bc9d4ea9 fix: transparent proxy authentication and token expiration handling
- Remove broken JellyfinAuthFilter that was checking non-existent CLIENT_USERNAME
- Clients now authenticate directly with Jellyfin (transparent proxy model)
- Improved token expiration detection and session cleanup
- Better logging with reduced verbosity (removed emoji spam)
- Added support for X-Emby-Token header format
- Added detection of public endpoints that don't require auth
- SessionManager now properly detects 401 responses and removes expired sessions
- Clarified .env.example comments about server-side vs client-side auth
- All functionality preserved: Spotify injection, external providers, playback tracking
2026-02-07 23:25:14 -05:00
f1dd01f6d5 refactor: reduce log spam by adjusting log levels
- Change WebSocket logs to Debug/Trace (connection events, message proxying)
- Change session management logs to Debug (creation, removal, capabilities)
- Change auth header forwarding logs to Debug
- Change playback forwarding logs to Debug
- Change 401 responses to Debug (expected when tokens expire)
- Keep Info level for significant business events (external playback, downloads)
- Keep Warning/Error for actual issues

This significantly reduces log noise while maintaining visibility of important events.
2026-02-07 23:11:48 -05:00
6c06c59f61 Add enhanced logging for lyrics fetching to debug Jellyfin embedded lyrics check
Some checks failed
CI / build-and-test (push) Has been cancelled
- Log when GetLyrics endpoint is called with itemId
- Log external vs local track determination
- Log Jellyfin lyrics check status code and result
- Will help identify why embedded lyrics aren't being found
2026-02-07 20:00:19 -05:00
56f2eca867 Fix endpoint usage parsing to show actual endpoints instead of HTTP methods
- Parse column 3 (endpoint path) instead of column 2 (HTTP method)
- Combine method and endpoint for clarity (e.g., 'GET users/{userId}')
- Now shows meaningful endpoint statistics instead of just GET/POST counts
2026-02-07 17:54:35 -05:00
248ab804f3 Add API Analytics tab to WebUI for endpoint usage tracking
- New 'API Analytics' tab displays endpoint usage statistics
- Shows total requests, unique endpoints, and most called endpoint
- Table view with top N endpoints (25/50/100/500 configurable)
- Color-coded by usage intensity and endpoint type
- Auto-refreshes every 30 seconds when tab is active
- Includes clear data functionality and helpful documentation
- Uses existing /api/admin/debug/endpoint-usage endpoint
2026-02-07 17:06:59 -05:00
b1769a35bf Fix download racing, cache cleanup, and WebUI storage mode display
- Replace endpoint racing with round-robin fallback for downloads to reduce CPU usage and prevent cancellation errors
- Fix cache cleanup to use LastWriteTimeUtc instead of unreliable LastAccessTimeUtc
- Add storage mode, cache duration, and download mode to admin config endpoint
- Show actual download path based on storage mode (cache/Music vs downloads)
2026-02-07 16:45:28 -05:00
f741cc5297 fix: simplify ghost playback start to avoid Jellyfin validation errors
Some checks failed
CI / build-and-test (push) Has been cancelled
- Remove complex Item object from playback start (was causing 400 errors)
- Send minimal playback info with just ghost UUID and playback state
- Progress reports already working (204 responses)
- Jellyfin will track session without full item details
2026-02-07 13:30:47 -05:00
1a0e0216f5 feat: implement ghost item reporting for external track WebSocket sessions
- Generate deterministic UUIDs from external track IDs using MD5 hashing
- Create fake BaseItemDto objects with track metadata for external tracks
- Forward playback reports (start/progress/stop) to Jellyfin with ghost items
- Enables 'Now Playing' info in Jellyfin dashboard for external tracks
- Remove redundant JellyfinSessionManager WebSocket creation (client handles via proxy)
- Fix indentation issues in SquidWTF services (tabs to spaces)
- Add apis/*.md to .gitignore for temporary docs
- Fix null reference warning in provider switch expression
2026-02-07 13:27:25 -05:00
73bd3bf308 feat: add endpoint benchmarking on startup
- New EndpointBenchmarkService pings all endpoints on startup
- Measures average response time and success rate
- Reorders endpoints by performance (fastest first)
- RoundRobinFallbackHelper now uses benchmarked order
- Racing still happens, but starts with fastest endpoints
- Reduces latency by prioritizing known-fast servers
- Logs benchmark results for visibility
2026-02-07 12:51:48 -05:00
43bf71c390 fix: race endpoints for download metadata fetching
- GetTrackDownloadInfoAsync now uses RaceAllEndpointsAsync instead of TryWithFallbackAsync
- Prevents sequential fallback through all 10 endpoints on cancellation
- Eliminates cascade of 'task was canceled' warnings
- Consistent racing strategy across all download operations
2026-02-07 12:49:43 -05:00
2254616d32 feat: preserve source search ordering instead of re-scoring
- Respect SquidWTF/Tidal's native search ranking (better than fuzzy matching)
- Interleave local and external results based on average match quality
- Put better-matching source first, preserve original order within each source
- Remove unnecessary re-scoring that was disrupting optimal search results
- Simplifies search logic and improves result relevance
2026-02-07 12:43:15 -05:00
c0444becad feat: add endpoint racing for downloads and searches
- Race all proxy endpoints in parallel for downloads (SquidWTF)
- Use fastest responding server, cancel slower ones
- Apply same racing strategy to search operations
- Reduces download wait times from 5-10s to sub-second
- Reduces search latency from ~1s to ~300-500ms
- Add RaceAllEndpointsAsync method to RoundRobinFallbackHelper
2026-02-07 12:36:50 -05:00
b906a5fd6d Refactor: Extract duplicate code into reusable helpers
- Created RoundRobinFallbackHelper for SquidWTF services (eliminates 3 duplicates)
- Moved QueueRequestAsync to BaseDownloadService (eliminates 2 duplicates)
- Moved CalculateArtistMatchScore to FuzzyMatcher (eliminates 2 duplicates)
- Updated all SquidWTF services to use RoundRobinFallbackHelper
- Updated DeezerDownloadService and SquidWTFDownloadService to use base class rate limiting
- Updated SpotifyTrackMatchingService and JellyfinController to use FuzzyMatcher helper
- All 225 tests passing
2026-02-07 12:27:10 -05:00
e3bcc93597 Add Odesli service for Tidal to Spotify ID conversion
- Created OdesliService to convert Tidal track IDs to Spotify IDs
- Integrated Odesli API calls into SquidWTF download workflow
- Updated SquidWTFDownloadService to use OdesliService for track metadata enrichment
- Fixed dependency injection in Program.cs for OdesliService
- All 225 tests passing
2026-02-07 12:19:41 -05:00
7e6bed51e1 refactor: extract Spotify ID from URL instead of entityUniqueId
- Use linksByPlatform.spotify.url from Odesli response
- Extract track ID from Spotify URL using regex
- More reliable than parsing entityUniqueId format
- Matches the approach used in ConvertToSpotifyIdViaOdesliAsync
2026-02-07 12:06:48 -05:00
47b9427c20 fix: extract Spotify track ID from Odesli entityUniqueId format
- Odesli returns entityUniqueId as 'SPOTIFY_SONG::trackid'
- Now extracts just the track ID part after '::'
- Fixes Spotify lyrics not working due to invalid ID format
- Spotify lyrics service expects clean track IDs like '0PgYPBGqF6Wm5KFHQ81nq5'
2026-02-07 12:05:55 -05:00
bb46db43b1 fix: use persistent cache/Music folder instead of /tmp
- Cache mode now uses cache/Music/ (survives restarts, cleaned after 24h)
- Permanent mode uses downloads/ (keeps forever)
- Fixed all three download services: SquidWTF, Deezer, Qobuz
- Files no longer stored in /tmp/allstarr-cache/ which gets wiped on restart
- Both folders are in project root alongside cache/ and downloads/ directories
2026-02-07 12:02:48 -05:00
3937e637c6 feat: convert Tidal tracks to Spotify ID immediately for lyrics
- Added SpotifyId field to Song model
- SquidWTFMetadataService now calls Odesli API when fetching track metadata
- Spotify ID is populated immediately when track is loaded, not during lyrics fetch
- GetLyrics now checks song.SpotifyId first before falling back to cache/Odesli
- Enables Spotify lyrics for all SquidWTF (Tidal) tracks automatically
- Reduces latency - conversion happens once during track load, not every lyrics request
2026-02-07 11:57:24 -05:00
2272e8d363 fix: use stored session headers for WebSocket auth
- Changed MaintainWebSocketForSessionAsync to use session.Headers instead of parameter
- Parameter headers might be disposed after HTTP request completes
- Session headers are cloned and stored safely in memory
- WebSocket now properly authenticates as the client instead of falling back to server API key
- Sessions will now appear under correct user in Jellyfin dashboard
2026-02-07 11:52:17 -05:00
6169d7a4ac fix: session capabilities using disposed HTTP context
- Extract AccessToken from auth response before background task
- Create new HeaderDictionary with token instead of using Request.Headers
- Prevents ObjectDisposedException when HTTP context is disposed
- Session capabilities now work correctly for all clients

Note: WebSocket support for external tracks already implemented via
JellyfinSessionManager.EnsureSessionAsync and WebSocketProxyMiddleware
2026-02-07 11:49:43 -05:00
da8cb29e08 refactor: make authentication truly transparent proxy
- Pass through ALL Jellyfin responses (success and error) without modification
- Move session capabilities posting to background task (don't block auth response)
- Remove generic error fallbacks - always return Jellyfin's actual response
- Simplify logic: if Jellyfin returns a response, pass it through; if not, return status code only
2026-02-07 11:45:16 -05:00
d88ed64e37 fix: pass through Jellyfin error responses to client
- Modified PostJsonAsync to return error response body as JSON when available
- Updated AuthenticateByName to pass through Jellyfin's actual error response
- Clients now see Jellyfin's real error messages instead of generic ones
2026-02-07 11:42:11 -05:00
210d18220b fix: use case-insensitive provider key matching 2026-02-07 11:22:00 -05:00
c44e48a425 add debug logging to track provider identification 2026-02-07 11:21:03 -05:00
e44b46aee1 remove lyrics column from playlist table 2026-02-07 11:17:48 -05:00
a75df9328a fix: use playlist cache in view tracks endpoint 2026-02-07 11:14:36 -05:00
35c125d042 fix: skip expensive track stats query for non-Spotify playlists to prevent timeouts
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-02-07 03:48:00 -05:00
b12c971968 comment 2026-02-07 03:47:15 -05:00
8f051ad413 chore: remove noisy admin controller init log 2026-02-07 03:39:07 -05:00
6c1a578b35 fix: include manual external mappings in fallback playlist stats and add live UI refresh 2026-02-07 03:36:26 -05:00
8ab2923493 fix: increase delays in refresh & match all to ensure cache clears before matching 2026-02-07 03:23:13 -05:00
42b4e0e399 feat: add tooltips, refresh & match button, and matching warning banner 2026-02-07 02:36:48 -05:00
f03aa0be35 refactor: remove lyrics prefetching UI and optimize admin endpoints 2026-02-07 01:16:03 -05:00
440ef9850f Make kept path configurable via web UI
- Added Library:KeptPath to appsettings.json (default: /app/kept)
- Added Library Settings card to web UI with DownloadPath and KeptPath
- Updated GetDownloads and DeleteDownload endpoints to use configured path
- Updated JellyfinController to use configured kept path
- Injected IConfiguration into JellyfinController
- Users can now customize where favorited tracks are stored
2026-02-07 00:35:12 -05:00
c9b44dea43 Fix delete endpoint to work with kept folder and clean up empty directories
- Changed delete endpoint from Library:DownloadPath to /app/kept
- Now properly deletes empty Album and Artist folders after file deletion
- Added debug logging for deletion operations
- Structure: Artist/Album/Track.flac
2026-02-07 00:32:22 -05:00
3a9d00dcdb Fix downloads endpoint to only show kept files with debug logging
- Downloads endpoint now ONLY shows /app/kept (favorited tracks)
- Removed cache downloads from this endpoint (separate box needed)
- Added debug logging to troubleshoot why kept files weren't showing
- Logs directory existence and file count
2026-02-06 23:55:07 -05:00
2389b80733 Fix downloads endpoint to show kept files and remove lyrics cache endpoint
- Downloads endpoint now shows both /app/downloads (cache) and /app/kept (favorited)
- Added location field to distinguish between cache and kept files
- Added cacheCount and keptCount to response
- Removed lyrics cache clear endpoint (no longer needed)
2026-02-06 23:53:36 -05:00
b99a199ef3 Fix lyrics fetching and disable prefetching
- Fix LyricsPrefetchService to use server API key for Jellyfin lyrics checks
- Remove Spotify lyrics caching (local Docker container is fast)
- Disable lyrics prefetching service (not needed - Jellyfin/Spotify are fast)
- Add POST /api/admin/cache/clear-lyrics endpoint to clear LRCLIB cache
- Only LRCLIB lyrics are cached now (external API)
2026-02-06 23:48:18 -05:00
64e2004bdc Fix syntax error in AdminController.cs - move closing brace to correct location 2026-02-06 23:26:30 -05:00