quintodrome/server/subsonic/api.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

358 lines
11 KiB
Go
Raw Normal View History

package subsonic
import (
"encoding/json"
"encoding/xml"
2022-09-30 14:54:25 -08:00
"errors"
"fmt"
"net/http"
2021-05-11 13:21:18 -08:00
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/artwork"
refactor: external_metadata -> external.Provider (#3903) * tests for TopSongs Signed-off-by: Deluan <deluan@navidrome.org> * convert to Ginkgo Signed-off-by: Deluan <deluan@navidrome.org> * consolidate tests Signed-off-by: Deluan <deluan@navidrome.org> * rename external metadata -wip Signed-off-by: Deluan <deluan@navidrome.org> * rename external metadata to extdata.Provider Signed-off-by: Deluan <deluan@navidrome.org> * refactor tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * refactor test helpers Signed-off-by: Deluan <deluan@navidrome.org> * remove reflection Signed-off-by: Deluan <deluan@navidrome.org> * use mock.Mock Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * fix Signed-off-by: Deluan <deluan@navidrome.org> * receive Agents interface in Provider constructor Signed-off-by: Deluan <deluan@navidrome.org> * use mock for Agents Signed-off-by: Deluan <deluan@navidrome.org> * tests for SimilarSongs Signed-off-by: Deluan <deluan@navidrome.org> * remove duplication Signed-off-by: Deluan <deluan@navidrome.org> * ArtistImage tests Signed-off-by: Deluan <deluan@navidrome.org> * AlbumImage tests Signed-off-by: Deluan <deluan@navidrome.org> * fix provider error handling Signed-off-by: Deluan <deluan@navidrome.org> * UpdateAlbumInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * UpdateAlbumInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * UpdateArtistInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * clean up Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * fix test descriptions Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor: rename extdata package to external Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-04-08 17:11:09 -08:00
"github.com/navidrome/navidrome/core/external"
"github.com/navidrome/navidrome/core/metrics"
"github.com/navidrome/navidrome/core/playback"
2021-06-19 16:56:56 -08:00
"github.com/navidrome/navidrome/core/scrobbler"
2020-02-01 16:07:15 -09:00
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/server"
"github.com/navidrome/navidrome/server/events"
2020-01-23 15:44:08 -09:00
"github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils/req"
)
2020-11-01 13:04:53 -09:00
const Version = "1.16.1"
type handler = func(*http.Request) (*responses.Subsonic, error)
type handlerRaw = func(http.ResponseWriter, *http.Request) (*responses.Subsonic, error)
type Router struct {
http.Handler
refactor: external_metadata -> external.Provider (#3903) * tests for TopSongs Signed-off-by: Deluan <deluan@navidrome.org> * convert to Ginkgo Signed-off-by: Deluan <deluan@navidrome.org> * consolidate tests Signed-off-by: Deluan <deluan@navidrome.org> * rename external metadata -wip Signed-off-by: Deluan <deluan@navidrome.org> * rename external metadata to extdata.Provider Signed-off-by: Deluan <deluan@navidrome.org> * refactor tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * refactor test helpers Signed-off-by: Deluan <deluan@navidrome.org> * remove reflection Signed-off-by: Deluan <deluan@navidrome.org> * use mock.Mock Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * fix Signed-off-by: Deluan <deluan@navidrome.org> * receive Agents interface in Provider constructor Signed-off-by: Deluan <deluan@navidrome.org> * use mock for Agents Signed-off-by: Deluan <deluan@navidrome.org> * tests for SimilarSongs Signed-off-by: Deluan <deluan@navidrome.org> * remove duplication Signed-off-by: Deluan <deluan@navidrome.org> * ArtistImage tests Signed-off-by: Deluan <deluan@navidrome.org> * AlbumImage tests Signed-off-by: Deluan <deluan@navidrome.org> * fix provider error handling Signed-off-by: Deluan <deluan@navidrome.org> * UpdateAlbumInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * UpdateAlbumInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * UpdateArtistInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * clean up Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * fix test descriptions Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor: rename extdata package to external Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-04-08 17:11:09 -08:00
ds model.DataStore
artwork artwork.Artwork
streamer core.MediaStreamer
archiver core.Archiver
players core.Players
provider external.Provider
playlists core.Playlists
feat(scanner): implement selective folder scanning and file system watcher improvements (#4674) * 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>
2025-11-14 18:15:43 -09:00
scanner model.Scanner
refactor: external_metadata -> external.Provider (#3903) * tests for TopSongs Signed-off-by: Deluan <deluan@navidrome.org> * convert to Ginkgo Signed-off-by: Deluan <deluan@navidrome.org> * consolidate tests Signed-off-by: Deluan <deluan@navidrome.org> * rename external metadata -wip Signed-off-by: Deluan <deluan@navidrome.org> * rename external metadata to extdata.Provider Signed-off-by: Deluan <deluan@navidrome.org> * refactor tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * refactor test helpers Signed-off-by: Deluan <deluan@navidrome.org> * remove reflection Signed-off-by: Deluan <deluan@navidrome.org> * use mock.Mock Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * fix Signed-off-by: Deluan <deluan@navidrome.org> * receive Agents interface in Provider constructor Signed-off-by: Deluan <deluan@navidrome.org> * use mock for Agents Signed-off-by: Deluan <deluan@navidrome.org> * tests for SimilarSongs Signed-off-by: Deluan <deluan@navidrome.org> * remove duplication Signed-off-by: Deluan <deluan@navidrome.org> * ArtistImage tests Signed-off-by: Deluan <deluan@navidrome.org> * AlbumImage tests Signed-off-by: Deluan <deluan@navidrome.org> * fix provider error handling Signed-off-by: Deluan <deluan@navidrome.org> * UpdateAlbumInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * UpdateAlbumInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * UpdateArtistInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * clean up Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * fix test descriptions Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor: rename extdata package to external Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-04-08 17:11:09 -08:00
broker events.Broker
scrobbler scrobbler.PlayTracker
share core.Share
playback playback.PlaybackServer
metrics metrics.Metrics
}
func New(ds model.DataStore, artwork artwork.Artwork, streamer core.MediaStreamer, archiver core.Archiver,
feat(scanner): implement selective folder scanning and file system watcher improvements (#4674) * 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>
2025-11-14 18:15:43 -09:00
players core.Players, provider external.Provider, scanner model.Scanner, broker events.Broker,
playlists core.Playlists, scrobbler scrobbler.PlayTracker, share core.Share, playback playback.PlaybackServer,
metrics metrics.Metrics,
) *Router {
r := &Router{
refactor: external_metadata -> external.Provider (#3903) * tests for TopSongs Signed-off-by: Deluan <deluan@navidrome.org> * convert to Ginkgo Signed-off-by: Deluan <deluan@navidrome.org> * consolidate tests Signed-off-by: Deluan <deluan@navidrome.org> * rename external metadata -wip Signed-off-by: Deluan <deluan@navidrome.org> * rename external metadata to extdata.Provider Signed-off-by: Deluan <deluan@navidrome.org> * refactor tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * refactor test helpers Signed-off-by: Deluan <deluan@navidrome.org> * remove reflection Signed-off-by: Deluan <deluan@navidrome.org> * use mock.Mock Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * fix Signed-off-by: Deluan <deluan@navidrome.org> * receive Agents interface in Provider constructor Signed-off-by: Deluan <deluan@navidrome.org> * use mock for Agents Signed-off-by: Deluan <deluan@navidrome.org> * tests for SimilarSongs Signed-off-by: Deluan <deluan@navidrome.org> * remove duplication Signed-off-by: Deluan <deluan@navidrome.org> * ArtistImage tests Signed-off-by: Deluan <deluan@navidrome.org> * AlbumImage tests Signed-off-by: Deluan <deluan@navidrome.org> * fix provider error handling Signed-off-by: Deluan <deluan@navidrome.org> * UpdateAlbumInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * UpdateAlbumInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * UpdateArtistInfo tests - wip Signed-off-by: Deluan <deluan@navidrome.org> * clean up Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * fix test descriptions Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> * refactor: rename extdata package to external Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-04-08 17:11:09 -08:00
ds: ds,
artwork: artwork,
streamer: streamer,
archiver: archiver,
players: players,
provider: provider,
playlists: playlists,
scanner: scanner,
broker: broker,
scrobbler: scrobbler,
share: share,
playback: playback,
metrics: metrics,
}
r.Handler = r.routes()
2020-01-11 09:21:43 -09:00
return r
}
func (api *Router) routes() http.Handler {
r := chi.NewRouter()
if conf.Server.Prometheus.Enabled {
r.Use(recordStats(api.metrics))
}
r.Use(postFormToQueryParams)
// Public
h(r, "getOpenSubsonicExtensions", api.GetOpenSubsonicExtensions)
// Protected
r.Group(func(r chi.Router) {
r.Use(checkRequiredParameters)
r.Use(authenticate(api.ds))
r.Use(server.UpdateLastAccessMiddleware(api.ds))
// Subsonic endpoints, grouped by controller
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "ping", api.Ping)
h(r, "getLicense", api.GetLicense)
})
Jukebox mode (#2289) * Adding cache directory to ignore-list * Adding jukebox-related config options * Adding DevEnableJukebox config option pls. dummy server * Adding types and routers * Now without panic * First draft on parsing the action * Some cleanups * Adding playback server * Verify audio device configuration * Adding debug-build target to have full symbol support * Adding beep sound library pls some example code. Not working yet * Play a fixed mp3 on any interface access for testing purposes * Put action code into separate file, adding stringer, more debug output, prepare structs, validation * Put action parameter parser code where it belongs * Have a single Action transporting all information * User fmt.Errorf for error-generation * Adding wide playback interface * Use action map for parsing, stringer instead switch stmt. * Use but only one switch case and direct dispatch, refactoring * Add error handling and pushing to client * send decent errormessage, no internal server error * Adding playback devices slice and load it from config * Combine config-verification and structure init * Return user-specific device * Separate playback server from device * Use dataStore to retrieve mediafile by id * WIP: Playlist and start/stop handling. Doing start/stop the hard way as of now * WIP: set, start and stop work on one single song. More to come * Dont need to wait for the end * Merge jukebox_action.go into jukebox.go * Remove getParameterAsInt64(). Use existing requiredParamInt() instead * Dont need to call newFailure() explicitly * Remove int64, use int instead. * Add and set action now accept multiple ids * Kickout copy of childFromMediaFile(). It is not needed here. * Refactoring devices and playbackServer * Turn (internal) playback.DeviceStatus into subsonic JukeboxStatus when rendering output. Indexes int64 -> int * Now we have a position and playing status * Switching gain to float32, xs:float is defined as 32 bit. Fixing nasty copy/pointer bug * Now with volume control * Start working the queue * Remove user from device interface * Rename function GetDevice -> GetDeviceForUser to make intention clearer * Have a nice stringer for the queue * User Prepared boolean for now to allow pause/unpause * Skipping works, but without offsets * Make ChildFromMediaFile public to be used in jukebox get() implementation * Return position in seconds and implement offset-skip in seconds * Default offset to 0 * Adding a simple setGain implementation * Prepare for transcoding AAC * WIP: transcode to WAV to use beeps wav decoder. Not done yet. * WIP: out of sheer desparation: convert to MP3 (which works) rather than WAV to troubleshoot issue. * Use FLAC as intermediate format to play Apple AAC * A bit of cleanup * Catching the end-of-stream event for further reactions * Have a trackSwitching goroutine waiting on channel when track ends * Move decoder code into own file. Restructure code a bit * Now with going on to play the next song in the playlist * Adding shuffle feature * Implementing remove action * Cleanup code * Remove templates for ffmpeg mp3 generation. Not needed anymore. * Adding some documentation * Check whether offset into track is in range. Fixing potential remove track bug. Documentation * Make golangci-lint happy: handling return values * Adding test suite and example dummy for playback package * Adding some basic queue tests * Only use Jukebox.Enabled config option * Adding stream closing handling * Pass context.Context to all PlaybackDevice methods * Remove unneeded function * Correct spelling * Reduce visibility of ChildFromMediaFile * Decomplicate action-parsing * Adding simple tempfile-based AAC->FLAC transcoding. No parallel reading and writing yet. * Try to optimize pipe-writing, tempfile-handling and reading. Not done yet. * Do a synchronous copy of the tempfile. Racecondition detected * More debugging statements and fixing the play/pause bug. More work needed * Start the trackSwitcher() with each device once. Return JSON position even if its 0. More debug-output * Moving all track-handling code into own module * Fix typo. Do not pass ctx around when not applicable * WIP: More refactoring, debugging output * Fix nil pointer * Repairing MP3 playback by pinning indirect dependencies: hajimehoshi/go-mp3 and hajimehoshi/oto * Do not forget to cleanup after a skip action * Make resync with master easy * Adding missing mocks * Adding missing error-handling found by linter * Updating github.com/hajimehoshi/oto * Removing duplicate function * Move BEEP-related code into own package * Juggle beep-related code around as preparation for interface access * More refactoring for interface separation * Gather CloseDevice() behind Track interface. * Adding skeleton, draft audio-interface using mpv.io * Adding majority of interface commands using messages to mpv socket. * Adding end-of-stream handling * MPV: start/stop are working * postition is given in float in mpv * Unify Close() and CloseDevice(). Using temp filename for controlling socket * Wait until control-socket shows up. Cleanup socket in Close() * Use canceable command. Rename to Executor * Skipping tracks works now * Now with actually setting the position * Fix regain * Add missing error-handling found by linter * Adding retry mode on time-pos property getter * Remove unneeded code on queue * Putting build-tag beep onto beep files * Remove deprecated call to rand.Seed() "As of Go 1.20 there is no reason to call Seed with a random value. Programs that call Seed with a known value to get a specific sequence of results should use New(NewSource(seed)) to obtain a local random generator." * Using int32 to conform to Subsonic API spec * Fix merge error * Minor style changes * Get username from context --------- Co-authored-by: Deluan <deluan@navidrome.org>
2023-09-10 07:25:22 -08:00
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "getMusicFolders", api.GetMusicFolders)
h(r, "getIndexes", api.GetIndexes)
h(r, "getArtists", api.GetArtists)
h(r, "getGenres", api.GetGenres)
h(r, "getMusicDirectory", api.GetMusicDirectory)
h(r, "getArtist", api.GetArtist)
h(r, "getAlbum", api.GetAlbum)
h(r, "getSong", api.GetSong)
h(r, "getAlbumInfo", api.GetAlbumInfo)
h(r, "getAlbumInfo2", api.GetAlbumInfo)
h(r, "getArtistInfo", api.GetArtistInfo)
h(r, "getArtistInfo2", api.GetArtistInfo2)
h(r, "getTopSongs", api.GetTopSongs)
h(r, "getSimilarSongs", api.GetSimilarSongs)
h(r, "getSimilarSongs2", api.GetSimilarSongs2)
Jukebox mode (#2289) * Adding cache directory to ignore-list * Adding jukebox-related config options * Adding DevEnableJukebox config option pls. dummy server * Adding types and routers * Now without panic * First draft on parsing the action * Some cleanups * Adding playback server * Verify audio device configuration * Adding debug-build target to have full symbol support * Adding beep sound library pls some example code. Not working yet * Play a fixed mp3 on any interface access for testing purposes * Put action code into separate file, adding stringer, more debug output, prepare structs, validation * Put action parameter parser code where it belongs * Have a single Action transporting all information * User fmt.Errorf for error-generation * Adding wide playback interface * Use action map for parsing, stringer instead switch stmt. * Use but only one switch case and direct dispatch, refactoring * Add error handling and pushing to client * send decent errormessage, no internal server error * Adding playback devices slice and load it from config * Combine config-verification and structure init * Return user-specific device * Separate playback server from device * Use dataStore to retrieve mediafile by id * WIP: Playlist and start/stop handling. Doing start/stop the hard way as of now * WIP: set, start and stop work on one single song. More to come * Dont need to wait for the end * Merge jukebox_action.go into jukebox.go * Remove getParameterAsInt64(). Use existing requiredParamInt() instead * Dont need to call newFailure() explicitly * Remove int64, use int instead. * Add and set action now accept multiple ids * Kickout copy of childFromMediaFile(). It is not needed here. * Refactoring devices and playbackServer * Turn (internal) playback.DeviceStatus into subsonic JukeboxStatus when rendering output. Indexes int64 -> int * Now we have a position and playing status * Switching gain to float32, xs:float is defined as 32 bit. Fixing nasty copy/pointer bug * Now with volume control * Start working the queue * Remove user from device interface * Rename function GetDevice -> GetDeviceForUser to make intention clearer * Have a nice stringer for the queue * User Prepared boolean for now to allow pause/unpause * Skipping works, but without offsets * Make ChildFromMediaFile public to be used in jukebox get() implementation * Return position in seconds and implement offset-skip in seconds * Default offset to 0 * Adding a simple setGain implementation * Prepare for transcoding AAC * WIP: transcode to WAV to use beeps wav decoder. Not done yet. * WIP: out of sheer desparation: convert to MP3 (which works) rather than WAV to troubleshoot issue. * Use FLAC as intermediate format to play Apple AAC * A bit of cleanup * Catching the end-of-stream event for further reactions * Have a trackSwitching goroutine waiting on channel when track ends * Move decoder code into own file. Restructure code a bit * Now with going on to play the next song in the playlist * Adding shuffle feature * Implementing remove action * Cleanup code * Remove templates for ffmpeg mp3 generation. Not needed anymore. * Adding some documentation * Check whether offset into track is in range. Fixing potential remove track bug. Documentation * Make golangci-lint happy: handling return values * Adding test suite and example dummy for playback package * Adding some basic queue tests * Only use Jukebox.Enabled config option * Adding stream closing handling * Pass context.Context to all PlaybackDevice methods * Remove unneeded function * Correct spelling * Reduce visibility of ChildFromMediaFile * Decomplicate action-parsing * Adding simple tempfile-based AAC->FLAC transcoding. No parallel reading and writing yet. * Try to optimize pipe-writing, tempfile-handling and reading. Not done yet. * Do a synchronous copy of the tempfile. Racecondition detected * More debugging statements and fixing the play/pause bug. More work needed * Start the trackSwitcher() with each device once. Return JSON position even if its 0. More debug-output * Moving all track-handling code into own module * Fix typo. Do not pass ctx around when not applicable * WIP: More refactoring, debugging output * Fix nil pointer * Repairing MP3 playback by pinning indirect dependencies: hajimehoshi/go-mp3 and hajimehoshi/oto * Do not forget to cleanup after a skip action * Make resync with master easy * Adding missing mocks * Adding missing error-handling found by linter * Updating github.com/hajimehoshi/oto * Removing duplicate function * Move BEEP-related code into own package * Juggle beep-related code around as preparation for interface access * More refactoring for interface separation * Gather CloseDevice() behind Track interface. * Adding skeleton, draft audio-interface using mpv.io * Adding majority of interface commands using messages to mpv socket. * Adding end-of-stream handling * MPV: start/stop are working * postition is given in float in mpv * Unify Close() and CloseDevice(). Using temp filename for controlling socket * Wait until control-socket shows up. Cleanup socket in Close() * Use canceable command. Rename to Executor * Skipping tracks works now * Now with actually setting the position * Fix regain * Add missing error-handling found by linter * Adding retry mode on time-pos property getter * Remove unneeded code on queue * Putting build-tag beep onto beep files * Remove deprecated call to rand.Seed() "As of Go 1.20 there is no reason to call Seed with a random value. Programs that call Seed with a known value to get a specific sequence of results should use New(NewSource(seed)) to obtain a local random generator." * Using int32 to conform to Subsonic API spec * Fix merge error * Minor style changes * Get username from context --------- Co-authored-by: Deluan <deluan@navidrome.org>
2023-09-10 07:25:22 -08:00
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
hr(r, "getAlbumList", api.GetAlbumList)
hr(r, "getAlbumList2", api.GetAlbumList2)
h(r, "getStarred", api.GetStarred)
h(r, "getStarred2", api.GetStarred2)
h(r, "getNowPlaying", api.GetNowPlaying)
h(r, "getRandomSongs", api.GetRandomSongs)
h(r, "getSongsByGenre", api.GetSongsByGenre)
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "setRating", api.SetRating)
h(r, "star", api.Star)
h(r, "unstar", api.Unstar)
h(r, "scrobble", api.Scrobble)
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "getPlaylists", api.GetPlaylists)
h(r, "getPlaylist", api.GetPlaylist)
h(r, "createPlaylist", api.CreatePlaylist)
h(r, "deletePlaylist", api.DeletePlaylist)
h(r, "updatePlaylist", api.UpdatePlaylist)
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "getBookmarks", api.GetBookmarks)
h(r, "createBookmark", api.CreateBookmark)
h(r, "deleteBookmark", api.DeleteBookmark)
h(r, "getPlayQueue", api.GetPlayQueue)
h(r, "getPlayQueueByIndex", api.GetPlayQueueByIndex)
h(r, "savePlayQueue", api.SavePlayQueue)
h(r, "savePlayQueueByIndex", api.SavePlayQueueByIndex)
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "search2", api.Search2)
h(r, "search3", api.Search3)
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "getUser", api.GetUser)
h(r, "getUsers", api.GetUsers)
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "getScanStatus", api.GetScanStatus)
h(r, "startScan", api.StartScan)
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
hr(r, "getAvatar", api.GetAvatar)
h(r, "getLyrics", api.GetLyrics)
h(r, "getLyricsBySongId", api.GetLyricsBySongId)
hr(r, "stream", api.Stream)
hr(r, "download", api.Download)
})
r.Group(func(r chi.Router) {
// configure request throttling
if conf.Server.DevArtworkMaxRequests > 0 {
log.Debug("Throttling Subsonic getCoverArt endpoint", "maxRequests", conf.Server.DevArtworkMaxRequests,
"backlogLimit", conf.Server.DevArtworkThrottleBacklogLimit, "backlogTimeout",
conf.Server.DevArtworkThrottleBacklogTimeout)
r.Use(middleware.ThrottleBacklog(conf.Server.DevArtworkMaxRequests, conf.Server.DevArtworkThrottleBacklogLimit,
conf.Server.DevArtworkThrottleBacklogTimeout))
}
hr(r, "getCoverArt", api.GetCoverArt)
})
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "createInternetRadioStation", api.CreateInternetRadio)
h(r, "deleteInternetRadioStation", api.DeleteInternetRadio)
h(r, "getInternetRadioStations", api.GetInternetRadios)
h(r, "updateInternetRadioStation", api.UpdateInternetRadio)
})
if conf.Server.EnableSharing {
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "getShares", api.GetShares)
h(r, "createShare", api.CreateShare)
h(r, "updateShare", api.UpdateShare)
h(r, "deleteShare", api.DeleteShare)
})
} else {
h501(r, "getShares", "createShare", "updateShare", "deleteShare")
}
Jukebox mode (#2289) * Adding cache directory to ignore-list * Adding jukebox-related config options * Adding DevEnableJukebox config option pls. dummy server * Adding types and routers * Now without panic * First draft on parsing the action * Some cleanups * Adding playback server * Verify audio device configuration * Adding debug-build target to have full symbol support * Adding beep sound library pls some example code. Not working yet * Play a fixed mp3 on any interface access for testing purposes * Put action code into separate file, adding stringer, more debug output, prepare structs, validation * Put action parameter parser code where it belongs * Have a single Action transporting all information * User fmt.Errorf for error-generation * Adding wide playback interface * Use action map for parsing, stringer instead switch stmt. * Use but only one switch case and direct dispatch, refactoring * Add error handling and pushing to client * send decent errormessage, no internal server error * Adding playback devices slice and load it from config * Combine config-verification and structure init * Return user-specific device * Separate playback server from device * Use dataStore to retrieve mediafile by id * WIP: Playlist and start/stop handling. Doing start/stop the hard way as of now * WIP: set, start and stop work on one single song. More to come * Dont need to wait for the end * Merge jukebox_action.go into jukebox.go * Remove getParameterAsInt64(). Use existing requiredParamInt() instead * Dont need to call newFailure() explicitly * Remove int64, use int instead. * Add and set action now accept multiple ids * Kickout copy of childFromMediaFile(). It is not needed here. * Refactoring devices and playbackServer * Turn (internal) playback.DeviceStatus into subsonic JukeboxStatus when rendering output. Indexes int64 -> int * Now we have a position and playing status * Switching gain to float32, xs:float is defined as 32 bit. Fixing nasty copy/pointer bug * Now with volume control * Start working the queue * Remove user from device interface * Rename function GetDevice -> GetDeviceForUser to make intention clearer * Have a nice stringer for the queue * User Prepared boolean for now to allow pause/unpause * Skipping works, but without offsets * Make ChildFromMediaFile public to be used in jukebox get() implementation * Return position in seconds and implement offset-skip in seconds * Default offset to 0 * Adding a simple setGain implementation * Prepare for transcoding AAC * WIP: transcode to WAV to use beeps wav decoder. Not done yet. * WIP: out of sheer desparation: convert to MP3 (which works) rather than WAV to troubleshoot issue. * Use FLAC as intermediate format to play Apple AAC * A bit of cleanup * Catching the end-of-stream event for further reactions * Have a trackSwitching goroutine waiting on channel when track ends * Move decoder code into own file. Restructure code a bit * Now with going on to play the next song in the playlist * Adding shuffle feature * Implementing remove action * Cleanup code * Remove templates for ffmpeg mp3 generation. Not needed anymore. * Adding some documentation * Check whether offset into track is in range. Fixing potential remove track bug. Documentation * Make golangci-lint happy: handling return values * Adding test suite and example dummy for playback package * Adding some basic queue tests * Only use Jukebox.Enabled config option * Adding stream closing handling * Pass context.Context to all PlaybackDevice methods * Remove unneeded function * Correct spelling * Reduce visibility of ChildFromMediaFile * Decomplicate action-parsing * Adding simple tempfile-based AAC->FLAC transcoding. No parallel reading and writing yet. * Try to optimize pipe-writing, tempfile-handling and reading. Not done yet. * Do a synchronous copy of the tempfile. Racecondition detected * More debugging statements and fixing the play/pause bug. More work needed * Start the trackSwitcher() with each device once. Return JSON position even if its 0. More debug-output * Moving all track-handling code into own module * Fix typo. Do not pass ctx around when not applicable * WIP: More refactoring, debugging output * Fix nil pointer * Repairing MP3 playback by pinning indirect dependencies: hajimehoshi/go-mp3 and hajimehoshi/oto * Do not forget to cleanup after a skip action * Make resync with master easy * Adding missing mocks * Adding missing error-handling found by linter * Updating github.com/hajimehoshi/oto * Removing duplicate function * Move BEEP-related code into own package * Juggle beep-related code around as preparation for interface access * More refactoring for interface separation * Gather CloseDevice() behind Track interface. * Adding skeleton, draft audio-interface using mpv.io * Adding majority of interface commands using messages to mpv socket. * Adding end-of-stream handling * MPV: start/stop are working * postition is given in float in mpv * Unify Close() and CloseDevice(). Using temp filename for controlling socket * Wait until control-socket shows up. Cleanup socket in Close() * Use canceable command. Rename to Executor * Skipping tracks works now * Now with actually setting the position * Fix regain * Add missing error-handling found by linter * Adding retry mode on time-pos property getter * Remove unneeded code on queue * Putting build-tag beep onto beep files * Remove deprecated call to rand.Seed() "As of Go 1.20 there is no reason to call Seed with a random value. Programs that call Seed with a known value to get a specific sequence of results should use New(NewSource(seed)) to obtain a local random generator." * Using int32 to conform to Subsonic API spec * Fix merge error * Minor style changes * Get username from context --------- Co-authored-by: Deluan <deluan@navidrome.org>
2023-09-10 07:25:22 -08:00
if conf.Server.Jukebox.Enabled {
r.Group(func(r chi.Router) {
r.Use(getPlayer(api.players))
h(r, "jukeboxControl", api.JukeboxControl)
})
} else {
h501(r, "jukeboxControl")
}
// Not Implemented (yet?)
h501(r, "getPodcasts", "getNewestPodcasts", "refreshPodcasts", "createPodcastChannel", "deletePodcastChannel",
"deletePodcastEpisode", "downloadPodcastEpisode")
h501(r, "createUser", "updateUser", "deleteUser", "changePassword")
// Deprecated/Won't implement/Out of scope endpoints
h410(r, "search")
h410(r, "getChatMessages", "addChatMessage")
h410(r, "getVideos", "getVideoInfo", "getCaptions", "hls")
})
return r
}
// Add a Subsonic handler
func h(r chi.Router, path string, f handler) {
hr(r, path, func(_ http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
return f(r)
})
}
// Add a Subsonic handler that requires an http.ResponseWriter (ex: stream, getCoverArt...)
func hr(r chi.Router, path string, f handlerRaw) {
handle := func(w http.ResponseWriter, r *http.Request) {
res, err := f(w, r)
if err != nil {
2020-10-27 11:23:29 -08:00
sendError(w, r, err)
return
}
if r.Context().Err() != nil {
if log.IsGreaterOrEqualTo(log.LevelDebug) {
2022-12-14 06:52:46 -09:00
log.Warn(r.Context(), "Request was interrupted", "endpoint", r.URL.Path, r.Context().Err())
}
return
}
if res != nil {
2020-10-27 11:23:29 -08:00
sendResponse(w, r, res)
}
}
addHandler(r, path, handle)
}
// Add a handler that returns 501 - Not implemented. Used to signal that an endpoint is not implemented yet
func h501(r chi.Router, paths ...string) {
for _, path := range paths {
handle := func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-Control", "no-cache")
w.WriteHeader(http.StatusNotImplemented)
_, _ = w.Write([]byte("This endpoint is not implemented, but may be in future releases"))
}
addHandler(r, path, handle)
}
}
// Add a handler that returns 410 - Gone. Used to signal that an endpoint will not be implemented
func h410(r chi.Router, paths ...string) {
for _, path := range paths {
handle := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusGone)
_, _ = w.Write([]byte("This endpoint will not be implemented"))
}
addHandler(r, path, handle)
}
}
func addHandler(r chi.Router, path string, handle func(w http.ResponseWriter, r *http.Request)) {
r.HandleFunc("/"+path, handle)
r.HandleFunc("/"+path+".view", handle)
}
func mapToSubsonicError(err error) subError {
switch {
case errors.Is(err, errSubsonic): // do nothing
case errors.Is(err, req.ErrMissingParam):
err = newError(responses.ErrorMissingParameter, err.Error())
case errors.Is(err, req.ErrInvalidParam):
err = newError(responses.ErrorGeneric, err.Error())
case errors.Is(err, model.ErrNotFound):
err = newError(responses.ErrorDataNotFound, "data not found")
default:
err = newError(responses.ErrorGeneric, fmt.Sprintf("Internal Server Error: %s", err))
}
var subErr subError
errors.As(err, &subErr)
return subErr
}
2020-10-27 11:23:29 -08:00
func sendError(w http.ResponseWriter, r *http.Request, err error) {
subErr := mapToSubsonicError(err)
response := newResponse()
response.Status = responses.StatusFailed
response.Error = &responses.Error{Code: subErr.code, Message: subErr.Error()}
2020-10-27 11:23:29 -08:00
sendResponse(w, r, response)
}
2020-10-27 11:23:29 -08:00
func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Subsonic) {
p := req.Params(r)
f, _ := p.String("f")
var response []byte
var err error
switch f {
case "json":
w.Header().Set("Content-Type", "application/json")
wrapper := &responses.JsonWrapper{Subsonic: *payload}
response, err = json.Marshal(wrapper)
case "jsonp":
2020-01-08 20:18:55 -09:00
w.Header().Set("Content-Type", "application/javascript")
callback, _ := p.String("callback")
wrapper := &responses.JsonWrapper{Subsonic: *payload}
response, err = json.Marshal(wrapper)
2026-02-08 05:57:30 -09:00
response = fmt.Appendf(nil, "%s(%s)", callback, response)
default:
w.Header().Set("Content-Type", "application/xml")
response, err = xml.Marshal(payload)
}
// This should never happen, but if it does, we need to know
if err != nil {
log.Error(r.Context(), "Error marshalling response", "format", f, err)
sendError(w, r, err)
return
}
if payload.Status == responses.StatusOK {
if log.IsGreaterOrEqualTo(log.LevelTrace) {
2022-12-14 06:52:46 -09:00
log.Debug(r.Context(), "API: Successful response", "endpoint", r.URL.Path, "status", "OK", "body", string(response))
2020-02-01 16:07:15 -09:00
} else {
2022-12-14 06:52:46 -09:00
log.Debug(r.Context(), "API: Successful response", "endpoint", r.URL.Path, "status", "OK")
2020-02-01 16:07:15 -09:00
}
} else {
2022-12-14 06:52:46 -09:00
log.Warn(r.Context(), "API: Failed response", "endpoint", r.URL.Path, "error", payload.Error.Code, "message", payload.Error.Message)
2020-02-01 16:07:15 -09:00
}
statusPointer, ok := r.Context().Value(subsonicErrorPointer).(*int32)
if ok && statusPointer != nil {
if payload.Status == responses.StatusOK {
*statusPointer = 0
} else {
*statusPointer = payload.Error.Code
}
}
2020-04-26 08:35:26 -08:00
if _, err := w.Write(response); err != nil {
2022-12-14 06:52:46 -09:00
log.Error(r, "Error sending response to client", "endpoint", r.URL.Path, "payload", string(response), err)
2020-04-26 08:35:26 -08:00
}
}