Comprehensive refactoring to improve maintainability, reduce code duplication, and facilitate the addition of new music providers.
Key architectural improvements:
- Introduced BaseDownloadService template method pattern, eliminating ~80% code duplication between Deezer and Qobuz services
- Organized services by provider (Services/Deezer/, Services/Qobuz/, Services/Local/, Services/Subsonic/)
- Extracted SubsonicController logic into 4 specialized services, reducing controller size by 43% (1,174 → 666 lines)
- Reorganized Models into domain-driven subdirectories (Domain/, Settings/, Download/, Search/)
- Implemented Result<T> pattern and GlobalExceptionHandler for consistent error handling
- Created unified validation architecture with IStartupValidator interface
Testing & Quality:
- Increased test coverage from ~30 to 127 tests (+323%)
- Added comprehensive test suites for QobuzDownloadService and Subsonic services
- All tests passing, zero build errors
Impact:
- Net reduction of 343 lines while adding more functionality
- No breaking changes - Subsonic API surface unchanged
- Foundation ready for easy addition of new providers (Tidal, Spotify, etc.)
- Split ci.yml into two workflows for cleaner PR checks
- ci.yml: runs build-and-test on PRs and pushes (no Docker)
- docker.yml: runs build-and-test + Docker build/push on merges, tags, and manual triggers
- Eliminates 'docker: skipping' status on open PRs
- Add 15 unit tests covering authentication, download logic, and configuration
- Test IsAvailableAsync with various credential combinations
- Test download flow for unsupported providers, existing files, and missing songs
- Test album background download functionality
- Test quality format configuration (FLAC, MP3, default)
- All tests passing (127 total tests)
- Extract SubsonicRequestParser for HTTP parameter extraction
- Extract SubsonicResponseBuilder for XML/JSON response formatting
- Extract SubsonicModelMapper for search result parsing and merging
- Extract SubsonicProxyService for upstream Subsonic server communication
- Add comprehensive test coverage (45 tests) for all new services
- Reduce SubsonicController from 1174 to 666 lines (-43%)
All tests passing. Build succeeds with 0 errors.
Moved PathHelper from DeezerDownloadService to Services/Common/ to:
- Remove awkward dependency of Qobuz on Deezer namespace
- Make path utilities reusable by all services
- Improve code organization and clarify dependencies
Updated imports in DeezerDownloadService, QobuzDownloadService,
BaseDownloadService, and DeezerDownloadServiceTests.
- Register DeezerDownloadService and DeezerMetadataService as Singleton
to properly share state across requests (rate limiting, download tracking)
- Fix race condition in LocalLibraryService.LoadMappingsAsync with
double-check locking pattern
- Dispose HttpRequestMessage objects to prevent memory leaks (4 occurrences)
- Handle fire-and-forget TriggerLibraryScanAsync with proper error logging
- Replace Console.WriteLine with ILogger in SubsonicController
- Fix while loop in DownloadSongAsync to refresh activeDownload state
- Use modern C# range operator syntax for Substring calls
- Clean up appsettings.json template (remove private IP, clear ARL token)
- Add documentation comment for Blowfish decryption key
- Add downloads directory to gitignore
- getArtist now merges local Navidrome albums with Deezer albums
- getAlbum returns complete song metadata for Deezer albums
- Added missing fields (parent, isDir, suffix, contentType, type, isVideo, duration, genre) for Aonsoku compatibility
ASP.NET Core serializes anonymous objects with camelCase, producing
'subsonicResponse' instead of 'subsonic-response'. Fixed by using
Dictionary with explicit key names for all JSON responses.