Auto reformatted the base, ref #470
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ProtocolVersion.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
@@ -32,4 +33,4 @@ namespace lms::api::subsonic
|
||||
std::string password;
|
||||
ProtocolVersion version;
|
||||
};
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -39,4 +39,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return password;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -22,11 +22,12 @@
|
||||
#include <Wt/Http/Request.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
@@ -84,5 +85,4 @@ namespace lms::api::subsonic
|
||||
|
||||
bool hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param);
|
||||
std::string decodePasswordIfNeeded(const std::string& password);
|
||||
}
|
||||
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -51,5 +51,4 @@ namespace lms::core::stringUtils
|
||||
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lms::core::stringUtils
|
||||
|
||||
@@ -32,11 +32,10 @@ namespace lms::api::subsonic
|
||||
|
||||
static inline constexpr ProtocolVersion defaultServerProtocolVersion{ 1, 16, 0 };
|
||||
static inline constexpr std::string_view serverVersion{ "6" };
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
namespace lms::core::stringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<api::subsonic::ProtocolVersion> readAs(std::string_view str);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <Wt/Http/Request.h>
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "ClientInfo.hpp"
|
||||
#include "ProtocolVersion.hpp"
|
||||
|
||||
@@ -31,7 +32,7 @@ namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
class User;
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -43,7 +44,6 @@ namespace lms::api::subsonic
|
||||
ClientInfo clientInfo;
|
||||
ProtocolVersion serverProtocolVersion;
|
||||
bool enableOpenSubsonic{ true };
|
||||
bool enableDefaultCover{ };
|
||||
bool enableDefaultCover{};
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -141,4 +141,4 @@ namespace lms::core::stringUtils
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
} // namespace lms::core::stringUtils
|
||||
|
||||
@@ -19,16 +19,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/String.hpp"
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/MediaLibraryId.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "database/TrackListId.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
struct RootId {};
|
||||
struct RootId
|
||||
{
|
||||
};
|
||||
|
||||
std::string idToString(db::ArtistId id);
|
||||
std::string idToString(db::MediaLibraryId id);
|
||||
@@ -58,5 +60,4 @@ namespace lms::core::stringUtils
|
||||
|
||||
template<>
|
||||
std::optional<db::TrackListId> readAs(std::string_view str);
|
||||
}
|
||||
|
||||
} // namespace lms::core::stringUtils
|
||||
|
||||
@@ -22,23 +22,29 @@
|
||||
#include <atomic>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "services/auth/IPasswordService.hpp"
|
||||
#include "services/auth/IEnvService.hpp"
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "core/EnumSet.hpp"
|
||||
#include "core/LiteralString.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/LiteralString.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "core/Utils.hpp"
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/auth/IEnvService.hpp"
|
||||
#include "services/auth/IPasswordService.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "ProtocolVersion.hpp"
|
||||
#include "RequestContext.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "SubsonicResponse.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "entrypoints/AlbumSongLists.hpp"
|
||||
#include "entrypoints/Browsing.hpp"
|
||||
#include "entrypoints/Bookmarks.hpp"
|
||||
#include "entrypoints/Browsing.hpp"
|
||||
#include "entrypoints/MediaAnnotation.hpp"
|
||||
#include "entrypoints/MediaLibraryScanning.hpp"
|
||||
#include "entrypoints/MediaRetrieval.hpp"
|
||||
@@ -46,12 +52,6 @@
|
||||
#include "entrypoints/Searching.hpp"
|
||||
#include "entrypoints/System.hpp"
|
||||
#include "entrypoints/UserManagement.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "ProtocolVersion.hpp"
|
||||
#include "RequestContext.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "SubsonicResponse.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -67,10 +67,10 @@ namespace lms::api::subsonic
|
||||
std::unordered_map<std::string, ProtocolVersion> res;
|
||||
|
||||
core::Service<core::IConfig>::get()->visitStrings("api-subsonic-old-server-protocol-clients",
|
||||
[&](std::string_view client)
|
||||
{
|
||||
[&](std::string_view client) {
|
||||
res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 });
|
||||
}, { "DSub" });
|
||||
},
|
||||
{ "DSub" });
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -80,10 +80,10 @@ namespace lms::api::subsonic
|
||||
std::unordered_set<std::string> res;
|
||||
|
||||
core::Service<core::IConfig>::get()->visitStrings("api-open-subsonic-disabled-clients",
|
||||
[&](std::string_view client)
|
||||
{
|
||||
[&](std::string_view client) {
|
||||
res.emplace(std::string{ client });
|
||||
}, { "DSub" });
|
||||
},
|
||||
{ "DSub" });
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -93,23 +93,22 @@ namespace lms::api::subsonic
|
||||
std::unordered_set<std::string> res;
|
||||
|
||||
core::Service<core::IConfig>::get()->visitStrings("api-subsonic-default-cover-clients",
|
||||
[&](std::string_view client)
|
||||
{
|
||||
[&](std::string_view client) {
|
||||
res.emplace(std::string{ client });
|
||||
}, { "DSub", "substreamer" });
|
||||
},
|
||||
{ "DSub", "substreamer" });
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap)
|
||||
{
|
||||
auto censorValue = [](const std::string& type, const std::string& value) -> std::string
|
||||
{
|
||||
if (type == "p" || type == "password")
|
||||
return "*REDACTED*";
|
||||
else
|
||||
return value;
|
||||
};
|
||||
auto censorValue = [](const std::string& type, const std::string& value) -> std::string {
|
||||
if (type == "p" || type == "password")
|
||||
return "*REDACTED*";
|
||||
else
|
||||
return value;
|
||||
};
|
||||
|
||||
std::string res;
|
||||
|
||||
@@ -148,124 +147,122 @@ namespace lms::api::subsonic
|
||||
using CheckImplementedFunc = std::function<void()>;
|
||||
struct RequestEntryPointInfo
|
||||
{
|
||||
RequestHandlerFunc func;
|
||||
core::EnumSet<db::UserType> allowedUserTypes{ db::UserType::DEMO, db::UserType::REGULAR, db::UserType::ADMIN };
|
||||
CheckImplementedFunc checkFunc{};
|
||||
RequestHandlerFunc func;
|
||||
core::EnumSet<db::UserType> allowedUserTypes{ db::UserType::DEMO, db::UserType::REGULAR, db::UserType::ADMIN };
|
||||
CheckImplementedFunc checkFunc{};
|
||||
};
|
||||
|
||||
const std::unordered_map<core::LiteralString, RequestEntryPointInfo, core::LiteralStringHash, core::LiteralStringEqual> requestEntryPoints
|
||||
{
|
||||
const std::unordered_map<core::LiteralString, RequestEntryPointInfo, core::LiteralStringHash, core::LiteralStringEqual> requestEntryPoints{
|
||||
// System
|
||||
{"/ping", {handlePingRequest}},
|
||||
{"/getLicense", {handleGetLicenseRequest}},
|
||||
{"/getOpenSubsonicExtensions", {handleGetOpenSubsonicExtensions}},
|
||||
{ "/ping", { handlePingRequest } },
|
||||
{ "/getLicense", { handleGetLicenseRequest } },
|
||||
{ "/getOpenSubsonicExtensions", { handleGetOpenSubsonicExtensions } },
|
||||
|
||||
// Browsing
|
||||
{"/getMusicFolders", {handleGetMusicFoldersRequest}},
|
||||
{"/getIndexes", {handleGetIndexesRequest}},
|
||||
{"/getMusicDirectory", {handleGetMusicDirectoryRequest}},
|
||||
{"/getGenres", {handleGetGenresRequest}},
|
||||
{"/getArtists", {handleGetArtistsRequest}},
|
||||
{"/getArtist", {handleGetArtistRequest}},
|
||||
{"/getAlbum", {handleGetAlbumRequest}},
|
||||
{"/getSong", {handleGetSongRequest}},
|
||||
{"/getVideos", {handleNotImplemented}},
|
||||
{"/getArtistInfo", {handleGetArtistInfoRequest}},
|
||||
{"/getArtistInfo2", {handleGetArtistInfo2Request}},
|
||||
{"/getAlbumInfo", {handleNotImplemented}},
|
||||
{"/getAlbumInfo2", {handleNotImplemented}},
|
||||
{"/getSimilarSongs", {handleGetSimilarSongsRequest}},
|
||||
{"/getSimilarSongs2", {handleGetSimilarSongs2Request}},
|
||||
{"/getTopSongs", {handleGetTopSongs}},
|
||||
{ "/getMusicFolders", { handleGetMusicFoldersRequest } },
|
||||
{ "/getIndexes", { handleGetIndexesRequest } },
|
||||
{ "/getMusicDirectory", { handleGetMusicDirectoryRequest } },
|
||||
{ "/getGenres", { handleGetGenresRequest } },
|
||||
{ "/getArtists", { handleGetArtistsRequest } },
|
||||
{ "/getArtist", { handleGetArtistRequest } },
|
||||
{ "/getAlbum", { handleGetAlbumRequest } },
|
||||
{ "/getSong", { handleGetSongRequest } },
|
||||
{ "/getVideos", { handleNotImplemented } },
|
||||
{ "/getArtistInfo", { handleGetArtistInfoRequest } },
|
||||
{ "/getArtistInfo2", { handleGetArtistInfo2Request } },
|
||||
{ "/getAlbumInfo", { handleNotImplemented } },
|
||||
{ "/getAlbumInfo2", { handleNotImplemented } },
|
||||
{ "/getSimilarSongs", { handleGetSimilarSongsRequest } },
|
||||
{ "/getSimilarSongs2", { handleGetSimilarSongs2Request } },
|
||||
{ "/getTopSongs", { handleGetTopSongs } },
|
||||
|
||||
// Album/song lists
|
||||
{"/getAlbumList", {handleGetAlbumListRequest}},
|
||||
{"/getAlbumList2", {handleGetAlbumList2Request}},
|
||||
{"/getRandomSongs", {handleGetRandomSongsRequest}},
|
||||
{"/getSongsByGenre", {handleGetSongsByGenreRequest}},
|
||||
{"/getNowPlaying", {handleNotImplemented}},
|
||||
{"/getStarred", {handleGetStarredRequest}},
|
||||
{"/getStarred2", {handleGetStarred2Request}},
|
||||
{ "/getAlbumList", { handleGetAlbumListRequest } },
|
||||
{ "/getAlbumList2", { handleGetAlbumList2Request } },
|
||||
{ "/getRandomSongs", { handleGetRandomSongsRequest } },
|
||||
{ "/getSongsByGenre", { handleGetSongsByGenreRequest } },
|
||||
{ "/getNowPlaying", { handleNotImplemented } },
|
||||
{ "/getStarred", { handleGetStarredRequest } },
|
||||
{ "/getStarred2", { handleGetStarred2Request } },
|
||||
|
||||
// Searching
|
||||
{"/search", {handleNotImplemented}},
|
||||
{"/search2", {handleSearch2Request}},
|
||||
{"/search3", {handleSearch3Request}},
|
||||
{ "/search", { handleNotImplemented } },
|
||||
{ "/search2", { handleSearch2Request } },
|
||||
{ "/search3", { handleSearch3Request } },
|
||||
|
||||
// Playlists
|
||||
{"/getPlaylists", {handleGetPlaylistsRequest}},
|
||||
{"/getPlaylist", {handleGetPlaylistRequest}},
|
||||
{"/createPlaylist", {handleCreatePlaylistRequest}},
|
||||
{"/updatePlaylist", {handleUpdatePlaylistRequest}},
|
||||
{"/deletePlaylist", {handleDeletePlaylistRequest}},
|
||||
{ "/getPlaylists", { handleGetPlaylistsRequest } },
|
||||
{ "/getPlaylist", { handleGetPlaylistRequest } },
|
||||
{ "/createPlaylist", { handleCreatePlaylistRequest } },
|
||||
{ "/updatePlaylist", { handleUpdatePlaylistRequest } },
|
||||
{ "/deletePlaylist", { handleDeletePlaylistRequest } },
|
||||
|
||||
// Media retrieval
|
||||
{"/hls", {handleNotImplemented}},
|
||||
{"/getCaptions", {handleNotImplemented}},
|
||||
{"/getLyrics", {handleNotImplemented}},
|
||||
{"/getAvatar", {handleNotImplemented}},
|
||||
{ "/hls", { handleNotImplemented } },
|
||||
{ "/getCaptions", { handleNotImplemented } },
|
||||
{ "/getLyrics", { handleNotImplemented } },
|
||||
{ "/getAvatar", { handleNotImplemented } },
|
||||
|
||||
// Media annotation
|
||||
{"/star", {handleStarRequest}},
|
||||
{"/unstar", {handleUnstarRequest}},
|
||||
{"/setRating", {handleNotImplemented}},
|
||||
{"/scrobble", {handleScrobble}},
|
||||
{ "/star", { handleStarRequest } },
|
||||
{ "/unstar", { handleUnstarRequest } },
|
||||
{ "/setRating", { handleNotImplemented } },
|
||||
{ "/scrobble", { handleScrobble } },
|
||||
|
||||
// Sharing
|
||||
{"/getShares", {handleNotImplemented}},
|
||||
{"/createShares", {handleNotImplemented}},
|
||||
{"/updateShare", {handleNotImplemented}},
|
||||
{"/deleteShare", {handleNotImplemented}},
|
||||
{ "/getShares", { handleNotImplemented } },
|
||||
{ "/createShares", { handleNotImplemented } },
|
||||
{ "/updateShare", { handleNotImplemented } },
|
||||
{ "/deleteShare", { handleNotImplemented } },
|
||||
|
||||
// Podcast
|
||||
{"/getPodcasts", {handleNotImplemented}},
|
||||
{"/getNewestPodcasts", {handleNotImplemented}},
|
||||
{"/refreshPodcasts", {handleNotImplemented}},
|
||||
{"/createPodcastChannel", {handleNotImplemented}},
|
||||
{"/deletePodcastChannel", {handleNotImplemented}},
|
||||
{"/deletePodcastEpisode", {handleNotImplemented}},
|
||||
{"/downloadPodcastEpisode", {handleNotImplemented}},
|
||||
{ "/getPodcasts", { handleNotImplemented } },
|
||||
{ "/getNewestPodcasts", { handleNotImplemented } },
|
||||
{ "/refreshPodcasts", { handleNotImplemented } },
|
||||
{ "/createPodcastChannel", { handleNotImplemented } },
|
||||
{ "/deletePodcastChannel", { handleNotImplemented } },
|
||||
{ "/deletePodcastEpisode", { handleNotImplemented } },
|
||||
{ "/downloadPodcastEpisode", { handleNotImplemented } },
|
||||
|
||||
// Jukebox
|
||||
{"/jukeboxControl", {handleNotImplemented}},
|
||||
{ "/jukeboxControl", { handleNotImplemented } },
|
||||
|
||||
// Internet radio
|
||||
{"/getInternetRadioStations", {handleNotImplemented}},
|
||||
{"/createInternetRadioStation", {handleNotImplemented}},
|
||||
{"/updateInternetRadioStation", {handleNotImplemented}},
|
||||
{"/deleteInternetRadioStation", {handleNotImplemented}},
|
||||
{ "/getInternetRadioStations", { handleNotImplemented } },
|
||||
{ "/createInternetRadioStation", { handleNotImplemented } },
|
||||
{ "/updateInternetRadioStation", { handleNotImplemented } },
|
||||
{ "/deleteInternetRadioStation", { handleNotImplemented } },
|
||||
|
||||
// Chat
|
||||
{"/getChatMessages", {handleNotImplemented}},
|
||||
{"/addChatMessages", {handleNotImplemented}},
|
||||
{ "/getChatMessages", { handleNotImplemented } },
|
||||
{ "/addChatMessages", { handleNotImplemented } },
|
||||
|
||||
// User management
|
||||
{"/getUser", {handleGetUserRequest}},
|
||||
{"/getUsers", {handleGetUsersRequest, {db::UserType::ADMIN}}},
|
||||
{"/createUser", {handleCreateUserRequest, {db::UserType::ADMIN}, &utils::checkSetPasswordImplemented}},
|
||||
{"/updateUser", {handleUpdateUserRequest, {db::UserType::ADMIN}}},
|
||||
{"/deleteUser", {handleDeleteUserRequest, {db::UserType::ADMIN}}},
|
||||
{"/changePassword", {handleChangePassword, {db::UserType::REGULAR, db::UserType::ADMIN}, &utils::checkSetPasswordImplemented}},
|
||||
{ "/getUser", { handleGetUserRequest } },
|
||||
{ "/getUsers", { handleGetUsersRequest, { db::UserType::ADMIN } } },
|
||||
{ "/createUser", { handleCreateUserRequest, { db::UserType::ADMIN }, &utils::checkSetPasswordImplemented } },
|
||||
{ "/updateUser", { handleUpdateUserRequest, { db::UserType::ADMIN } } },
|
||||
{ "/deleteUser", { handleDeleteUserRequest, { db::UserType::ADMIN } } },
|
||||
{ "/changePassword", { handleChangePassword, { db::UserType::REGULAR, db::UserType::ADMIN }, &utils::checkSetPasswordImplemented } },
|
||||
|
||||
// Bookmarks
|
||||
{"/getBookmarks", {handleGetBookmarks}},
|
||||
{"/createBookmark", {handleCreateBookmark}},
|
||||
{"/deleteBookmark", {handleDeleteBookmark}},
|
||||
{"/getPlayQueue", {handleNotImplemented}},
|
||||
{"/savePlayQueue", {handleNotImplemented}},
|
||||
{ "/getBookmarks", { handleGetBookmarks } },
|
||||
{ "/createBookmark", { handleCreateBookmark } },
|
||||
{ "/deleteBookmark", { handleDeleteBookmark } },
|
||||
{ "/getPlayQueue", { handleNotImplemented } },
|
||||
{ "/savePlayQueue", { handleNotImplemented } },
|
||||
|
||||
// Media library scanning
|
||||
{"/getScanStatus", {Scan::handleGetScanStatus, {db::UserType::ADMIN}}},
|
||||
{"/startScan", {Scan::handleStartScan, {db::UserType::ADMIN}}},
|
||||
{ "/getScanStatus", { Scan::handleGetScanStatus, { db::UserType::ADMIN } } },
|
||||
{ "/startScan", { Scan::handleStartScan, { db::UserType::ADMIN } } },
|
||||
};
|
||||
|
||||
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
|
||||
const std::unordered_map<core::LiteralString, MediaRetrievalHandlerFunc, core::LiteralStringHash, core::LiteralStringEqual> mediaRetrievalHandlers
|
||||
{
|
||||
const std::unordered_map<core::LiteralString, MediaRetrievalHandlerFunc, core::LiteralStringHash, core::LiteralStringEqual> mediaRetrievalHandlers{
|
||||
// Media retrieval
|
||||
{"/download", handleDownload},
|
||||
{"/stream", handleStream},
|
||||
{"/getCoverArt", handleGetCoverArt},
|
||||
{ "/download", handleDownload },
|
||||
{ "/stream", handleStream },
|
||||
{ "/getCoverArt", handleGetCoverArt },
|
||||
};
|
||||
|
||||
struct TLSMonotonicMemoryResourceCleaner
|
||||
@@ -280,7 +277,7 @@ namespace lms::api::subsonic
|
||||
TLSMonotonicMemoryResourceCleaner(const TLSMonotonicMemoryResourceCleaner&) = delete;
|
||||
TLSMonotonicMemoryResourceCleaner& operator=(const TLSMonotonicMemoryResourceCleaner&) = delete;
|
||||
};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SubsonicResource::SubsonicResource(db::Db& db)
|
||||
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
|
||||
@@ -294,8 +291,8 @@ namespace lms::api::subsonic
|
||||
{
|
||||
static std::atomic<std::size_t> curRequestId{};
|
||||
|
||||
const std::size_t requestId{ curRequestId++ };
|
||||
TLSMonotonicMemoryResourceCleaner memoryResourceCleaner;
|
||||
const std::size_t requestId{ curRequestId++ };
|
||||
TLSMonotonicMemoryResourceCleaner memoryResourceCleaner;
|
||||
|
||||
LMS_LOG(API_SUBSONIC, DEBUG, "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap()));
|
||||
|
||||
@@ -327,7 +324,7 @@ namespace lms::api::subsonic
|
||||
const Response resp{ [&] {
|
||||
LMS_SCOPED_TRACE_DETAILED("Subsonic", "HandleRequest");
|
||||
return itEntryPoint->second.func(requestContext);
|
||||
}()};
|
||||
}() };
|
||||
|
||||
{
|
||||
LMS_SCOPED_TRACE_DETAILED("Subsonic", "WriteResponse");
|
||||
@@ -356,8 +353,8 @@ namespace lms::api::subsonic
|
||||
catch (const Error& e)
|
||||
{
|
||||
LMS_LOG(API_SUBSONIC, ERROR, "Error while processing request '" << requestPath << "'"
|
||||
<< ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]"
|
||||
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'");
|
||||
<< ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]"
|
||||
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'");
|
||||
Response resp{ Response::createFailedResponse(protocolVersion, e) };
|
||||
resp.write(response.out(), format);
|
||||
response.setMimeType(std::string{ ResponseFormatToMimeType(format) });
|
||||
@@ -443,7 +440,7 @@ namespace lms::api::subsonic
|
||||
return user->getId();
|
||||
}
|
||||
|
||||
if (auto * authEnvService{ core::Service<auth::IEnvService>::get() })
|
||||
if (auto* authEnvService{ core::Service<auth::IEnvService>::get() })
|
||||
{
|
||||
const auto checkResult{ authEnvService->processRequest(request) };
|
||||
if (checkResult.state != auth::IEnvService::CheckResult::State::Granted)
|
||||
@@ -451,7 +448,7 @@ namespace lms::api::subsonic
|
||||
|
||||
return *checkResult.userId;
|
||||
}
|
||||
else if (auto * authPasswordService{ core::Service<auth::IPasswordService>::get() })
|
||||
else if (auto* authPasswordService{ core::Service<auth::IPasswordService>::get() })
|
||||
{
|
||||
const auto checkResult{ authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), clientInfo.user, clientInfo.password) };
|
||||
|
||||
@@ -471,4 +468,3 @@ namespace lms::api::subsonic
|
||||
}
|
||||
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
|
||||
@@ -19,14 +19,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <Wt/WResource.h>
|
||||
#include <Wt/Http/Response.h>
|
||||
#include <Wt/WResource.h>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
#include "database/UserId.hpp"
|
||||
|
||||
#include "ClientInfo.hpp"
|
||||
#include "RequestContext.hpp"
|
||||
|
||||
@@ -37,26 +38,24 @@ namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
|
||||
class SubsonicResource final : public Wt::WResource
|
||||
{
|
||||
public:
|
||||
SubsonicResource(db::Db& db);
|
||||
public:
|
||||
SubsonicResource(db::Db& db);
|
||||
|
||||
private:
|
||||
void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override;
|
||||
ProtocolVersion getServerProtocolVersion(const std::string& clientName) const;
|
||||
private:
|
||||
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
|
||||
ProtocolVersion getServerProtocolVersion(const std::string& clientName) const;
|
||||
|
||||
static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server);
|
||||
ClientInfo getClientInfo(const Wt::Http::Request& request);
|
||||
RequestContext buildRequestContext(const Wt::Http::Request& request);
|
||||
db::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo);
|
||||
static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server);
|
||||
ClientInfo getClientInfo(const Wt::Http::Request& request);
|
||||
RequestContext buildRequestContext(const Wt::Http::Request& request);
|
||||
db::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo);
|
||||
|
||||
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
|
||||
const std::unordered_set<std::string> _openSubsonicDisabledClients;
|
||||
const std::unordered_set<std::string> _defaultCoverClients;
|
||||
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
|
||||
const std::unordered_set<std::string> _openSubsonicDisabledClients;
|
||||
const std::unordered_set<std::string> _defaultCoverClients;
|
||||
|
||||
db::Db& _db;
|
||||
db::Db& _db;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "ProtocolVersion.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
@@ -34,8 +36,10 @@ namespace lms::api::subsonic
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ResponseFormat::xml: return "text/xml";
|
||||
case ResponseFormat::json: return "application/json";
|
||||
case ResponseFormat::xml:
|
||||
return "text/xml";
|
||||
case ResponseFormat::json:
|
||||
return "application/json";
|
||||
}
|
||||
|
||||
return "";
|
||||
@@ -92,7 +96,7 @@ namespace lms::api::subsonic
|
||||
assert(!_children.contains(key));
|
||||
auto& values{ _childrenValues[key] };
|
||||
values.emplace_back(string{ value });
|
||||
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) {return value.index() == values.front().index();}));
|
||||
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) { return value.index() == values.front().index(); }));
|
||||
}
|
||||
|
||||
void Response::Node::addArrayValue(Key key, long long value)
|
||||
@@ -100,7 +104,7 @@ namespace lms::api::subsonic
|
||||
assert(!_value);
|
||||
auto& values{ _childrenValues[key] };
|
||||
values.emplace_back(value);
|
||||
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) {return value.index() == values.front().index();}));
|
||||
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) { return value.index() == values.front().index(); }));
|
||||
}
|
||||
|
||||
Response::Node& Response::Node::createChild(Key key)
|
||||
@@ -186,60 +190,58 @@ namespace lms::api::subsonic
|
||||
|
||||
void Response::writeXML(std::ostream& os) const
|
||||
{
|
||||
std::function<boost::property_tree::ptree(const Node&)> nodeToPropertyTree = [&](const Node& node)
|
||||
std::function<boost::property_tree::ptree(const Node&)> nodeToPropertyTree = [&](const Node& node) {
|
||||
boost::property_tree::ptree res;
|
||||
|
||||
for (const auto& [key, value] : node._attributes)
|
||||
{
|
||||
if (std::holds_alternative<Node::string>(value))
|
||||
res.put("<xmlattr>." + std::string{ key.str() }, std::get<Node::string>(value));
|
||||
else if (std::holds_alternative<bool>(value))
|
||||
res.put("<xmlattr>." + std::string{ key.str() }, std::get<bool>(value));
|
||||
else if (std::holds_alternative<float>(value))
|
||||
res.put("<xmlattr>." + std::string{ key.str() }, std::get<float>(value));
|
||||
else if (std::holds_alternative<long long>(value))
|
||||
res.put("<xmlattr>." + std::string{ key.str() }, std::get<long long>(value));
|
||||
}
|
||||
|
||||
auto valueToPropertyTree = [](const Node::ValueType& value) {
|
||||
boost::property_tree::ptree res;
|
||||
|
||||
for (const auto& [key, value] : node._attributes)
|
||||
{
|
||||
if (std::holds_alternative<Node::string>(value))
|
||||
res.put("<xmlattr>." + std::string{ key.str() }, std::get<Node::string>(value));
|
||||
else if (std::holds_alternative<bool>(value))
|
||||
res.put("<xmlattr>." + std::string{ key.str() }, std::get<bool>(value));
|
||||
else if (std::holds_alternative<float>(value))
|
||||
res.put("<xmlattr>." + std::string{ key.str() }, std::get<float>(value));
|
||||
else if (std::holds_alternative<long long>(value))
|
||||
res.put("<xmlattr>." + std::string{ key.str() }, std::get<long long>(value));
|
||||
}
|
||||
|
||||
auto valueToPropertyTree = [](const Node::ValueType& value)
|
||||
{
|
||||
boost::property_tree::ptree res;
|
||||
std::visit([&](const auto& rawValue)
|
||||
{
|
||||
res.put_value(rawValue);
|
||||
}, value);
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
if (node._value)
|
||||
{
|
||||
res = valueToPropertyTree(*node._value);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& [key, childNode] : node._children)
|
||||
{
|
||||
res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode));
|
||||
}
|
||||
|
||||
for (const auto& [key, childArrayNodes] : node._childrenArrays)
|
||||
{
|
||||
for (const Node& childNode : childArrayNodes)
|
||||
res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode));
|
||||
}
|
||||
|
||||
for (const auto& [key, childArrayValues] : node._childrenValues)
|
||||
{
|
||||
for (const Response::Node::ValueType& value : childArrayValues)
|
||||
res.add_child(std::string{ key.str() }, valueToPropertyTree(value));
|
||||
}
|
||||
}
|
||||
std::visit([&](const auto& rawValue) {
|
||||
res.put_value(rawValue);
|
||||
},
|
||||
value);
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
if (node._value)
|
||||
{
|
||||
res = valueToPropertyTree(*node._value);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& [key, childNode] : node._children)
|
||||
{
|
||||
res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode));
|
||||
}
|
||||
|
||||
for (const auto& [key, childArrayNodes] : node._childrenArrays)
|
||||
{
|
||||
for (const Node& childNode : childArrayNodes)
|
||||
res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode));
|
||||
}
|
||||
|
||||
for (const auto& [key, childArrayValues] : node._childrenValues)
|
||||
{
|
||||
for (const Response::Node::ValueType& value : childArrayValues)
|
||||
res.add_child(std::string{ key.str() }, valueToPropertyTree(value));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
const boost::property_tree::ptree root{ nodeToPropertyTree(_root) };
|
||||
boost::property_tree::write_xml(os, root);
|
||||
}
|
||||
@@ -376,4 +378,4 @@ namespace lms::api::subsonic
|
||||
serializer.serializeNode(os, _root);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "core/LiteralString.hpp"
|
||||
|
||||
#include "RequestContext.hpp"
|
||||
#include "SubsonicResponseAllocator.hpp"
|
||||
|
||||
@@ -57,7 +58,8 @@ namespace lms::api::subsonic
|
||||
RequestedDataNotFound = 70,
|
||||
};
|
||||
|
||||
Error(Code code) : _code{ code } {}
|
||||
Error(Code code)
|
||||
: _code{ code } {}
|
||||
|
||||
virtual std::string getMessage() const = 0;
|
||||
|
||||
@@ -70,7 +72,8 @@ namespace lms::api::subsonic
|
||||
class GenericError : public Error
|
||||
{
|
||||
public:
|
||||
GenericError() : Error{ Code::Generic } {}
|
||||
GenericError()
|
||||
: Error{ Code::Generic } {}
|
||||
};
|
||||
|
||||
class RequiredParameterMissingError : public Error
|
||||
@@ -79,7 +82,8 @@ namespace lms::api::subsonic
|
||||
RequiredParameterMissingError(std::string_view param)
|
||||
: Error{ Code::RequiredParameterMissing }
|
||||
, _param{ param }
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Required parameter '" + _param + "' is missing."; }
|
||||
@@ -89,7 +93,9 @@ namespace lms::api::subsonic
|
||||
class ClientMustUpgradeError : public Error
|
||||
{
|
||||
public:
|
||||
ClientMustUpgradeError() : Error{ Code::ClientMustUpgrade } {}
|
||||
ClientMustUpgradeError()
|
||||
: Error{ Code::ClientMustUpgrade } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; }
|
||||
};
|
||||
@@ -97,7 +103,9 @@ namespace lms::api::subsonic
|
||||
class ServerMustUpgradeError : public Error
|
||||
{
|
||||
public:
|
||||
ServerMustUpgradeError() : Error{ Code::ServerMustUpgrade } {}
|
||||
ServerMustUpgradeError()
|
||||
: Error{ Code::ServerMustUpgrade } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; }
|
||||
};
|
||||
@@ -105,7 +113,9 @@ namespace lms::api::subsonic
|
||||
class WrongUsernameOrPasswordError : public Error
|
||||
{
|
||||
public:
|
||||
WrongUsernameOrPasswordError() : Error{ Code::WrongUsernameOrPassword } {}
|
||||
WrongUsernameOrPasswordError()
|
||||
: Error{ Code::WrongUsernameOrPassword } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Wrong username or password."; }
|
||||
};
|
||||
@@ -113,7 +123,9 @@ namespace lms::api::subsonic
|
||||
class TokenAuthenticationNotSupportedForLDAPUsersError : public Error
|
||||
{
|
||||
public:
|
||||
TokenAuthenticationNotSupportedForLDAPUsersError() : Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {}
|
||||
TokenAuthenticationNotSupportedForLDAPUsersError()
|
||||
: Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Token authentication not supported for LDAP users."; }
|
||||
};
|
||||
@@ -121,7 +133,9 @@ namespace lms::api::subsonic
|
||||
class UserNotAuthorizedError : public Error
|
||||
{
|
||||
public:
|
||||
UserNotAuthorizedError() : Error{ Code::UserNotAuthorized } {}
|
||||
UserNotAuthorizedError()
|
||||
: Error{ Code::UserNotAuthorized } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "User is not authorized for the given operation."; }
|
||||
};
|
||||
@@ -129,7 +143,9 @@ namespace lms::api::subsonic
|
||||
class RequestedDataNotFoundError : public Error
|
||||
{
|
||||
public:
|
||||
RequestedDataNotFoundError() : Error{ Code::RequestedDataNotFound } {}
|
||||
RequestedDataNotFoundError()
|
||||
: Error{ Code::RequestedDataNotFound } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "The requested data was not found."; }
|
||||
};
|
||||
@@ -137,7 +153,9 @@ namespace lms::api::subsonic
|
||||
class InternalErrorGenericError : public GenericError
|
||||
{
|
||||
public:
|
||||
InternalErrorGenericError(const std::string& message) : _message{ message } {}
|
||||
InternalErrorGenericError(const std::string& message)
|
||||
: _message{ message } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Internal error: " + _message; }
|
||||
const std::string _message;
|
||||
@@ -181,7 +199,8 @@ namespace lms::api::subsonic
|
||||
class BadParameterGenericError : public GenericError
|
||||
{
|
||||
public:
|
||||
BadParameterGenericError(const std::string& parameterName) : _parameterName{ parameterName } {}
|
||||
BadParameterGenericError(const std::string& parameterName)
|
||||
: _parameterName{ parameterName } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; }
|
||||
@@ -192,7 +211,9 @@ namespace lms::api::subsonic
|
||||
class ParameterValueTooHighGenericError : public GenericError
|
||||
{
|
||||
public:
|
||||
ParameterValueTooHighGenericError(std::string_view parameterName, std::size_t max) : _parameterName{ parameterName }, _max{ max } {}
|
||||
ParameterValueTooHighGenericError(std::string_view parameterName, std::size_t max)
|
||||
: _parameterName{ parameterName }
|
||||
, _max{ max } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value (max is " + std::to_string(_max) + ")"; }
|
||||
@@ -211,7 +232,7 @@ namespace lms::api::subsonic
|
||||
|
||||
void setAttribute(Key key, std::string_view value);
|
||||
|
||||
template <typename T, std::enable_if_t<std::is_arithmetic<T>::value>* = nullptr>
|
||||
template<typename T, std::enable_if_t<std::is_arithmetic<T>::value>* = nullptr>
|
||||
void setAttribute(Key key, T value)
|
||||
{
|
||||
if constexpr (std::is_same<bool, T>::value)
|
||||
@@ -242,10 +263,10 @@ namespace lms::api::subsonic
|
||||
|
||||
friend class Response;
|
||||
|
||||
template <typename Key, typename Value>
|
||||
using map = std::map< Key, Value, std::less<Key>, ResponseAllocator<std::pair<const Key, Value>>>;
|
||||
template<typename Key, typename Value>
|
||||
using map = std::map<Key, Value, std::less<Key>, ResponseAllocator<std::pair<const Key, Value>>>;
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
using vector = std::vector<T, ResponseAllocator<T>>;
|
||||
|
||||
using string = std::basic_string<char, std::char_traits<char>, ResponseAllocator<char>>;
|
||||
@@ -280,7 +301,7 @@ namespace lms::api::subsonic
|
||||
|
||||
class JsonSerializer
|
||||
{
|
||||
public:
|
||||
public:
|
||||
void serializeNode(std::ostream& os, const Node& node);
|
||||
void serializeValue(std::ostream& os, const Node::ValueType& value);
|
||||
void serializeEscapedString(std::ostream&, std::string_view str);
|
||||
@@ -293,5 +314,4 @@ namespace lms::api::subsonic
|
||||
Node _root;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
// Stateless allocator that uses a shared MemoryResource
|
||||
template <typename MemoryResource, typename T>
|
||||
template<typename MemoryResource, typename T>
|
||||
class Allocator
|
||||
{
|
||||
public:
|
||||
@@ -37,10 +37,12 @@ namespace lms::api::subsonic
|
||||
|
||||
constexpr Allocator() noexcept = default;
|
||||
|
||||
template <typename U>
|
||||
constexpr Allocator(const Allocator<MemoryResource, U>&) noexcept {}
|
||||
template<typename U>
|
||||
constexpr Allocator(const Allocator<MemoryResource, U>&) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
template<typename V>
|
||||
struct rebind
|
||||
{
|
||||
using other = Allocator<MemoryResource, V>;
|
||||
@@ -59,17 +61,17 @@ namespace lms::api::subsonic
|
||||
};
|
||||
|
||||
template<class MemoryResource, class T, class U>
|
||||
bool operator==(const Allocator <MemoryResource, T>&, const Allocator <MemoryResource, U>&)
|
||||
bool operator==(const Allocator<MemoryResource, T>&, const Allocator<MemoryResource, U>&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class MemoryResource, class T, class U>
|
||||
bool operator!=(const Allocator <MemoryResource, T>&, const Allocator <MemoryResource, U>&)
|
||||
bool operator!=(const Allocator<MemoryResource, T>&, const Allocator<MemoryResource, U>&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
template<typename T>
|
||||
using ResponseAllocator = Allocator<TLSMonotonicMemoryResource, T>;
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -18,10 +18,10 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <list>
|
||||
|
||||
namespace lms::api::subsonic
|
||||
@@ -55,7 +55,7 @@ namespace lms::api::subsonic
|
||||
throw std::bad_alloc{};
|
||||
|
||||
assert(currentAddrAligned >= &_currentBlock->front());
|
||||
|
||||
|
||||
std::byte* res{ currentAddrAligned };
|
||||
_currentAddr = currentAddrAligned + byteCount;
|
||||
|
||||
@@ -100,4 +100,4 @@ namespace lms::api::subsonic
|
||||
BlockType* _currentBlock{};
|
||||
std::byte* _currentAddr{};
|
||||
};
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "services/auth/IPasswordService.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::api::subsonic::utils
|
||||
@@ -38,4 +39,4 @@ namespace lms::api::subsonic::utils
|
||||
return core::stringUtils::replaceInString(name, "/", "_");
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace lms::api::subsonic::utils
|
||||
@@ -26,4 +26,4 @@ namespace lms::api::subsonic::utils
|
||||
{
|
||||
void checkSetPasswordImplemented();
|
||||
std::string makeNameFilesystemCompatible(std::string_view name);
|
||||
}
|
||||
} // namespace lms::api::subsonic::utils
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "AlbumSongLists.hpp"
|
||||
|
||||
#include "core/Service.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/Release.hpp"
|
||||
@@ -27,12 +28,12 @@
|
||||
#include "database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "responses/Album.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
#include "responses/Song.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -245,10 +246,9 @@ namespace lms::api::subsonic
|
||||
params.setRange(Range{ 0, size });
|
||||
params.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track)
|
||||
{
|
||||
randomSongsNode.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
});
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
|
||||
randomSongsNode.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -262,7 +262,7 @@ namespace lms::api::subsonic
|
||||
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
|
||||
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(10) };
|
||||
if (count > defaultMaxCountSize)
|
||||
throw ParameterValueTooHighGenericError{"count", defaultMaxCountSize};
|
||||
throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize };
|
||||
|
||||
std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) };
|
||||
|
||||
@@ -284,10 +284,9 @@ namespace lms::api::subsonic
|
||||
params.setRange(Range{ offset, count });
|
||||
params.setMediaLibrary(mediaLibrary);
|
||||
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track)
|
||||
{
|
||||
songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
});
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
|
||||
songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -302,4 +301,4 @@ namespace lms::api::subsonic
|
||||
return handleGetStarredRequestCommon(context, true /* id3 */);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -30,4 +30,4 @@ namespace lms::api::subsonic
|
||||
Response handleGetSongsByGenreRequest(RequestContext& context);
|
||||
Response handleGetStarredRequest(RequestContext& context);
|
||||
Response handleGetStarred2Request(RequestContext& context);
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
#include "Bookmarks.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackBookmark.hpp"
|
||||
#include "responses/Bookmark.hpp"
|
||||
#include "responses/Song.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "responses/Bookmark.hpp"
|
||||
#include "responses/Song.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -92,4 +93,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -27,4 +27,4 @@ namespace lms::api::subsonic
|
||||
Response handleGetBookmarks(RequestContext& context);
|
||||
Response handleCreateBookmark(RequestContext& context);
|
||||
Response handleDeleteBookmark(RequestContext& context);
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -19,31 +19,32 @@
|
||||
|
||||
#include "Browsing.hpp"
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Random.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/MediaLibrary.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/recommendation/IRecommendationService.hpp"
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Random.hpp"
|
||||
#include "core/Service.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "responses/Album.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
#include "responses/Genre.hpp"
|
||||
#include "responses/Song.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
using namespace db;
|
||||
|
||||
static const unsigned long long reportedDummyDateULong{ 946684800000ULL }; // 2000-01-01T00:00:00 UTC
|
||||
static const unsigned long long reportedDummyDateULong{ 946684800000ULL }; // 2000-01-01T00:00:00 UTC
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -70,7 +71,7 @@ namespace lms::api::subsonic
|
||||
artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->getAsString());
|
||||
}
|
||||
|
||||
auto similarArtistsId{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(id, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, count) };
|
||||
auto similarArtistsId{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(id, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, count) };
|
||||
|
||||
{
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
@@ -170,7 +171,7 @@ namespace lms::api::subsonic
|
||||
{
|
||||
// API says: "Returns a random collection of songs from the given artist and similar artists"
|
||||
const std::size_t similarArtistCount{ count / 5 };
|
||||
std::vector<ArtistId> artistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, similarArtistCount) };
|
||||
std::vector<ArtistId> artistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, similarArtistCount) };
|
||||
artistIds.push_back(artistId);
|
||||
|
||||
const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 };
|
||||
@@ -265,7 +266,7 @@ namespace lms::api::subsonic
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Response handleGetMusicFoldersRequest(RequestContext& context)
|
||||
{
|
||||
@@ -273,13 +274,12 @@ namespace lms::api::subsonic
|
||||
Response::Node& musicFoldersNode{ response.createNode("musicFolders") };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library)
|
||||
{
|
||||
Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") };
|
||||
MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library) {
|
||||
Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") };
|
||||
|
||||
musicFolderNode.setAttribute("id", idToString(library->getId()));
|
||||
musicFolderNode.setAttribute("name", library->getName());
|
||||
});
|
||||
musicFolderNode.setAttribute("id", idToString(library->getId()));
|
||||
musicFolderNode.setAttribute("name", library->getName());
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -310,10 +310,9 @@ namespace lms::api::subsonic
|
||||
directoryNode.setAttribute("name", "Music");
|
||||
|
||||
// TODO: this does not scale when a lot of artists are present
|
||||
Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::SortName), [&](const Artist::pointer& artist)
|
||||
{
|
||||
directoryNode.addArrayChild("child", createArtistNode(context, artist, context.user, false /* no id3 */));
|
||||
});
|
||||
Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::SortName), [&](const Artist::pointer& artist) {
|
||||
directoryNode.addArrayChild("child", createArtistNode(context, artist, context.user, false /* no id3 */));
|
||||
});
|
||||
}
|
||||
else if (artistId)
|
||||
{
|
||||
@@ -325,10 +324,9 @@ namespace lms::api::subsonic
|
||||
|
||||
directoryNode.setAttribute("name", utils::makeNameFilesystemCompatible(artist->getName()));
|
||||
|
||||
Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release)
|
||||
{
|
||||
directoryNode.addArrayChild("child", createAlbumNode(context, release, context.user, false /* no id3 */));
|
||||
});
|
||||
Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release) {
|
||||
directoryNode.addArrayChild("child", createAlbumNode(context, release, context.user, false /* no id3 */));
|
||||
});
|
||||
}
|
||||
else if (releaseId)
|
||||
{
|
||||
@@ -340,10 +338,9 @@ namespace lms::api::subsonic
|
||||
|
||||
directoryNode.setAttribute("name", utils::makeNameFilesystemCompatible(release->getName()));
|
||||
|
||||
Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track)
|
||||
{
|
||||
directoryNode.addArrayChild("child", createSongNode(context, track, context.user));
|
||||
});
|
||||
Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track) {
|
||||
directoryNode.addArrayChild("child", createSongNode(context, track, context.user));
|
||||
});
|
||||
}
|
||||
else
|
||||
throw BadParameterGenericError{ "id" };
|
||||
@@ -390,7 +387,7 @@ namespace lms::api::subsonic
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response::Node artistNode{ createArtistNode(context, artist, context.user, true /* id3 */) };
|
||||
|
||||
const auto releases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(artist->getId())) };
|
||||
const auto releases{ Release::find(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
|
||||
for (const Release::pointer& release : releases.results)
|
||||
artistNode.addArrayChild("album", createAlbumNode(context, release, context.user, true /* id3 */));
|
||||
|
||||
@@ -490,4 +487,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -37,4 +37,4 @@ namespace lms::api::subsonic
|
||||
Response handleGetSimilarSongsRequest(RequestContext& context);
|
||||
Response handleGetSimilarSongs2Request(RequestContext& context);
|
||||
Response handleGetTopSongs(RequestContext& context);
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -21,13 +21,14 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "core/Service.hpp"
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
#include "core/Service.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
@@ -55,7 +56,7 @@ namespace lms::api::subsonic
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Response handleStarRequest(RequestContext& context)
|
||||
{
|
||||
@@ -119,11 +120,11 @@ namespace lms::api::subsonic
|
||||
{
|
||||
const TrackId trackId{ ids[i] };
|
||||
const unsigned long time{ times[i] };
|
||||
core::Service<scrobbling::IScrobblingService>::get()->addTimedListen({ {context.user->getId(), trackId}, Wt::WDateTime::fromTime_t(static_cast<std::time_t>(time / 1000)) });
|
||||
core::Service<scrobbling::IScrobblingService>::get()->addTimedListen({ { context.user->getId(), trackId }, Wt::WDateTime::fromTime_t(static_cast<std::time_t>(time / 1000)) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -27,4 +27,4 @@ namespace lms::api::subsonic
|
||||
Response handleStarRequest(RequestContext& context);
|
||||
Response handleUnstarRequest(RequestContext& context);
|
||||
Response handleScrobble(RequestContext& context);
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
#include "MediaLibraryScanning.hpp"
|
||||
|
||||
#include "services/scanner/IScannerService.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "services/scanner/IScannerService.hpp"
|
||||
|
||||
namespace lms::api::subsonic::Scan
|
||||
{
|
||||
@@ -47,7 +47,7 @@ namespace lms::api::subsonic::Scan
|
||||
|
||||
return statusResponse;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Response handleGetScanStatus(RequestContext& context)
|
||||
{
|
||||
@@ -66,5 +66,4 @@ namespace lms::api::subsonic::Scan
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lms::api::subsonic::Scan
|
||||
|
||||
@@ -26,5 +26,4 @@ namespace lms::api::subsonic::Scan
|
||||
{
|
||||
Response handleGetScanStatus(RequestContext& context);
|
||||
Response handleStartScan(RequestContext& context);
|
||||
}
|
||||
|
||||
} // namespace lms::api::subsonic::Scan
|
||||
|
||||
@@ -24,15 +24,16 @@
|
||||
#include "av/TranscodingParameters.hpp"
|
||||
#include "av/TranscodingResourceHandlerCreator.hpp"
|
||||
#include "av/Types.hpp"
|
||||
#include "services/cover/ICoverService.hpp"
|
||||
#include "core/FileResourceHandlerCreator.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/IResourceHandler.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "core/Utils.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "core/IResourceHandler.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/FileResourceHandlerCreator.hpp"
|
||||
#include "core/Utils.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "services/cover/ICoverService.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
@@ -40,15 +41,15 @@ namespace lms::api::subsonic
|
||||
{
|
||||
using namespace db;
|
||||
|
||||
namespace
|
||||
namespace
|
||||
{
|
||||
std::optional<av::transcoding::OutputFormat> subsonicStreamFormatToAvOutputFormat(std::string_view format)
|
||||
{
|
||||
for (const auto& [str, avFormat] : std::initializer_list<std::pair<std::string_view, av::transcoding::OutputFormat>>{
|
||||
{"mp3", av::transcoding::OutputFormat::MP3},
|
||||
{"opus", av::transcoding::OutputFormat::OGG_OPUS},
|
||||
{"vorbis", av::transcoding::OutputFormat::OGG_VORBIS},
|
||||
})
|
||||
{ "mp3", av::transcoding::OutputFormat::MP3 },
|
||||
{ "opus", av::transcoding::OutputFormat::OGG_OPUS },
|
||||
{ "vorbis", av::transcoding::OutputFormat::OGG_VORBIS },
|
||||
})
|
||||
{
|
||||
if (core::stringUtils::stringCaseInsensitiveEqual(str, format))
|
||||
return avFormat;
|
||||
@@ -60,11 +61,16 @@ namespace lms::api::subsonic
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case db::TranscodingOutputFormat::MP3: return av::transcoding::OutputFormat::MP3;
|
||||
case db::TranscodingOutputFormat::OGG_OPUS: return av::transcoding::OutputFormat::OGG_OPUS;
|
||||
case db::TranscodingOutputFormat::MATROSKA_OPUS: return av::transcoding::OutputFormat::MATROSKA_OPUS;
|
||||
case db::TranscodingOutputFormat::OGG_VORBIS: return av::transcoding::OutputFormat::OGG_VORBIS;
|
||||
case db::TranscodingOutputFormat::WEBM_VORBIS: return av::transcoding::OutputFormat::WEBM_VORBIS;
|
||||
case db::TranscodingOutputFormat::MP3:
|
||||
return av::transcoding::OutputFormat::MP3;
|
||||
case db::TranscodingOutputFormat::OGG_OPUS:
|
||||
return av::transcoding::OutputFormat::OGG_OPUS;
|
||||
case db::TranscodingOutputFormat::MATROSKA_OPUS:
|
||||
return av::transcoding::OutputFormat::MATROSKA_OPUS;
|
||||
case db::TranscodingOutputFormat::OGG_VORBIS:
|
||||
return av::transcoding::OutputFormat::OGG_VORBIS;
|
||||
case db::TranscodingOutputFormat::WEBM_VORBIS:
|
||||
return av::transcoding::OutputFormat::WEBM_VORBIS;
|
||||
}
|
||||
return av::transcoding::OutputFormat::OGG_OPUS;
|
||||
}
|
||||
@@ -147,7 +153,7 @@ namespace lms::api::subsonic
|
||||
requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat());
|
||||
}
|
||||
|
||||
if (!requestedFormat && (maxBitRate == 0 || track->getBitrate() <= maxBitRate ))
|
||||
if (!requestedFormat && (maxBitRate == 0 || track->getBitrate() <= maxBitRate))
|
||||
{
|
||||
LMS_LOG(API_SUBSONIC, DEBUG, "File's bitrate is compatible with parameters => no transcoding");
|
||||
return parameters; // no transcoding needed
|
||||
@@ -166,7 +172,7 @@ namespace lms::api::subsonic
|
||||
}
|
||||
bitrate = maxBitRate;
|
||||
}
|
||||
|
||||
|
||||
if (!requestedFormat)
|
||||
requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat());
|
||||
if (!bitrate)
|
||||
@@ -181,7 +187,7 @@ namespace lms::api::subsonic
|
||||
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response)
|
||||
{
|
||||
|
||||
@@ -29,5 +29,4 @@ namespace lms::api::subsonic
|
||||
void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response);
|
||||
void handleStream(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response);
|
||||
void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response);
|
||||
}
|
||||
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -23,10 +23,11 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "responses/Playlist.hpp"
|
||||
#include "responses/Song.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "responses/Playlist.hpp"
|
||||
#include "responses/Song.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -200,4 +201,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -29,4 +29,4 @@ namespace lms::api::subsonic
|
||||
Response handleCreatePlaylistRequest(RequestContext& context);
|
||||
Response handleUpdatePlaylistRequest(RequestContext& context);
|
||||
Response handleDeletePlaylistRequest(RequestContext& context);
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,8 +20,8 @@
|
||||
#include "Searching.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "core/Random.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
@@ -29,11 +29,12 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "responses/Album.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
#include "responses/Song.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -44,7 +45,7 @@ namespace lms::api::subsonic
|
||||
// Search endpoints can be used to scan/sync the database
|
||||
// This class is used to keep track of the current scans, in order to retrieve the last objectId
|
||||
// to speed up the query of the following range (avoid the 'offset' cost)
|
||||
template <typename ObjectId>
|
||||
template<typename ObjectId>
|
||||
class ScanTracker
|
||||
{
|
||||
public:
|
||||
@@ -71,7 +72,7 @@ namespace lms::api::subsonic
|
||||
};
|
||||
|
||||
static constexpr std::size_t maxScanCount{ 50 };
|
||||
static constexpr ClockType::duration maxEntryDuration{ std::chrono::seconds{30} };
|
||||
static constexpr ClockType::duration maxEntryDuration{ std::chrono::seconds{ 30 } };
|
||||
|
||||
std::mutex _mutex;
|
||||
std::map<ScanInfo, Entry> _ongoingScans;
|
||||
@@ -126,20 +127,18 @@ namespace lms::api::subsonic
|
||||
const std::size_t artistOffset{ getParameterAs<std::size_t>(context.parameters, "artistOffset").value_or(0) };
|
||||
|
||||
ArtistId lastRetrievedId;
|
||||
auto findArtists{ [&]
|
||||
{
|
||||
Artist::FindParameters params;
|
||||
params.setKeywords(keywords);
|
||||
params.setRange(Range{ artistOffset, artistCount });
|
||||
params.setMediaLibrary(mediaLibrary);
|
||||
params.setSortMethod(ArtistSortMethod::Id); // must be consistent with both methods
|
||||
auto findArtists{ [&] {
|
||||
Artist::FindParameters params;
|
||||
params.setKeywords(keywords);
|
||||
params.setRange(Range{ artistOffset, artistCount });
|
||||
params.setMediaLibrary(mediaLibrary);
|
||||
params.setSortMethod(ArtistSortMethod::Id); // must be consistent with both methods
|
||||
|
||||
Artist::find(context.dbSession, params, [&](const Artist::pointer& artist)
|
||||
{
|
||||
searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3));
|
||||
lastRetrievedId = artist->getId();
|
||||
});
|
||||
} };
|
||||
Artist::find(context.dbSession, params, [&](const Artist::pointer& artist) {
|
||||
searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3));
|
||||
lastRetrievedId = artist->getId();
|
||||
});
|
||||
} };
|
||||
|
||||
if (!keywords.empty())
|
||||
{
|
||||
@@ -147,8 +146,7 @@ namespace lms::api::subsonic
|
||||
}
|
||||
else
|
||||
{
|
||||
ScanTracker<ArtistId>::ScanInfo scanInfo
|
||||
{
|
||||
ScanTracker<ArtistId>::ScanInfo scanInfo{
|
||||
.clientAddress = context.clientInfo.ipAddress,
|
||||
.clientName = context.clientInfo.name,
|
||||
.userName = context.clientInfo.user,
|
||||
@@ -158,10 +156,11 @@ namespace lms::api::subsonic
|
||||
|
||||
if (ArtistId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
|
||||
{
|
||||
Artist::find(context.dbSession, cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist)
|
||||
{
|
||||
Artist::find(
|
||||
context.dbSession, cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist) {
|
||||
searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3));
|
||||
}, mediaLibrary);
|
||||
},
|
||||
mediaLibrary);
|
||||
lastRetrievedId = cachedLastRetrievedId;
|
||||
}
|
||||
else
|
||||
@@ -192,19 +191,17 @@ namespace lms::api::subsonic
|
||||
|
||||
ReleaseId lastRetrievedId;
|
||||
|
||||
auto findReleases{ [&]
|
||||
{
|
||||
auto findReleases{ [&] {
|
||||
Release::FindParameters params;
|
||||
params.setKeywords(keywords);
|
||||
params.setRange(Range{ albumOffset, albumCount });
|
||||
params.setMediaLibrary(mediaLibrary);
|
||||
params.setSortMethod(ReleaseSortMethod::Id); // must be consistent with both methods
|
||||
|
||||
Release::find(context.dbSession, params, [&](const Release::pointer& release)
|
||||
{
|
||||
searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3));
|
||||
lastRetrievedId = release->getId();
|
||||
});
|
||||
Release::find(context.dbSession, params, [&](const Release::pointer& release) {
|
||||
searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3));
|
||||
lastRetrievedId = release->getId();
|
||||
});
|
||||
} };
|
||||
|
||||
if (!keywords.empty())
|
||||
@@ -213,8 +210,7 @@ namespace lms::api::subsonic
|
||||
}
|
||||
else
|
||||
{
|
||||
ScanTracker<ReleaseId>::ScanInfo scanInfo
|
||||
{
|
||||
ScanTracker<ReleaseId>::ScanInfo scanInfo{
|
||||
.clientAddress = context.clientInfo.ipAddress,
|
||||
.clientName = context.clientInfo.name,
|
||||
.userName = context.clientInfo.user,
|
||||
@@ -224,10 +220,11 @@ namespace lms::api::subsonic
|
||||
|
||||
if (ReleaseId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
|
||||
{
|
||||
Release::find(context.dbSession, cachedLastRetrievedId, albumCount, [&](const Release::pointer& release)
|
||||
{
|
||||
Release::find(
|
||||
context.dbSession, cachedLastRetrievedId, albumCount, [&](const Release::pointer& release) {
|
||||
searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3));
|
||||
}, mediaLibrary);
|
||||
},
|
||||
mediaLibrary);
|
||||
lastRetrievedId = cachedLastRetrievedId;
|
||||
}
|
||||
else
|
||||
@@ -258,19 +255,17 @@ namespace lms::api::subsonic
|
||||
|
||||
TrackId lastRetrievedId;
|
||||
|
||||
auto findTracks{ [&]
|
||||
{
|
||||
auto findTracks{ [&] {
|
||||
Track::FindParameters params;
|
||||
params.setKeywords(keywords);
|
||||
params.setRange(Range{ songOffset, songCount });
|
||||
params.setMediaLibrary(mediaLibrary);
|
||||
params.setSortMethod(TrackSortMethod::Id); // must be consistent with both methods
|
||||
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track)
|
||||
{
|
||||
searchResultNode.addArrayChild("song", createSongNode(context, track, user));
|
||||
lastRetrievedId = track->getId();
|
||||
});
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
|
||||
searchResultNode.addArrayChild("song", createSongNode(context, track, user));
|
||||
lastRetrievedId = track->getId();
|
||||
});
|
||||
} };
|
||||
|
||||
if (!keywords.empty())
|
||||
@@ -279,8 +274,7 @@ namespace lms::api::subsonic
|
||||
}
|
||||
else
|
||||
{
|
||||
ScanTracker<TrackId>::ScanInfo scanInfo
|
||||
{
|
||||
ScanTracker<TrackId>::ScanInfo scanInfo{
|
||||
.clientAddress = context.clientInfo.ipAddress,
|
||||
.clientName = context.clientInfo.name,
|
||||
.userName = context.clientInfo.user,
|
||||
@@ -290,10 +284,11 @@ namespace lms::api::subsonic
|
||||
|
||||
if (TrackId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
|
||||
{
|
||||
Track::find(context.dbSession, cachedLastRetrievedId, songCount, [&](const Track::pointer& track)
|
||||
{
|
||||
Track::find(
|
||||
context.dbSession, cachedLastRetrievedId, songCount, [&](const Track::pointer& track) {
|
||||
searchResultNode.addArrayChild("song", createSongNode(context, track, user));
|
||||
}, mediaLibrary);
|
||||
},
|
||||
mediaLibrary);
|
||||
lastRetrievedId = cachedLastRetrievedId;
|
||||
}
|
||||
else
|
||||
@@ -308,7 +303,7 @@ namespace lms::api::subsonic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -340,7 +335,7 @@ namespace lms::api::subsonic
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Response handleSearch2Request(RequestContext& context)
|
||||
{
|
||||
@@ -351,4 +346,4 @@ namespace lms::api::subsonic
|
||||
{
|
||||
return handleSearchRequestCommon(context, true /* id3 */);
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -26,4 +26,4 @@ namespace lms::api::subsonic
|
||||
{
|
||||
Response handleSearch2Request(RequestContext& context);
|
||||
Response handleSearch3Request(RequestContext& context);
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -37,4 +37,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return response;
|
||||
};
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -27,4 +27,4 @@ namespace lms::api::subsonic
|
||||
Response handlePingRequest(RequestContext& context);
|
||||
Response handleGetLicenseRequest(RequestContext& context);
|
||||
Response handleGetOpenSubsonicExtensions(RequestContext& context);
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
#include "UserManagement.hpp"
|
||||
|
||||
#include "core/Service.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/auth/IPasswordService.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "responses/User.hpp"
|
||||
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "responses/User.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
using namespace db;
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
void checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username)
|
||||
{
|
||||
if (context.user->getLoginName() != username && !context.user->isAdmin())
|
||||
throw UserNotAuthorizedError{};
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Response handleGetUserRequest(RequestContext& context)
|
||||
{
|
||||
@@ -44,10 +46,9 @@ namespace lms::api::subsonic
|
||||
Response::Node& usersNode{ response.createNode("users") };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
User::find(context.dbSession, User::FindParameters{}, [&](const User::pointer& user)
|
||||
{
|
||||
usersNode.addArrayChild("user", createUserNode(user));
|
||||
});
|
||||
User::find(context.dbSession, User::FindParameters{}, [&](const User::pointer& user) {
|
||||
usersNode.addArrayChild("user", createUserNode(user));
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -70,13 +71,12 @@ namespace lms::api::subsonic
|
||||
userId = user->getId();
|
||||
}
|
||||
|
||||
auto removeCreatedUser{ [&]
|
||||
{
|
||||
auto transaction {context.dbSession.createWriteTransaction()};
|
||||
User::pointer user{ User::find(context.dbSession, userId) };
|
||||
if (user)
|
||||
user.remove();
|
||||
} };
|
||||
auto removeCreatedUser{ [&] {
|
||||
auto transaction{ context.dbSession.createWriteTransaction() };
|
||||
User::pointer user{ User::find(context.dbSession, userId) };
|
||||
if (user)
|
||||
user.remove();
|
||||
} };
|
||||
|
||||
try
|
||||
{
|
||||
@@ -198,4 +198,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -30,4 +30,4 @@ namespace lms::api::subsonic
|
||||
Response handleUpdateUserRequest(RequestContext& context);
|
||||
Response handleDeleteUserRequest(RequestContext& context);
|
||||
Response handleChangePassword(RequestContext& context);
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -19,21 +19,21 @@
|
||||
|
||||
#include "responses/Album.hpp"
|
||||
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/Release.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "SubsonicId.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
#include "responses/DiscTitle.hpp"
|
||||
#include "responses/ItemDate.hpp"
|
||||
#include "responses/ItemGenre.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -45,13 +45,14 @@ namespace lms::api::subsonic
|
||||
|
||||
Response::Node albumNode;
|
||||
|
||||
if (id3) {
|
||||
if (id3)
|
||||
{
|
||||
albumNode.setAttribute("name", release->getName());
|
||||
albumNode.setAttribute("songCount", release->getTrackCount());
|
||||
albumNode.setAttribute(
|
||||
"duration", std::chrono::duration_cast<std::chrono::seconds>(
|
||||
release->getDuration())
|
||||
.count());
|
||||
release->getDuration())
|
||||
.count());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -97,7 +98,7 @@ namespace lms::api::subsonic
|
||||
const ClusterType::pointer genreClusterType{ ClusterType::find(context.dbSession, "GENRE") };
|
||||
if (genreClusterType)
|
||||
{
|
||||
auto clusters{ release->getClusterGroups({genreClusterType->getId()}, 1) };
|
||||
auto clusters{ release->getClusterGroups({ genreClusterType->getId() }, 1) };
|
||||
if (!clusters.empty() && !clusters.front().empty())
|
||||
albumNode.setAttribute("genre", clusters.front().front()->getName());
|
||||
}
|
||||
@@ -124,19 +125,17 @@ namespace lms::api::subsonic
|
||||
albumNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : "");
|
||||
}
|
||||
|
||||
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName)
|
||||
{
|
||||
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) {
|
||||
albumNode.createEmptyArrayValue(field);
|
||||
|
||||
Cluster::FindParameters params;
|
||||
params.setRelease(release->getId());
|
||||
params.setClusterTypeName(clusterTypeName);
|
||||
|
||||
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster)
|
||||
{
|
||||
albumNode.addArrayValue(field, cluster->getName());
|
||||
});
|
||||
}};
|
||||
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) {
|
||||
albumNode.addArrayValue(field, cluster->getName());
|
||||
});
|
||||
} };
|
||||
|
||||
addClusters("moods", "MOOD");
|
||||
|
||||
@@ -148,10 +147,9 @@ namespace lms::api::subsonic
|
||||
params.setRelease(release->getId());
|
||||
params.setClusterType(genreClusterType->getId());
|
||||
|
||||
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster)
|
||||
{
|
||||
albumNode.addArrayChild("genres", createItemGenreNode(cluster->getName()));
|
||||
});
|
||||
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) {
|
||||
albumNode.addArrayChild("genres", createItemGenreNode(cluster->getName()));
|
||||
});
|
||||
}
|
||||
|
||||
albumNode.createEmptyArrayChild("artists");
|
||||
@@ -186,4 +184,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return albumNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
@@ -27,7 +28,7 @@ namespace lms::db
|
||||
class Release;
|
||||
class User;
|
||||
class Session;
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
#include "responses/Artist.hpp"
|
||||
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Release.hpp"
|
||||
#include "database/TrackArtistLink.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
@@ -45,8 +45,7 @@ namespace lms::api::subsonic
|
||||
names.resize(artists.size());
|
||||
|
||||
std::transform(std::cbegin(artists), std::cend(artists), std::begin(names),
|
||||
[](const Artist::pointer& artist)
|
||||
{
|
||||
[](const Artist::pointer& artist) {
|
||||
return artist->getName();
|
||||
});
|
||||
|
||||
@@ -57,22 +56,33 @@ namespace lms::api::subsonic
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TrackArtistLinkType::Arranger: return "arranger";
|
||||
case TrackArtistLinkType::Artist: return "artist";
|
||||
case TrackArtistLinkType::Composer: return "composer";
|
||||
case TrackArtistLinkType::Conductor: return "conductor";
|
||||
case TrackArtistLinkType::Lyricist: return "lyricist";
|
||||
case TrackArtistLinkType::Mixer: return "mixer";
|
||||
case TrackArtistLinkType::Performer: return "performer";
|
||||
case TrackArtistLinkType::Producer: return "producer";
|
||||
case TrackArtistLinkType::ReleaseArtist: return "albumartist";
|
||||
case TrackArtistLinkType::Remixer: return "remixer";
|
||||
case TrackArtistLinkType::Writer: return "writer";
|
||||
case TrackArtistLinkType::Arranger:
|
||||
return "arranger";
|
||||
case TrackArtistLinkType::Artist:
|
||||
return "artist";
|
||||
case TrackArtistLinkType::Composer:
|
||||
return "composer";
|
||||
case TrackArtistLinkType::Conductor:
|
||||
return "conductor";
|
||||
case TrackArtistLinkType::Lyricist:
|
||||
return "lyricist";
|
||||
case TrackArtistLinkType::Mixer:
|
||||
return "mixer";
|
||||
case TrackArtistLinkType::Performer:
|
||||
return "performer";
|
||||
case TrackArtistLinkType::Producer:
|
||||
return "producer";
|
||||
case TrackArtistLinkType::ReleaseArtist:
|
||||
return "albumartist";
|
||||
case TrackArtistLinkType::Remixer:
|
||||
return "remixer";
|
||||
case TrackArtistLinkType::Writer:
|
||||
return "writer";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
} // namespace utils
|
||||
|
||||
Response::Node createArtistNode(RequestContext& context, const Artist::pointer& artist, const User::pointer& user, bool id3)
|
||||
{
|
||||
@@ -86,7 +96,7 @@ namespace lms::api::subsonic
|
||||
|
||||
if (id3)
|
||||
{
|
||||
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters {}.setArtist(artist->getId())) };
|
||||
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
|
||||
artistNode.setAttribute("albumCount", count);
|
||||
}
|
||||
|
||||
@@ -125,4 +135,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return artistNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -21,8 +21,10 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
@@ -30,7 +32,7 @@ namespace lms::db
|
||||
class Artist;
|
||||
class User;
|
||||
class Session;
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -38,7 +40,7 @@ namespace lms::api::subsonic
|
||||
{
|
||||
std::string joinArtistNames(const std::vector<db::ObjectPtr<db::Artist>>& artists);
|
||||
std::string_view toString(db::TrackArtistLinkType type);
|
||||
}
|
||||
} // namespace utils
|
||||
Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr<db::Artist>& artist, const db::ObjectPtr<db::User>& user, bool id3);
|
||||
Response::Node createArtistNode(const db::ObjectPtr<db::Artist>& artist); // only minimal info
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -39,4 +39,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return trackBookmarkNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/TrackArtistLink.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
|
||||
@@ -37,4 +38,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return contributorNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,13 +20,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Artist;
|
||||
class TrackArtistLink;
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
|
||||
@@ -30,4 +30,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return discTitleNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
|
||||
@@ -33,4 +33,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return clusterNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
|
||||
@@ -40,4 +40,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return itemDateNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Wt/WDate.h>
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
|
||||
@@ -31,4 +31,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return genreNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
@@ -42,9 +43,9 @@ namespace lms::api::subsonic
|
||||
playlistNode.setAttribute("created", reportedDummyDate);
|
||||
playlistNode.setAttribute("owner", tracklist->getUser()->getLoginName());
|
||||
|
||||
if (const auto entry {tracklist->getEntry(0)})
|
||||
if (const auto entry{ tracklist->getEntry(0) })
|
||||
playlistNode.setAttribute("coverArt", idToString(entry->getTrack()->getId()));
|
||||
|
||||
return playlistNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,13 +20,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class TrackList;
|
||||
class Session;
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
|
||||
@@ -35,4 +35,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return replayGainNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include <string_view>
|
||||
|
||||
#include "av/IAudioFile.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/Release.hpp"
|
||||
@@ -30,15 +33,13 @@
|
||||
#include "database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "SubsonicId.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
#include "responses/Contributor.hpp"
|
||||
#include "responses/ItemGenre.hpp"
|
||||
#include "responses/ReplayGain.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -50,16 +51,21 @@ namespace lms::api::subsonic
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TranscodingOutputFormat::MP3: return "mp3";
|
||||
case TranscodingOutputFormat::OGG_OPUS: return "opus";
|
||||
case TranscodingOutputFormat::MATROSKA_OPUS: return "mka";
|
||||
case TranscodingOutputFormat::OGG_VORBIS: return "ogg";
|
||||
case TranscodingOutputFormat::WEBM_VORBIS: return "webm";
|
||||
case TranscodingOutputFormat::MP3:
|
||||
return "mp3";
|
||||
case TranscodingOutputFormat::OGG_OPUS:
|
||||
return "opus";
|
||||
case TranscodingOutputFormat::MATROSKA_OPUS:
|
||||
return "mka";
|
||||
case TranscodingOutputFormat::OGG_VORBIS:
|
||||
return "ogg";
|
||||
case TranscodingOutputFormat::WEBM_VORBIS:
|
||||
return "webm";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Response::Node createSongNode(RequestContext& context, const Track::pointer& track, const User::pointer& user)
|
||||
{
|
||||
@@ -94,7 +100,7 @@ namespace lms::api::subsonic
|
||||
|
||||
trackResponse.setAttribute("coverArt", idToString(track->getId()));
|
||||
|
||||
const std::vector<Artist::pointer>& artists{ track->getArtists({TrackArtistLinkType::Artist}) };
|
||||
const std::vector<Artist::pointer>& artists{ track->getArtists({ TrackArtistLinkType::Artist }) };
|
||||
if (!artists.empty())
|
||||
{
|
||||
if (!track->getArtistDisplayName().empty())
|
||||
@@ -160,18 +166,17 @@ namespace lms::api::subsonic
|
||||
trackResponse.createEmptyArrayChild("artists");
|
||||
trackResponse.createEmptyArrayChild("contributors");
|
||||
|
||||
TrackArtistLink::find(context.dbSession, track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist)
|
||||
{
|
||||
TrackArtistLink::find(context.dbSession, track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) {
|
||||
switch (link->getType())
|
||||
{
|
||||
case TrackArtistLinkType::Artist:
|
||||
trackResponse.addArrayChild("artists", createArtistNode(artist));
|
||||
break;
|
||||
case TrackArtistLinkType::ReleaseArtist:
|
||||
trackResponse.addArrayChild("albumartists", createArtistNode(artist));
|
||||
break;
|
||||
default:
|
||||
trackResponse.addArrayChild("contributors", createContributorNode(link, artist));
|
||||
case TrackArtistLinkType::Artist:
|
||||
trackResponse.addArrayChild("artists", createArtistNode(artist));
|
||||
break;
|
||||
case TrackArtistLinkType::ReleaseArtist:
|
||||
trackResponse.addArrayChild("albumartists", createArtistNode(artist));
|
||||
break;
|
||||
default:
|
||||
trackResponse.addArrayChild("contributors", createContributorNode(link, artist));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -180,8 +185,7 @@ namespace lms::api::subsonic
|
||||
if (release)
|
||||
trackResponse.setAttribute("displayAlbumArtist", release->getArtistDisplayName());
|
||||
|
||||
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName)
|
||||
{
|
||||
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) {
|
||||
trackResponse.createEmptyArrayValue(field);
|
||||
|
||||
Cluster::FindParameters params;
|
||||
@@ -203,4 +207,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return trackResponse;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
@@ -27,7 +28,7 @@ namespace lms::db
|
||||
class Track;
|
||||
class User;
|
||||
class Session;
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
|
||||
@@ -49,4 +49,4 @@ namespace lms::api::subsonic
|
||||
|
||||
return userNode;
|
||||
}
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
|
||||
Reference in New Issue
Block a user