From 7c7fbba31900525752ec28d8dd3b4dad52427fa9 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 2 Oct 2023 20:32:28 +0200 Subject: [PATCH] Changed coding style --- src/libs/subsonic/impl/ClientInfo.hpp | 14 +- src/libs/subsonic/impl/ParameterParsing.hpp | 82 +-- src/libs/subsonic/impl/ProtocolVersion.cpp | 51 +- src/libs/subsonic/impl/ProtocolVersion.hpp | 17 +- src/libs/subsonic/impl/RequestContext.hpp | 18 +- src/libs/subsonic/impl/SubsonicId.cpp | 160 +++-- src/libs/subsonic/impl/SubsonicId.hpp | 37 +- src/libs/subsonic/impl/SubsonicResource.cpp | 608 +++++++++--------- src/libs/subsonic/impl/SubsonicResource.hpp | 30 +- src/libs/subsonic/impl/SubsonicResponse.cpp | 381 ++++++----- src/libs/subsonic/impl/SubsonicResponse.hpp | 352 +++++----- .../impl/entrypoints/MediaLibraryScanning.cpp | 64 +- .../impl/entrypoints/MediaLibraryScanning.hpp | 4 +- .../impl/entrypoints/MediaRetrieval.cpp | 258 ++++---- .../impl/entrypoints/MediaRetrieval.hpp | 6 +- src/libs/subsonic/impl/responses/Genre.cpp | 16 +- src/libs/utils/impl/String.cpp | 408 ++++++------ src/libs/utils/include/utils/String.hpp | 70 +- 18 files changed, 1265 insertions(+), 1311 deletions(-) diff --git a/src/libs/subsonic/impl/ClientInfo.hpp b/src/libs/subsonic/impl/ClientInfo.hpp index 9c2dafd0..ed25a18b 100644 --- a/src/libs/subsonic/impl/ClientInfo.hpp +++ b/src/libs/subsonic/impl/ClientInfo.hpp @@ -24,11 +24,11 @@ namespace API::Subsonic { - struct ClientInfo - { - std::string name; - std::string user; - std::string password; - ProtocolVersion version; - }; + struct ClientInfo + { + std::string name; + std::string user; + std::string password; + ProtocolVersion version; + }; } diff --git a/src/libs/subsonic/impl/ParameterParsing.hpp b/src/libs/subsonic/impl/ParameterParsing.hpp index 414d3aa6..9197c1b1 100644 --- a/src/libs/subsonic/impl/ParameterParsing.hpp +++ b/src/libs/subsonic/impl/ParameterParsing.hpp @@ -32,57 +32,57 @@ namespace API::Subsonic { - template - std::vector getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& paramName) - { - std::vector res; + template + std::vector getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& paramName) + { + std::vector res; - auto it = parameterMap.find(paramName); - if (it == parameterMap.end()) - return res; + auto it = parameterMap.find(paramName); + if (it == parameterMap.end()) + return res; - for (const std::string& param : it->second) - { - auto value{ StringUtils::readAs(param) }; - if (value) - res.emplace_back(std::move(*value)); - } + for (const std::string& param : it->second) + { + auto value{ StringUtils::readAs(param) }; + if (value) + res.emplace_back(std::move(*value)); + } - return res; - } + return res; + } - template - std::vector getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param) - { - std::vector res{ getMultiParametersAs(parameterMap, param) }; - if (res.empty()) - throw RequiredParameterMissingError{ param }; + template + std::vector getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param) + { + std::vector res{ getMultiParametersAs(parameterMap, param) }; + if (res.empty()) + throw RequiredParameterMissingError{ param }; - return res; - } + return res; + } - template - std::optional getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param) - { - std::vector params{ getMultiParametersAs(parameterMap, param) }; + template + std::optional getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param) + { + std::vector params{ getMultiParametersAs(parameterMap, param) }; - if (params.size() != 1) - return std::nullopt; + if (params.size() != 1) + return std::nullopt; - return T{ std::move(params.front()) }; - } + return T{ std::move(params.front()) }; + } - template - T getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param) - { - auto res{ getParameterAs(parameterMap, param) }; - if (!res) - throw RequiredParameterMissingError{ param }; + template + T getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param) + { + auto res{ getParameterAs(parameterMap, param) }; + if (!res) + throw RequiredParameterMissingError{ param }; - return *res; - } + return *res; + } - bool hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param); - std::string decodePasswordIfNeeded(const std::string& password); + bool hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param); + std::string decodePasswordIfNeeded(const std::string& password); } diff --git a/src/libs/subsonic/impl/ProtocolVersion.cpp b/src/libs/subsonic/impl/ProtocolVersion.cpp index 4a02a4e3..2dbf3a45 100644 --- a/src/libs/subsonic/impl/ProtocolVersion.cpp +++ b/src/libs/subsonic/impl/ProtocolVersion.cpp @@ -21,36 +21,35 @@ namespace StringUtils { - template<> - std::optional - readAs(std::string_view str) - { - // Expects "X.Y.Z" - const auto numbers {StringUtils::splitString(str, ".")}; - if (numbers.size() < 2 || numbers.size() > 3) - return std::nullopt; + template<> + std::optional readAs(std::string_view str) + { + // Expects "X.Y.Z" + const auto numbers{ StringUtils::splitString(str, ".") }; + if (numbers.size() < 2 || numbers.size() > 3) + return std::nullopt; - API::Subsonic::ProtocolVersion version; + API::Subsonic::ProtocolVersion version; - auto number {StringUtils::readAs(numbers[0])}; - if (!number) - return std::nullopt; - version.major = *number; + auto number{ StringUtils::readAs(numbers[0]) }; + if (!number) + return std::nullopt; + version.major = *number; - number = {StringUtils::readAs(numbers[1])}; - if (!number) - return std::nullopt; - version.minor = *number; + number = { StringUtils::readAs(numbers[1]) }; + if (!number) + return std::nullopt; + version.minor = *number; - if (numbers.size() == 3) - { - number = {StringUtils::readAs(numbers[2])}; - if (!number) - return std::nullopt; - version.patch = *number; - } + if (numbers.size() == 3) + { + number = { StringUtils::readAs(numbers[2]) }; + if (!number) + return std::nullopt; + version.patch = *number; + } - return version; - } + return version; + } } diff --git a/src/libs/subsonic/impl/ProtocolVersion.hpp b/src/libs/subsonic/impl/ProtocolVersion.hpp index fc046e22..c1a56086 100644 --- a/src/libs/subsonic/impl/ProtocolVersion.hpp +++ b/src/libs/subsonic/impl/ProtocolVersion.hpp @@ -23,18 +23,19 @@ namespace API::Subsonic { - struct ProtocolVersion - { - unsigned major {}; - unsigned minor {}; - unsigned patch {}; - }; + struct ProtocolVersion + { + unsigned major{}; + unsigned minor{}; + unsigned patch{}; + }; - static inline constexpr ProtocolVersion defaultServerProtocolVersion {1, 16, 0}; + static inline constexpr ProtocolVersion defaultServerProtocolVersion{ 1, 16, 0 }; } namespace StringUtils { - template<> std::optional readAs(std::string_view str); + template<> + std::optional readAs(std::string_view str); } diff --git a/src/libs/subsonic/impl/RequestContext.hpp b/src/libs/subsonic/impl/RequestContext.hpp index 01930783..4f2801bc 100644 --- a/src/libs/subsonic/impl/RequestContext.hpp +++ b/src/libs/subsonic/impl/RequestContext.hpp @@ -29,18 +29,18 @@ namespace Database { - class Session; + class Session; } namespace API::Subsonic { - struct RequestContext - { - const Wt::Http::ParameterMap& parameters; - Database::Session& dbSession; - Database::UserId userId; - ClientInfo clientInfo; - ProtocolVersion serverProtocolVersion; - }; + struct RequestContext + { + const Wt::Http::ParameterMap& parameters; + Database::Session& dbSession; + Database::UserId userId; + ClientInfo clientInfo; + ProtocolVersion serverProtocolVersion; + }; } diff --git a/src/libs/subsonic/impl/SubsonicId.cpp b/src/libs/subsonic/impl/SubsonicId.cpp index 1252a7b8..ddd3090f 100644 --- a/src/libs/subsonic/impl/SubsonicId.cpp +++ b/src/libs/subsonic/impl/SubsonicId.cpp @@ -26,114 +26,104 @@ namespace API::Subsonic { - std::string - idToString(Database::ArtistId id) - { - return "ar-" + id.toString(); - } + std::string idToString(Database::ArtistId id) + { + return "ar-" + id.toString(); + } - std::string - idToString(Database::ReleaseId id) - { - return "al-" + id.toString(); - } + std::string idToString(Database::ReleaseId id) + { + return "al-" + id.toString(); + } - std::string - idToString(RootId) - { - return "root"; - } + std::string idToString(RootId) + { + return "root"; + } - std::string - idToString(Database::TrackId id) - { - return "tr-" + id.toString(); - } + std::string idToString(Database::TrackId id) + { + return "tr-" + id.toString(); + } - std::string - idToString(Database::TrackListId id) - { - return "pl-" + id.toString(); - } + std::string idToString(Database::TrackListId id) + { + return "pl-" + id.toString(); + } } // namespace API::Subsonic namespace StringUtils { - template<> - std::optional - readAs(std::string_view str) - { - std::vector values {StringUtils::splitString(str, "-")}; - if (values.size() != 2) - return std::nullopt; + template<> + std::optional readAs(std::string_view str) + { + std::vector values{ StringUtils::splitString(str, "-") }; + if (values.size() != 2) + return std::nullopt; - if (values[0] != "ar") - return std::nullopt; + if (values[0] != "ar") + return std::nullopt; - if (const auto value {StringUtils::readAs(values[1])}) - return Database::ArtistId {*value}; + if (const auto value{ StringUtils::readAs(values[1]) }) + return Database::ArtistId{ *value }; - return std::nullopt; - } + return std::nullopt; + } - template<> - std::optional - readAs(std::string_view str) - { - std::vector values {StringUtils::splitString(str, "-")}; - if (values.size() != 2) - return std::nullopt; + template<> + std::optional readAs(std::string_view str) + { + std::vector values{ StringUtils::splitString(str, "-") }; + if (values.size() != 2) + return std::nullopt; - if (values[0] != "al") - return std::nullopt; + if (values[0] != "al") + return std::nullopt; - if (const auto value {StringUtils::readAs(values[1])}) - return Database::ReleaseId {*value}; + if (const auto value{ StringUtils::readAs(values[1]) }) + return Database::ReleaseId{ *value }; - return std::nullopt; - } + return std::nullopt; + } - template<> - std::optional - readAs(std::string_view str) - { - if (str == "root") - return API::Subsonic::RootId {}; + template<> + std::optional readAs(std::string_view str) + { + if (str == "root") + return API::Subsonic::RootId{}; - return std::nullopt; - } + return std::nullopt; + } - template<> - std::optional - readAs(std::string_view str) - { - std::vector values {StringUtils::splitString(str, "-")}; - if (values.size() != 2) - return std::nullopt; + template<> + std::optional readAs(std::string_view str) + { + std::vector values{ StringUtils::splitString(str, "-") }; + if (values.size() != 2) + return std::nullopt; - if (values[0] != "tr") - return std::nullopt; + if (values[0] != "tr") + return std::nullopt; - if (const auto value {StringUtils::readAs(values[1])}) - return Database::TrackId {*value}; + if (const auto value{ StringUtils::readAs(values[1]) }) + return Database::TrackId{ *value }; - return std::nullopt; - } + return std::nullopt; + } - template<> - std::optional - readAs(std::string_view str) - { - std::vector values {StringUtils::splitString(str, "-")}; - if (values.size() != 2) - return std::nullopt; + template<> + std::optional readAs(std::string_view str) + { + std::vector values{ StringUtils::splitString(str, "-") }; + if (values.size() != 2) + return std::nullopt; - if (values[0] != "pl") - return std::nullopt; + if (values[0] != "pl") + return std::nullopt; - if (const auto value {StringUtils::readAs(values[1])}) - return Database::TrackListId {*value}; + if (const auto value{ StringUtils::readAs(values[1]) }) + return Database::TrackListId{ *value }; - return std::nullopt; - } + return std::nullopt; + } } diff --git a/src/libs/subsonic/impl/SubsonicId.hpp b/src/libs/subsonic/impl/SubsonicId.hpp index 96db3e79..40b831bf 100644 --- a/src/libs/subsonic/impl/SubsonicId.hpp +++ b/src/libs/subsonic/impl/SubsonicId.hpp @@ -27,36 +27,31 @@ namespace API::Subsonic { - struct RootId {}; + struct RootId {}; - std::string idToString(Database::ArtistId id); - std::string idToString(Database::ReleaseId id); - std::string idToString(Database::TrackId id); - std::string idToString(Database::TrackListId id); - std::string idToString(RootId); + std::string idToString(Database::ArtistId id); + std::string idToString(Database::ReleaseId id); + std::string idToString(Database::TrackId id); + std::string idToString(Database::TrackListId id); + std::string idToString(RootId); } // namespace API::Subsonic // Used to parse parameters namespace StringUtils { - template<> - std::optional - readAs(std::string_view str); + template<> + std::optional readAs(std::string_view str); - template<> - std::optional - readAs(std::string_view str); + template<> + std::optional readAs(std::string_view str); - template<> - std::optional - readAs(std::string_view str); + template<> + std::optional readAs(std::string_view str); - template<> - std::optional - readAs(std::string_view str); + template<> + std::optional readAs(std::string_view str); - template<> - std::optional - readAs(std::string_view str); + template<> + std::optional readAs(std::string_view str); } diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 4dab94ac..532a6ca2 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -54,378 +54,364 @@ using namespace Database; namespace API::Subsonic { + std::unique_ptr createSubsonicResource(Database::Db& db) + { + return std::make_unique(db); + } -std::unique_ptr -createSubsonicResource(Database::Db& db) -{ - return std::make_unique(db); -} + namespace + { + std::unordered_map readConfigProtocolVersions() + { + std::unordered_map res; -static -std::unordered_map -readConfigProtocolVersions() -{ - std::unordered_map res; + Service::get()->visitStrings("api-subsonic-report-old-server-protocol", + [&](std::string_view client) + { + res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 }); + }, { "DSub" }); - Service::get()->visitStrings("api-subsonic-report-old-server-protocol", - [&](std::string_view client) - { - res.emplace(std::string {client}, ProtocolVersion {1, 12, 0}); - }, {"DSub"}); + return res; + } - return res; -} -SubsonicResource::SubsonicResource(Db& db) -: _serverProtocolVersionsByClient {readConfigProtocolVersions()} -, _db {db} -{ -} + 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; + }; -static -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; - }; + std::string res; - std::string res; + for (const auto& params : parameterMap) + { + res += "{" + params.first + "="; + if (params.second.size() == 1) + { + res += censorValue(params.first, params.second.front()); + } + else + { + res += "{"; + for (const std::string& param : params.second) + res += censorValue(params.first, param) + ","; + res += "}"; + } + res += "}, "; + } - for (const auto& params : parameterMap) - { - res += "{" + params.first + "="; - if (params.second.size() == 1) - { - res += censorValue(params.first, params.second.front()); - } - else - { - res += "{"; - for (const std::string& param : params.second) - res += censorValue(params.first, param) + ","; - res += "}"; - } - res += "}, "; - } + return res; + } - return res; -} + void checkUserTypeIsAllowed(RequestContext& context, EnumSet allowedUserTypes) + { + auto transaction{ context.dbSession.createSharedTransaction() }; -static -void -checkUserTypeIsAllowed(RequestContext& context, EnumSet allowedUserTypes) -{ - auto transaction {context.dbSession.createSharedTransaction()}; + User::pointer currentUser{ User::find(context.dbSession, context.userId) }; + if (!currentUser) + throw RequestedDataNotFoundError{}; - User::pointer currentUser {User::find(context.dbSession, context.userId)}; - if (!currentUser) - throw RequestedDataNotFoundError {}; + if (!allowedUserTypes.contains(currentUser->getType())) + throw UserNotAuthorizedError{}; + } - if (!allowedUserTypes.contains(currentUser->getType())) - throw UserNotAuthorizedError {}; -} + Response handlePingRequest(RequestContext& context) + { + return Response::createOkResponse(context.serverProtocolVersion); + } -static -Response -handlePingRequest(RequestContext& context) -{ - return Response::createOkResponse(context.serverProtocolVersion); -} + Response handleGetLicenseRequest(RequestContext& context) + { + Response response{ Response::createOkResponse(context.serverProtocolVersion) }; -static -Response -handleGetLicenseRequest(RequestContext& context) -{ - Response response {Response::createOkResponse(context.serverProtocolVersion)}; + Response::Node& licenseNode{ response.createNode("license") }; + licenseNode.setAttribute("licenseExpires", "2025-09-03T14:46:43"); + licenseNode.setAttribute("email", "foo@bar.com"); + licenseNode.setAttribute("valid", true); - Response::Node& licenseNode {response.createNode("license")}; - licenseNode.setAttribute("licenseExpires", "2025-09-03T14:46:43"); - licenseNode.setAttribute("email", "foo@bar.com"); - licenseNode.setAttribute("valid", true); + return response; + } - return response; -} + Response handleNotImplemented(RequestContext&) + { + throw NotImplementedGenericError{}; + } -static -Response -handleNotImplemented(RequestContext&) -{ - throw NotImplementedGenericError {}; -} -using RequestHandlerFunc = std::function; -using CheckImplementedFunc = std::function; -struct RequestEntryPointInfo -{ - RequestHandlerFunc func; - EnumSet allowedUserTypes {UserType::DEMO, UserType::REGULAR, UserType::ADMIN}; - CheckImplementedFunc checkFunc {}; -}; + using RequestHandlerFunc = std::function; + using CheckImplementedFunc = std::function; + struct RequestEntryPointInfo + { + RequestHandlerFunc func; + EnumSet allowedUserTypes{ UserType::DEMO, UserType::REGULAR, UserType::ADMIN }; + CheckImplementedFunc checkFunc{}; + }; -static const std::unordered_map requestEntryPoints -{ - // System - {"/ping", {handlePingRequest}}, - {"/getLicense", {handleGetLicenseRequest}}, + static const std::unordered_map requestEntryPoints + { + // System + {"/ping", {handlePingRequest}}, + {"/getLicense", {handleGetLicenseRequest}}, - // 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", {handleNotImplemented}}, + // 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", {handleNotImplemented}}, - // Album/song lists - {"/getAlbumList", {handleGetAlbumListRequest}}, - {"/getAlbumList2", {handleGetAlbumList2Request}}, - {"/getRandomSongs", {handleGetRandomSongsRequest}}, - {"/getSongsByGenre", {handleGetSongsByGenreRequest}}, - {"/getNowPlaying", {handleNotImplemented}}, - {"/getStarred", {handleGetStarredRequest}}, - {"/getStarred2", {handleGetStarred2Request}}, + // Album/song lists + {"/getAlbumList", {handleGetAlbumListRequest}}, + {"/getAlbumList2", {handleGetAlbumList2Request}}, + {"/getRandomSongs", {handleGetRandomSongsRequest}}, + {"/getSongsByGenre", {handleGetSongsByGenreRequest}}, + {"/getNowPlaying", {handleNotImplemented}}, + {"/getStarred", {handleGetStarredRequest}}, + {"/getStarred2", {handleGetStarred2Request}}, - // Searching - {"/search", {handleNotImplemented}}, - {"/search2", {handleSearch2Request}}, - {"/search3", {handleSearch3Request}}, + // Searching + {"/search", {handleNotImplemented}}, + {"/search2", {handleSearch2Request}}, + {"/search3", {handleSearch3Request}}, - // Playlists - {"/getPlaylists", {handleGetPlaylistsRequest}}, - {"/getPlaylist", {handleGetPlaylistRequest}}, - {"/createPlaylist", {handleCreatePlaylistRequest}}, - {"/updatePlaylist", {handleUpdatePlaylistRequest}}, - {"/deletePlaylist", {handleDeletePlaylistRequest}}, + // Playlists + {"/getPlaylists", {handleGetPlaylistsRequest}}, + {"/getPlaylist", {handleGetPlaylistRequest}}, + {"/createPlaylist", {handleCreatePlaylistRequest}}, + {"/updatePlaylist", {handleUpdatePlaylistRequest}}, + {"/deletePlaylist", {handleDeletePlaylistRequest}}, - // Media retrieval - {"/hls", {handleNotImplemented}}, - {"/getCaptions", {handleNotImplemented}}, - {"/getLyrics", {handleNotImplemented}}, - {"/getAvatar", {handleNotImplemented}}, + // Media retrieval + {"/hls", {handleNotImplemented}}, + {"/getCaptions", {handleNotImplemented}}, + {"/getLyrics", {handleNotImplemented}}, + {"/getAvatar", {handleNotImplemented}}, - // Media annotation - {"/star", {handleStarRequest}}, - {"/unstar", {handleUnstarRequest}}, - {"/setRating", {handleNotImplemented}}, - {"/scrobble", {handleScrobble}}, + // Media annotation + {"/star", {handleStarRequest}}, + {"/unstar", {handleUnstarRequest}}, + {"/setRating", {handleNotImplemented}}, + {"/scrobble", {handleScrobble}}, - // Sharing - {"/getShares", {handleNotImplemented}}, - {"/createShares", {handleNotImplemented}}, - {"/updateShare", {handleNotImplemented}}, - {"/deleteShare", {handleNotImplemented}}, + // Sharing + {"/getShares", {handleNotImplemented}}, + {"/createShares", {handleNotImplemented}}, + {"/updateShare", {handleNotImplemented}}, + {"/deleteShare", {handleNotImplemented}}, - // Podcast - {"/getPodcasts", {handleNotImplemented}}, - {"/getNewestPodcasts", {handleNotImplemented}}, - {"/refreshPodcasts", {handleNotImplemented}}, - {"/createPodcastChannel", {handleNotImplemented}}, - {"/deletePodcastChannel", {handleNotImplemented}}, - {"/deletePodcastEpisode", {handleNotImplemented}}, - {"/downloadPodcastEpisode", {handleNotImplemented}}, + // Podcast + {"/getPodcasts", {handleNotImplemented}}, + {"/getNewestPodcasts", {handleNotImplemented}}, + {"/refreshPodcasts", {handleNotImplemented}}, + {"/createPodcastChannel", {handleNotImplemented}}, + {"/deletePodcastChannel", {handleNotImplemented}}, + {"/deletePodcastEpisode", {handleNotImplemented}}, + {"/downloadPodcastEpisode", {handleNotImplemented}}, - // Jukebox - {"/jukeboxControl", {handleNotImplemented}}, + // Jukebox + {"/jukeboxControl", {handleNotImplemented}}, - // Internet radio - {"/getInternetRadioStations", {handleNotImplemented}}, - {"/createInternetRadioStation", {handleNotImplemented}}, - {"/updateInternetRadioStation", {handleNotImplemented}}, - {"/deleteInternetRadioStation", {handleNotImplemented}}, + // Internet radio + {"/getInternetRadioStations", {handleNotImplemented}}, + {"/createInternetRadioStation", {handleNotImplemented}}, + {"/updateInternetRadioStation", {handleNotImplemented}}, + {"/deleteInternetRadioStation", {handleNotImplemented}}, - // Chat - {"/getChatMessages", {handleNotImplemented}}, - {"/addChatMessages", {handleNotImplemented}}, + // Chat + {"/getChatMessages", {handleNotImplemented}}, + {"/addChatMessages", {handleNotImplemented}}, - // User management - {"/getUser", {handleGetUserRequest}}, - {"/getUsers", {handleGetUsersRequest, {UserType::ADMIN}}}, - {"/createUser", {handleCreateUserRequest, {UserType::ADMIN}, &Utils::checkSetPasswordImplemented}}, - {"/updateUser", {handleUpdateUserRequest, {UserType::ADMIN}}}, - {"/deleteUser", {handleDeleteUserRequest, {UserType::ADMIN}}}, - {"/changePassword", {handleChangePassword, {UserType::REGULAR, UserType::ADMIN}, &Utils::checkSetPasswordImplemented}}, + // User management + {"/getUser", {handleGetUserRequest}}, + {"/getUsers", {handleGetUsersRequest, {UserType::ADMIN}}}, + {"/createUser", {handleCreateUserRequest, {UserType::ADMIN}, &Utils::checkSetPasswordImplemented}}, + {"/updateUser", {handleUpdateUserRequest, {UserType::ADMIN}}}, + {"/deleteUser", {handleDeleteUserRequest, {UserType::ADMIN}}}, + {"/changePassword", {handleChangePassword, {UserType::REGULAR, UserType::ADMIN}, &Utils::checkSetPasswordImplemented}}, - // Bookmarks - {"/getBookmarks", {handleGetBookmarks}}, - {"/createBookmark", {handleCreateBookmark}}, - {"/deleteBookmark", {handleDeleteBookmark}}, - {"/getPlayQueue", {handleNotImplemented}}, - {"/savePlayQueue", {handleNotImplemented}}, + // Bookmarks + {"/getBookmarks", {handleGetBookmarks}}, + {"/createBookmark", {handleCreateBookmark}}, + {"/deleteBookmark", {handleDeleteBookmark}}, + {"/getPlayQueue", {handleNotImplemented}}, + {"/savePlayQueue", {handleNotImplemented}}, - // Media library scanning - {"/getScanStatus", {Scan::handleGetScanStatus, {UserType::ADMIN}}}, - {"/startScan", {Scan::handleStartScan, {UserType::ADMIN}}}, -}; + // Media library scanning + {"/getScanStatus", {Scan::handleGetScanStatus, {UserType::ADMIN}}}, + {"/startScan", {Scan::handleStartScan, {UserType::ADMIN}}}, + }; -using MediaRetrievalHandlerFunc = std::function; -static std::unordered_map mediaRetrievalHandlers -{ - // Media retrieval - {"/download", handleDownload}, - {"/stream", handleStream}, - {"/getCoverArt", handleGetCoverArt}, -}; + using MediaRetrievalHandlerFunc = std::function; + static std::unordered_map mediaRetrievalHandlers + { + // Media retrieval + {"/download", handleDownload}, + {"/stream", handleStream}, + {"/getCoverArt", handleGetCoverArt}, + }; + } -void -SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) -{ - static std::atomic curRequestId {}; - const std::size_t requestId {curRequestId++}; + SubsonicResource::SubsonicResource(Db& db) + : _serverProtocolVersionsByClient{ readConfigProtocolVersions() } + , _db{ db } + { + } - LMS_LOG(API_SUBSONIC, DEBUG) << "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap()); + void SubsonicResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) + { + static std::atomic curRequestId{}; - std::string requestPath {request.pathInfo()}; - if (StringUtils::stringEndsWith(requestPath, ".view")) - requestPath.resize(requestPath.length() - 5); + const std::size_t requestId{ curRequestId++ }; - // Optional parameters - const ResponseFormat format {getParameterAs(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml}; + LMS_LOG(API_SUBSONIC, DEBUG) << "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap()); - ProtocolVersion protocolVersion {defaultServerProtocolVersion}; + std::string requestPath{ request.pathInfo() }; + if (StringUtils::stringEndsWith(requestPath, ".view")) + requestPath.resize(requestPath.length() - 5); - try - { - // We need to parse client a soon as possible to make sure to answer with the right protocol version - protocolVersion = getServerProtocolVersion(getMandatoryParameterAs(request.getParameterMap(), "c")); - RequestContext requestContext {buildRequestContext(request)}; + // Optional parameters + const ResponseFormat format{ getParameterAs(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml }; - auto itEntryPoint {requestEntryPoints.find(requestPath)}; - if (itEntryPoint != requestEntryPoints.end()) - { - if (itEntryPoint->second.checkFunc) - itEntryPoint->second.checkFunc(); + ProtocolVersion protocolVersion{ defaultServerProtocolVersion }; - checkUserTypeIsAllowed(requestContext, itEntryPoint->second.allowedUserTypes); + try + { + // We need to parse client a soon as possible to make sure to answer with the right protocol version + protocolVersion = getServerProtocolVersion(getMandatoryParameterAs(request.getParameterMap(), "c")); + RequestContext requestContext{ buildRequestContext(request) }; - Response resp {(itEntryPoint->second.func)(requestContext)}; + auto itEntryPoint{ requestEntryPoints.find(requestPath) }; + if (itEntryPoint != requestEntryPoints.end()) + { + if (itEntryPoint->second.checkFunc) + itEntryPoint->second.checkFunc(); - resp.write(response.out(), format); - response.setMimeType(ResponseFormatToMimeType(format)); + checkUserTypeIsAllowed(requestContext, itEntryPoint->second.allowedUserTypes); - LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!"; - return; - } + Response resp{ (itEntryPoint->second.func)(requestContext) }; - auto itStreamHandler {mediaRetrievalHandlers.find(requestPath)}; - if (itStreamHandler != mediaRetrievalHandlers.end()) - { - itStreamHandler->second(requestContext, request, response); - LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!"; - return; - } + resp.write(response.out(), format); + response.setMimeType(ResponseFormatToMimeType(format)); - LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'"; - throw UnknownEntryPointGenericError {}; - } - catch (const Error& e) - { - LMS_LOG(API_SUBSONIC, ERROR) << "Error while processing request '" << requestPath << "'" - << ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]" - << ", code = " << static_cast(e.getCode()) << ", msg = '" << e.getMessage() << "'"; - Response resp {Response::createFailedResponse(protocolVersion, e)}; - resp.write(response.out(), format); - response.setMimeType(ResponseFormatToMimeType(format)); - } -} + LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!"; + return; + } -ProtocolVersion -SubsonicResource::getServerProtocolVersion(const std::string& clientName) const -{ - auto it {_serverProtocolVersionsByClient.find(clientName)}; - if (it == std::cend(_serverProtocolVersionsByClient)) - return defaultServerProtocolVersion; + auto itStreamHandler{ mediaRetrievalHandlers.find(requestPath) }; + if (itStreamHandler != mediaRetrievalHandlers.end()) + { + itStreamHandler->second(requestContext, request, response); + LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!"; + return; + } - return it->second; -} + LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'"; + throw UnknownEntryPointGenericError{}; + } + catch (const Error& e) + { + LMS_LOG(API_SUBSONIC, ERROR) << "Error while processing request '" << requestPath << "'" + << ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]" + << ", code = " << static_cast(e.getCode()) << ", msg = '" << e.getMessage() << "'"; + Response resp{ Response::createFailedResponse(protocolVersion, e) }; + resp.write(response.out(), format); + response.setMimeType(ResponseFormatToMimeType(format)); + } + } -void -SubsonicResource::checkProtocolVersion(ProtocolVersion client, ProtocolVersion server) -{ - if (client.major > server.major) - throw ServerMustUpgradeError {}; - if (client.major < server.major) - throw ClientMustUpgradeError {}; - if (client.minor > server.minor) - throw ServerMustUpgradeError {}; - else if (client.minor == server.minor) - { - if (client.patch > server.patch) - throw ServerMustUpgradeError {}; - } -} + ProtocolVersion SubsonicResource::getServerProtocolVersion(const std::string& clientName) const + { + auto it{ _serverProtocolVersionsByClient.find(clientName) }; + if (it == std::cend(_serverProtocolVersionsByClient)) + return defaultServerProtocolVersion; -ClientInfo -SubsonicResource::getClientInfo(const Wt::Http::ParameterMap& parameters) -{ - ClientInfo res; + return it->second; + } - if (hasParameter(parameters, "t")) - throw TokenAuthenticationNotSupportedForLDAPUsersError {}; + void SubsonicResource::checkProtocolVersion(ProtocolVersion client, ProtocolVersion server) + { + if (client.major > server.major) + throw ServerMustUpgradeError{}; + if (client.major < server.major) + throw ClientMustUpgradeError{}; + if (client.minor > server.minor) + throw ServerMustUpgradeError{}; + else if (client.minor == server.minor) + { + if (client.patch > server.patch) + throw ServerMustUpgradeError{}; + } + } - // Mandatory parameters - res.name = getMandatoryParameterAs(parameters, "c"); - res.version = getMandatoryParameterAs(parameters, "v"); - res.user = getMandatoryParameterAs(parameters, "u"); - res.password = decodePasswordIfNeeded(getMandatoryParameterAs(parameters, "p")); + ClientInfo SubsonicResource::getClientInfo(const Wt::Http::ParameterMap& parameters) + { + ClientInfo res; - return res; -} + if (hasParameter(parameters, "t")) + throw TokenAuthenticationNotSupportedForLDAPUsersError{}; -RequestContext -SubsonicResource::buildRequestContext(const Wt::Http::Request& request) -{ - const Wt::Http::ParameterMap& parameters {request.getParameterMap()}; - const ClientInfo clientInfo {getClientInfo(parameters)}; - const Database::UserId userId {authenticateUser(request, clientInfo)}; + // Mandatory parameters + res.name = getMandatoryParameterAs(parameters, "c"); + res.version = getMandatoryParameterAs(parameters, "v"); + res.user = getMandatoryParameterAs(parameters, "u"); + res.password = decodePasswordIfNeeded(getMandatoryParameterAs(parameters, "p")); - return {parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name)}; -} + return res; + } -Database::UserId -SubsonicResource::authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo) -{ - if (auto *authEnvService {Service<::Auth::IEnvService>::get()}) - { - const auto checkResult {authEnvService->processRequest(request)}; - if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted) - throw UserNotAuthorizedError {}; + RequestContext SubsonicResource::buildRequestContext(const Wt::Http::Request& request) + { + const Wt::Http::ParameterMap& parameters{ request.getParameterMap() }; + const ClientInfo clientInfo{ getClientInfo(parameters) }; + const Database::UserId userId{ authenticateUser(request, clientInfo) }; - return *checkResult.userId; - } - else if (auto *authPasswordService {Service<::Auth::IPasswordService>::get()}) - { - const auto checkResult {authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), - clientInfo.user, clientInfo.password)}; + return { parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name) }; + } - switch (checkResult.state) - { - case Auth::IPasswordService::CheckResult::State::Granted: - return *checkResult.userId; - break; - case Auth::IPasswordService::CheckResult::State::Denied: - throw WrongUsernameOrPasswordError {}; - case Auth::IPasswordService::CheckResult::State::Throttled: - throw LoginThrottledGenericError {}; - } - } + Database::UserId SubsonicResource::authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo) + { + if (auto * authEnvService{ Service<::Auth::IEnvService>::get() }) + { + const auto checkResult{ authEnvService->processRequest(request) }; + if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted) + throw UserNotAuthorizedError{}; - throw InternalErrorGenericError {"No service avalaible to authenticate user"}; -} + return *checkResult.userId; + } + else if (auto * authPasswordService{ Service<::Auth::IPasswordService>::get() }) + { + const auto checkResult{ authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), clientInfo.user, clientInfo.password) }; + + switch (checkResult.state) + { + case Auth::IPasswordService::CheckResult::State::Granted: + return *checkResult.userId; + break; + case Auth::IPasswordService::CheckResult::State::Denied: + throw WrongUsernameOrPasswordError{}; + case Auth::IPasswordService::CheckResult::State::Throttled: + throw LoginThrottledGenericError{}; + } + } + + throw InternalErrorGenericError{ "No service avalaible to authenticate user" }; + } } // namespace api::subsonic diff --git a/src/libs/subsonic/impl/SubsonicResource.hpp b/src/libs/subsonic/impl/SubsonicResource.hpp index 24c0754b..643ca504 100644 --- a/src/libs/subsonic/impl/SubsonicResource.hpp +++ b/src/libs/subsonic/impl/SubsonicResource.hpp @@ -30,28 +30,28 @@ namespace Database { - class Db; + class Db; } namespace API::Subsonic { - class SubsonicResource final : public Wt::WResource - { - public: - SubsonicResource(Database::Db& db); + class SubsonicResource final : public Wt::WResource + { + public: + SubsonicResource(Database::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::ParameterMap& parameters); - RequestContext buildRequestContext(const Wt::Http::Request& request); - Database::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo); + static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server); + ClientInfo getClientInfo(const Wt::Http::ParameterMap& parameters); + RequestContext buildRequestContext(const Wt::Http::Request& request); + Database::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo); - const std::unordered_map _serverProtocolVersionsByClient; - Database::Db& _db; - }; + const std::unordered_map _serverProtocolVersionsByClient; + Database::Db& _db; + }; } // namespace diff --git a/src/libs/subsonic/impl/SubsonicResponse.cpp b/src/libs/subsonic/impl/SubsonicResponse.cpp index 2958291c..f96ce0dc 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.cpp +++ b/src/libs/subsonic/impl/SubsonicResponse.cpp @@ -34,249 +34,232 @@ namespace API::Subsonic { -std::string -ResponseFormatToMimeType(ResponseFormat format) -{ - switch (format) - { - case ResponseFormat::xml: return "text/xml"; - case ResponseFormat::json: return "application/json"; - } + std::string ResponseFormatToMimeType(ResponseFormat format) + { + switch (format) + { + case ResponseFormat::xml: return "text/xml"; + case ResponseFormat::json: return "application/json"; + } - return ""; -} + return ""; + } -void -Response::Node::setValue(std::string_view value) -{ - if (!_children.empty() || !_childrenArrays.empty()) - throw LmsException {"Node already has children"}; + void Response::Node::setValue(std::string_view value) + { + if (!_children.empty() || !_childrenArrays.empty()) + throw LmsException{ "Node already has children" }; - _value = std::string {value}; -} + _value = std::string{ value }; + } -void -Response::Node::setValue(long long value) -{ - if (!_children.empty() || !_childrenArrays.empty()) - throw LmsException {"Node already has children"}; + void Response::Node::setValue(long long value) + { + if (!_children.empty() || !_childrenArrays.empty()) + throw LmsException{ "Node already has children" }; - _value = value; -} + _value = value; + } -void -Response::Node::setAttribute(std::string_view key, std::string_view value) -{ - _attributes[std::string {key}] = std::string {value}; -} + void Response::Node::setAttribute(std::string_view key, std::string_view value) + { + _attributes[std::string{ key }] = std::string{ value }; + } -void -Response::Node::addChild(const std::string& key, Node node) -{ - if (_value) - throw LmsException {"Node already has a value"}; + void Response::Node::addChild(const std::string& key, Node node) + { + if (_value) + throw LmsException{ "Node already has a value" }; - _children[key].emplace_back(std::move(node)); -} + _children[key].emplace_back(std::move(node)); + } -void -Response::Node::addArrayChild(const std::string& key, Node node) -{ - if (_value) - throw LmsException {"Node already has a value"}; + void Response::Node::addArrayChild(const std::string& key, Node node) + { + if (_value) + throw LmsException{ "Node already has a value" }; - _childrenArrays[key].emplace_back(std::move(node)); -} + _childrenArrays[key].emplace_back(std::move(node)); + } -Response::Node& -Response::Node::createChild(const std::string& key) -{ - _children[key].emplace_back(); - return _children[key].back(); -} + Response::Node& Response::Node::createChild(const std::string& key) + { + _children[key].emplace_back(); + return _children[key].back(); + } -Response::Node& -Response::Node::createArrayChild(const std::string& key) -{ - _childrenArrays[key].emplace_back(); - return _childrenArrays[key].back(); -} + Response::Node& Response::Node::createArrayChild(const std::string& key) + { + _childrenArrays[key].emplace_back(); + return _childrenArrays[key].back(); + } -void -Response::Node::setVersionAttribute(ProtocolVersion protocolVersion) -{ - setAttribute("version", std::to_string(protocolVersion.major) + "." + std::to_string(protocolVersion.minor) + "." + std::to_string(protocolVersion.patch)); -} + void Response::Node::setVersionAttribute(ProtocolVersion protocolVersion) + { + setAttribute("version", std::to_string(protocolVersion.major) + "." + std::to_string(protocolVersion.minor) + "." + std::to_string(protocolVersion.patch)); + } -Response -Response::createOkResponse(ProtocolVersion protocolVersion) -{ - Response response; - Node& responseNode {response._root.createChild("subsonic-response")}; + Response Response::createOkResponse(ProtocolVersion protocolVersion) + { + Response response; + Node& responseNode{ response._root.createChild("subsonic-response") }; - responseNode.setAttribute("status", "ok"); - responseNode.setVersionAttribute(protocolVersion); - responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks + responseNode.setAttribute("status", "ok"); + responseNode.setVersionAttribute(protocolVersion); + responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks - return response; -} + return response; + } -Response -Response::createFailedResponse(ProtocolVersion protocolVersion, const Error& error) -{ - Response response; - Node& responseNode {response._root.createChild("subsonic-response")}; + Response Response::createFailedResponse(ProtocolVersion protocolVersion, const Error& error) + { + Response response; + Node& responseNode{ response._root.createChild("subsonic-response") }; - responseNode.setAttribute("status", "failed"); - responseNode.setVersionAttribute(protocolVersion); - responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks + responseNode.setAttribute("status", "failed"); + responseNode.setVersionAttribute(protocolVersion); + responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks - Node& errorNode {responseNode.createChild("error")}; - errorNode.setAttribute("code", static_cast(error.getCode())); - errorNode.setAttribute("message", error.getMessage()); + Node& errorNode{ responseNode.createChild("error") }; + errorNode.setAttribute("code", static_cast(error.getCode())); + errorNode.setAttribute("message", error.getMessage()); - return response; -} + return response; + } -void -Response::addNode(const std::string& key, Node node) -{ - return _root._children["subsonic-response"].front().addChild(key, std::move(node)); -} + void Response::addNode(const std::string& key, Node node) + { + return _root._children["subsonic-response"].front().addChild(key, std::move(node)); + } -Response::Node& -Response::createNode(const std::string& key) -{ - return _root._children["subsonic-response"].front().createChild(key); -} + Response::Node& Response::createNode(const std::string& key) + { + return _root._children["subsonic-response"].front().createChild(key); + } -Response::Node& -Response::createArrayNode(const std::string& key) -{ - return _root._children["subsonic-response"].front().createArrayChild(key); -} + Response::Node& Response::createArrayNode(const std::string& key) + { + return _root._children["subsonic-response"].front().createArrayChild(key); + } -void -Response::write(std::ostream& os, ResponseFormat format) -{ - switch (format) - { - case ResponseFormat::xml: - writeXML(os); - break; - case ResponseFormat::json: - writeJSON(os); - break; - } -} + void Response::write(std::ostream& os, ResponseFormat format) + { + switch (format) + { + case ResponseFormat::xml: + writeXML(os); + break; + case ResponseFormat::json: + writeJSON(os); + break; + } + } -void -Response::writeXML(std::ostream& os) -{ - std::function nodeToPropertyTree = [&] (const Response::Node& node) - { - boost::property_tree::ptree res; + void Response::writeXML(std::ostream& os) + { + std::function nodeToPropertyTree = [&](const Response::Node& node) + { + boost::property_tree::ptree res; - for (auto itAttribute : node._attributes) - { - if (std::holds_alternative(itAttribute.second)) - res.put("." + itAttribute.first, std::get(itAttribute.second)); - else if (std::holds_alternative(itAttribute.second)) - res.put("." + itAttribute.first, std::get(itAttribute.second)); - else if (std::holds_alternative(itAttribute.second)) - res.put("." + itAttribute.first, std::get(itAttribute.second)); - } + for (auto itAttribute : node._attributes) + { + if (std::holds_alternative(itAttribute.second)) + res.put("." + itAttribute.first, std::get(itAttribute.second)); + else if (std::holds_alternative(itAttribute.second)) + res.put("." + itAttribute.first, std::get(itAttribute.second)); + else if (std::holds_alternative(itAttribute.second)) + res.put("." + itAttribute.first, std::get(itAttribute.second)); + } - if (node._value) - { - const auto& value {*node._value}; + if (node._value) + { + const auto& value{ *node._value }; - if (std::holds_alternative(value)) - res.put_value(std::get(value)); - else if (std::holds_alternative(value)) - res.put_value(std::get(value)); - else if (std::holds_alternative(value)) - res.put_value(std::get(value)); - } - else - { - for (auto itChildNode : node._children) - { - for (const Response::Node& childNode : itChildNode.second) - res.add_child(itChildNode.first, nodeToPropertyTree(childNode)); - } + if (std::holds_alternative(value)) + res.put_value(std::get(value)); + else if (std::holds_alternative(value)) + res.put_value(std::get(value)); + else if (std::holds_alternative(value)) + res.put_value(std::get(value)); + } + else + { + for (auto itChildNode : node._children) + { + for (const Response::Node& childNode : itChildNode.second) + res.add_child(itChildNode.first, nodeToPropertyTree(childNode)); + } - for (auto itChildArrayNode : node._childrenArrays) - { - const std::vector& childArrayNodes {itChildArrayNode.second}; + for (auto itChildArrayNode : node._childrenArrays) + { + const std::vector& childArrayNodes{ itChildArrayNode.second }; - for (const Response::Node& childNode : childArrayNodes ) - res.add_child(itChildArrayNode.first, nodeToPropertyTree(childNode)); - } - } + for (const Response::Node& childNode : childArrayNodes) + res.add_child(itChildArrayNode.first, nodeToPropertyTree(childNode)); + } + } - return res; - }; + return res; + }; - boost::property_tree::ptree root {nodeToPropertyTree(_root)}; - boost::property_tree::write_xml(os, root); -} + boost::property_tree::ptree root{ nodeToPropertyTree(_root) }; + boost::property_tree::write_xml(os, root); + } -void -Response::writeJSON(std::ostream& os) -{ - namespace Json = Wt::Json; + void Response::writeJSON(std::ostream& os) + { + namespace Json = Wt::Json; - std::function nodeToJsonObject = [&] (const Response::Node& node) - { - Json::Object res; + std::function nodeToJsonObject = [&](const Response::Node& node) + { + Json::Object res; - auto valueToJsonValue {[](const Node::ValueType& value) -> Json::Value - { - if (std::holds_alternative(value)) - return Json::Value {std::get(value)}; - else if (std::holds_alternative(value)) - return Json::Value {std::get(value)}; - else if (std::holds_alternative(value)) - return Json::Value {std::get(value)}; + auto valueToJsonValue{ [](const Node::ValueType& value) -> Json::Value + { + if (std::holds_alternative(value)) + return Json::Value {std::get(value)}; + else if (std::holds_alternative(value)) + return Json::Value {std::get(value)}; + else if (std::holds_alternative(value)) + return Json::Value {std::get(value)}; - throw LmsException("Unexpected value type"); - }}; + throw LmsException("Unexpected value type"); + } }; - for (auto itAttribute : node._attributes) - res[itAttribute.first] = valueToJsonValue(itAttribute.second); + for (auto itAttribute : node._attributes) + res[itAttribute.first] = valueToJsonValue(itAttribute.second); - if (node._value) - { - res["value"] = valueToJsonValue(*node._value); - } - else - { - for (auto itChildNode : node._children) - { - for (const Response::Node& childNode : itChildNode.second) - res[itChildNode.first] = nodeToJsonObject(childNode); - } + if (node._value) + { + res["value"] = valueToJsonValue(*node._value); + } + else + { + for (auto itChildNode : node._children) + { + for (const Response::Node& childNode : itChildNode.second) + res[itChildNode.first] = nodeToJsonObject(childNode); + } - for (auto itChildArrayNode : node._childrenArrays) - { - const std::vector& childArrayNodes {itChildArrayNode .second}; + for (auto itChildArrayNode : node._childrenArrays) + { + const std::vector& childArrayNodes{ itChildArrayNode.second }; - Json::Array array; - for (const Response::Node& childNode : childArrayNodes ) - array.emplace_back(nodeToJsonObject(childNode)); + Json::Array array; + for (const Response::Node& childNode : childArrayNodes) + array.emplace_back(nodeToJsonObject(childNode)); - res[itChildArrayNode.first] = std::move(array); - } - } + res[itChildArrayNode.first] = std::move(array); + } + } - return res; - }; + return res; + }; - Json::Object root {nodeToJsonObject(_root)}; - os << Json::serialize(root); -} + Json::Object root{ nodeToJsonObject(_root) }; + os << Json::serialize(root); + } } // namespace diff --git a/src/libs/subsonic/impl/SubsonicResponse.hpp b/src/libs/subsonic/impl/SubsonicResponse.hpp index d2d5294f..df9344a1 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.hpp +++ b/src/libs/subsonic/impl/SubsonicResponse.hpp @@ -30,220 +30,220 @@ namespace API::Subsonic { -enum class ResponseFormat -{ - xml, - json, -}; + enum class ResponseFormat + { + xml, + json, + }; -std::string ResponseFormatToMimeType(ResponseFormat format); + std::string ResponseFormatToMimeType(ResponseFormat format); -class Error -{ - public: - enum class Code - { - Generic = 0, - RequiredParameterMissing = 10, - ClientMustUpgrade = 20, - ServerMustUpgrade = 30, - WrongUsernameOrPassword = 40, - TokenAuthenticationNotSupportedForLDAPUsers = 41, - UserNotAuthorized = 50, - RequestedDataNotFound = 70, - }; + class Error + { + public: + enum class Code + { + Generic = 0, + RequiredParameterMissing = 10, + ClientMustUpgrade = 20, + ServerMustUpgrade = 30, + WrongUsernameOrPassword = 40, + TokenAuthenticationNotSupportedForLDAPUsers = 41, + UserNotAuthorized = 50, + RequestedDataNotFound = 70, + }; - Error(Code code) : _code {code} {} + Error(Code code) : _code{ code } {} - virtual std::string getMessage() const = 0; + virtual std::string getMessage() const = 0; - Code getCode() const { return _code; } + Code getCode() const { return _code; } - private: - const Code _code; -}; + private: + const Code _code; + }; -class GenericError : public Error -{ - public: - GenericError() : Error {Code::Generic} {} -}; + class GenericError : public Error + { + public: + GenericError() : Error{ Code::Generic } {} + }; -class RequiredParameterMissingError : public Error -{ - public: - RequiredParameterMissingError(std::string_view param) - : Error {Code::RequiredParameterMissing} - , _param {param} - {} + class RequiredParameterMissingError : public Error + { + public: + RequiredParameterMissingError(std::string_view param) + : Error{ Code::RequiredParameterMissing } + , _param{ param } + {} - private: - std::string getMessage() const override { return "Required parameter '" + _param + "' is missing."; } - std::string _param; -}; + private: + std::string getMessage() const override { return "Required parameter '" + _param + "' is missing."; } + std::string _param; + }; -class ClientMustUpgradeError : public Error -{ - public: - ClientMustUpgradeError() : Error {Code::ClientMustUpgrade} {} - private: - std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; } -}; + class ClientMustUpgradeError : public Error + { + public: + ClientMustUpgradeError() : Error{ Code::ClientMustUpgrade } {} + private: + std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; } + }; -class ServerMustUpgradeError : public Error -{ - public: - ServerMustUpgradeError() : Error {Code::ServerMustUpgrade} {} - private: - std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; } -}; + class ServerMustUpgradeError : public Error + { + public: + ServerMustUpgradeError() : Error{ Code::ServerMustUpgrade } {} + private: + std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; } + }; -class WrongUsernameOrPasswordError : public Error -{ - public: - WrongUsernameOrPasswordError() : Error {Code::WrongUsernameOrPassword} {} - private: - std::string getMessage() const override { return "Wrong username or password."; } -}; + class WrongUsernameOrPasswordError : public Error + { + public: + WrongUsernameOrPasswordError() : Error{ Code::WrongUsernameOrPassword } {} + private: + std::string getMessage() const override { return "Wrong username or password."; } + }; -class TokenAuthenticationNotSupportedForLDAPUsersError : public Error -{ - public: - TokenAuthenticationNotSupportedForLDAPUsersError() : Error {Code::TokenAuthenticationNotSupportedForLDAPUsers} {} - private: - std::string getMessage() const override { return "Token authentication not supported for LDAP users."; } -}; + class TokenAuthenticationNotSupportedForLDAPUsersError : public Error + { + public: + TokenAuthenticationNotSupportedForLDAPUsersError() : Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {} + private: + std::string getMessage() const override { return "Token authentication not supported for LDAP users."; } + }; -class UserNotAuthorizedError : public Error -{ - public: - UserNotAuthorizedError () : Error {Code::UserNotAuthorized} {} - private: - std::string getMessage() const override { return "User is not authorized for the given operation."; } -}; + class UserNotAuthorizedError : public Error + { + public: + UserNotAuthorizedError() : Error{ Code::UserNotAuthorized } {} + private: + std::string getMessage() const override { return "User is not authorized for the given operation."; } + }; -class RequestedDataNotFoundError : public Error -{ - public: - RequestedDataNotFoundError() : Error {Code::RequestedDataNotFound} {} - private: - std::string getMessage() const override { return "The requested data was not found."; } -}; + class RequestedDataNotFoundError : public Error + { + public: + RequestedDataNotFoundError() : Error{ Code::RequestedDataNotFound } {} + private: + std::string getMessage() const override { return "The requested data was not found."; } + }; -class InternalErrorGenericError : public GenericError -{ - public: - InternalErrorGenericError(const std::string& message) : _message {message} {} - private: - std::string getMessage() const override { return "Internal error: " + _message; } - const std::string _message; -}; + class InternalErrorGenericError : public GenericError + { + public: + InternalErrorGenericError(const std::string& message) : _message{ message } {} + private: + std::string getMessage() const override { return "Internal error: " + _message; } + const std::string _message; + }; -class LoginThrottledGenericError : public GenericError -{ - std::string getMessage() const override { return "Login throttled, too many attempts"; } -}; + class LoginThrottledGenericError : public GenericError + { + std::string getMessage() const override { return "Login throttled, too many attempts"; } + }; -class NotImplementedGenericError : public GenericError -{ - std::string getMessage() const override { return "Not implemented"; } -}; + class NotImplementedGenericError : public GenericError + { + std::string getMessage() const override { return "Not implemented"; } + }; -class UnknownEntryPointGenericError : public GenericError -{ - std::string getMessage() const override { return "Unknown API method"; } -}; + class UnknownEntryPointGenericError : public GenericError + { + std::string getMessage() const override { return "Unknown API method"; } + }; -class PasswordTooWeakGenericError : public GenericError -{ - std::string getMessage() const override { return "Password too weak"; } -}; + class PasswordTooWeakGenericError : public GenericError + { + std::string getMessage() const override { return "Password too weak"; } + }; -class PasswordMustMatchLoginNameGenericError : public GenericError -{ - std::string getMessage() const override { return "Password must match login name"; } -}; + class PasswordMustMatchLoginNameGenericError : public GenericError + { + std::string getMessage() const override { return "Password must match login name"; } + }; -class DemoUserCannotChangePasswordGenericError : public GenericError -{ - std::string getMessage() const override { return "Demo user cannot change its password"; } -}; + class DemoUserCannotChangePasswordGenericError : public GenericError + { + std::string getMessage() const override { return "Demo user cannot change its password"; } + }; -class UserAlreadyExistsGenericError : public GenericError -{ - std::string getMessage() const override { return "User already exists"; } -}; + class UserAlreadyExistsGenericError : public GenericError + { + std::string getMessage() const override { return "User already exists"; } + }; -class BadParameterGenericError : public GenericError -{ - public: - BadParameterGenericError(const std::string& parameterName) : _parameterName {parameterName} {} + class BadParameterGenericError : public GenericError + { + public: + BadParameterGenericError(const std::string& parameterName) : _parameterName{ parameterName } {} - private: - std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; } + private: + std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; } - const std::string _parameterName; -}; + const std::string _parameterName; + }; -class Response -{ - public: - class Node - { - public: - void setAttribute(std::string_view key, std::string_view value); + class Response + { + public: + class Node + { + public: + void setAttribute(std::string_view key, std::string_view value); - template ::value>* = nullptr> - void setAttribute(std::string_view key, T value) - { - if constexpr (std::is_same::value) - _attributes[std::string {key}] = value; - else - _attributes[std::string {key}] = static_cast(value); - } + template ::value>* = nullptr> + void setAttribute(std::string_view key, T value) + { + if constexpr (std::is_same::value) + _attributes[std::string{ key }] = value; + else + _attributes[std::string{ key }] = static_cast(value); + } - // A Node has either a value or some children - void setValue(std::string_view value); - void setValue(long long value); - Node& createChild(const std::string& key); - Node& createArrayChild(const std::string& key); + // A Node has either a value or some children + void setValue(std::string_view value); + void setValue(long long value); + Node& createChild(const std::string& key); + Node& createArrayChild(const std::string& key); - void addChild(const std::string& key, Node node); - void addArrayChild(const std::string& key, Node node); + void addChild(const std::string& key, Node node); + void addArrayChild(const std::string& key, Node node); - private: - void setVersionAttribute(ProtocolVersion version); + private: + void setVersionAttribute(ProtocolVersion version); - friend class Response; - using ValueType = std::variant; - std::map _attributes; - std::optional _value; - std::map> _children; - std::map> _childrenArrays; - }; + friend class Response; + using ValueType = std::variant; + std::map _attributes; + std::optional _value; + std::map> _children; + std::map> _childrenArrays; + }; - static Response createOkResponse(ProtocolVersion protocolVersion); - static Response createFailedResponse(ProtocolVersion protocolVersion, const Error& error); + static Response createOkResponse(ProtocolVersion protocolVersion); + static Response createFailedResponse(ProtocolVersion protocolVersion, const Error& error); - virtual ~Response() {} - Response(const Response&) = delete; - Response& operator=(const Response&) = delete; - Response(Response&&) = default; - Response& operator=(Response&&) = default; + virtual ~Response() {} + Response(const Response&) = delete; + Response& operator=(const Response&) = delete; + Response(Response&&) = default; + Response& operator=(Response&&) = default; - void addNode(const std::string& key, Node node); - Node& createNode(const std::string& key); - Node& createArrayNode(const std::string& key); + void addNode(const std::string& key, Node node); + Node& createNode(const std::string& key); + Node& createArrayNode(const std::string& key); - void write(std::ostream& os, ResponseFormat format); + void write(std::ostream& os, ResponseFormat format); - private: - void writeJSON(std::ostream& os); - void writeXML(std::ostream& os); + private: + void writeJSON(std::ostream& os); + void writeXML(std::ostream& os); - Response() = default; - Node _root; -}; + Response() = default; + Node _root; + }; } // namespace diff --git a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp index b5087db3..b328553e 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp @@ -24,48 +24,48 @@ namespace API::Subsonic::Scan { - using namespace Scanner; + using namespace Scanner; - namespace - { - Response::Node - createStatusResponseNode() - { - Response::Node statusResponse; + namespace + { + Response::Node + createStatusResponseNode() + { + Response::Node statusResponse; - const IScannerService::Status scanStatus{ Service::get()->getStatus() }; + const IScannerService::Status scanStatus{ Service::get()->getStatus() }; - statusResponse.setAttribute("scanning", scanStatus.currentState == IScannerService::State::InProgress); - if (scanStatus.currentState == IScannerService::State::InProgress) - { - std::size_t count{}; + statusResponse.setAttribute("scanning", scanStatus.currentState == IScannerService::State::InProgress); + if (scanStatus.currentState == IScannerService::State::InProgress) + { + std::size_t count{}; - if (scanStatus.currentScanStepStats && scanStatus.currentScanStepStats->currentStep == ScanStep::ScanningFiles) - count = scanStatus.currentScanStepStats->processedElems; + if (scanStatus.currentScanStepStats && scanStatus.currentScanStepStats->currentStep == ScanStep::ScanningFiles) + count = scanStatus.currentScanStepStats->processedElems; - statusResponse.setAttribute("count", count); - } + statusResponse.setAttribute("count", count); + } - return statusResponse; - } - } + return statusResponse; + } + } - Response handleGetScanStatus(RequestContext& context) - { - Response response{ Response::createOkResponse(context.serverProtocolVersion) }; - response.addNode("scanStatus", createStatusResponseNode()); + Response handleGetScanStatus(RequestContext& context) + { + Response response{ Response::createOkResponse(context.serverProtocolVersion) }; + response.addNode("scanStatus", createStatusResponseNode()); - return response; - } + return response; + } - Response handleStartScan(RequestContext& context) - { - Service::get()->requestImmediateScan(false); + Response handleStartScan(RequestContext& context) + { + Service::get()->requestImmediateScan(false); - Response response{ Response::createOkResponse(context.serverProtocolVersion) }; - response.addNode("scanStatus", createStatusResponseNode()); + Response response{ Response::createOkResponse(context.serverProtocolVersion) }; + response.addNode("scanStatus", createStatusResponseNode()); - return response; - } + return response; + } } diff --git a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp index 3074a7b5..3fd5329c 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp +++ b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp @@ -24,7 +24,7 @@ namespace API::Subsonic::Scan { - Response handleGetScanStatus(RequestContext& context); - Response handleStartScan(RequestContext& context); + Response handleGetScanStatus(RequestContext& context); + Response handleStartScan(RequestContext& context); } diff --git a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp index 96e3436e..3a0db100 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp @@ -37,164 +37,164 @@ using namespace Database; namespace API::Subsonic { - namespace { - Av::Format userTranscodeFormatToAvFormat(AudioFormat format) - { - switch (format) - { - case AudioFormat::MP3: return Av::Format::MP3; - case AudioFormat::OGG_OPUS: return Av::Format::OGG_OPUS; - case AudioFormat::MATROSKA_OPUS: return Av::Format::MATROSKA_OPUS; - case AudioFormat::OGG_VORBIS: return Av::Format::OGG_VORBIS; - case AudioFormat::WEBM_VORBIS: return Av::Format::WEBM_VORBIS; - default: return Av::Format::OGG_OPUS; - } - } + namespace { + Av::Format userTranscodeFormatToAvFormat(AudioFormat format) + { + switch (format) + { + case AudioFormat::MP3: return Av::Format::MP3; + case AudioFormat::OGG_OPUS: return Av::Format::OGG_OPUS; + case AudioFormat::MATROSKA_OPUS: return Av::Format::MATROSKA_OPUS; + case AudioFormat::OGG_VORBIS: return Av::Format::OGG_VORBIS; + case AudioFormat::WEBM_VORBIS: return Av::Format::WEBM_VORBIS; + default: return Av::Format::OGG_OPUS; + } + } - struct StreamParameters - { - Av::InputFileParameters inputFileParameters; - std::optional transcodeParameters; - bool estimateContentLength{}; - }; + struct StreamParameters + { + Av::InputFileParameters inputFileParameters; + std::optional transcodeParameters; + bool estimateContentLength{}; + }; - StreamParameters getStreamParameters(RequestContext& context) - { - // Mandatory params - const TrackId id{ getMandatoryParameterAs(context.parameters, "id") }; + StreamParameters getStreamParameters(RequestContext& context) + { + // Mandatory params + const TrackId id{ getMandatoryParameterAs(context.parameters, "id") }; - // Optional params - std::optional maxBitRate{ getParameterAs(context.parameters, "maxBitRate") }; - std::optional format{ getParameterAs(context.parameters, "format") }; - bool estimateContentLength{ getParameterAs(context.parameters, "estimateContentLength").value_or(false) }; + // Optional params + std::optional maxBitRate{ getParameterAs(context.parameters, "maxBitRate") }; + std::optional format{ getParameterAs(context.parameters, "format") }; + bool estimateContentLength{ getParameterAs(context.parameters, "estimateContentLength").value_or(false) }; - StreamParameters parameters; + StreamParameters parameters; - parameters.estimateContentLength = estimateContentLength; + parameters.estimateContentLength = estimateContentLength; - auto transaction{ context.dbSession.createSharedTransaction() }; + auto transaction{ context.dbSession.createSharedTransaction() }; - { - auto track{ Track::find(context.dbSession, id) }; - if (!track) - throw RequestedDataNotFoundError{}; + { + auto track{ Track::find(context.dbSession, id) }; + if (!track) + throw RequestedDataNotFoundError{}; - parameters.inputFileParameters.trackPath = track->getPath(); - parameters.inputFileParameters.duration = track->getDuration(); - } + parameters.inputFileParameters.trackPath = track->getPath(); + parameters.inputFileParameters.duration = track->getDuration(); + } - { - const User::pointer user{ User::find(context.dbSession, context.userId) }; - if (!user) - throw UserNotAuthorizedError{}; + { + const User::pointer user{ User::find(context.dbSession, context.userId) }; + if (!user) + throw UserNotAuthorizedError{}; - // format = "raw" => no transcode. Other format values will be ignored - const bool transcode{ (!format || (*format != "raw")) && user->getSubsonicTranscodeEnable() }; - if (transcode) - { - std::size_t bitRate{ user->getSubsonicTranscodeBitrate() / 1000 }; + // format = "raw" => no transcode. Other format values will be ignored + const bool transcode{ (!format || (*format != "raw")) && user->getSubsonicTranscodeEnable() }; + if (transcode) + { + std::size_t bitRate{ user->getSubsonicTranscodeBitrate() / 1000 }; - // "If set to zero, no limit is imposed" - if (maxBitRate && *maxBitRate != 0) - bitRate = Utils::clamp(*maxBitRate, std::size_t{ 48 }, bitRate); + // "If set to zero, no limit is imposed" + if (maxBitRate && *maxBitRate != 0) + bitRate = Utils::clamp(*maxBitRate, std::size_t{ 48 }, bitRate); - Av::TranscodeParameters transcodeParameters; + Av::TranscodeParameters transcodeParameters; - transcodeParameters.bitrate = bitRate * 1000; - transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicTranscodeFormat()); - transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) + transcodeParameters.bitrate = bitRate * 1000; + transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicTranscodeFormat()); + transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) - parameters.transcodeParameters = std::move(transcodeParameters); - } - } + parameters.transcodeParameters = std::move(transcodeParameters); + } + } - return parameters; - } - } + return parameters; + } + } - void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response) - { - std::shared_ptr resourceHandler; + void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response) + { + std::shared_ptr resourceHandler; - Wt::Http::ResponseContinuation* continuation{ request.continuation() }; - if (!continuation) - { - // Mandatory params - Database::TrackId id{ getMandatoryParameterAs(context.parameters, "id") }; + Wt::Http::ResponseContinuation* continuation{ request.continuation() }; + if (!continuation) + { + // Mandatory params + Database::TrackId id{ getMandatoryParameterAs(context.parameters, "id") }; - std::filesystem::path trackPath; - { - auto transaction{ context.dbSession.createSharedTransaction() }; + std::filesystem::path trackPath; + { + auto transaction{ context.dbSession.createSharedTransaction() }; - auto track{ Track::find(context.dbSession, id) }; - if (!track) - throw RequestedDataNotFoundError{}; + auto track{ Track::find(context.dbSession, id) }; + if (!track) + throw RequestedDataNotFoundError{}; - trackPath = track->getPath(); - } + trackPath = track->getPath(); + } - resourceHandler = createFileResourceHandler(trackPath); - } - else - { - resourceHandler = Wt::cpp17::any_cast>(continuation->data()); - } + resourceHandler = createFileResourceHandler(trackPath); + } + else + { + resourceHandler = Wt::cpp17::any_cast>(continuation->data()); + } - continuation = resourceHandler->processRequest(request, response); - if (continuation) - continuation->setData(resourceHandler); - } + continuation = resourceHandler->processRequest(request, response); + if (continuation) + continuation->setData(resourceHandler); + } - void handleStream(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response) - { - std::shared_ptr resourceHandler; + void handleStream(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response) + { + std::shared_ptr resourceHandler; - try - { - Wt::Http::ResponseContinuation* continuation = request.continuation(); - if (!continuation) - { - StreamParameters streamParameters{ getStreamParameters(context) }; - if (streamParameters.transcodeParameters) - resourceHandler = Av::createTranscodeResourceHandler(streamParameters.inputFileParameters, *streamParameters.transcodeParameters, streamParameters.estimateContentLength); - else - resourceHandler = createFileResourceHandler(streamParameters.inputFileParameters.trackPath); - } - else - { - resourceHandler = Wt::cpp17::any_cast>(continuation->data()); - } + try + { + Wt::Http::ResponseContinuation* continuation = request.continuation(); + if (!continuation) + { + StreamParameters streamParameters{ getStreamParameters(context) }; + if (streamParameters.transcodeParameters) + resourceHandler = Av::createTranscodeResourceHandler(streamParameters.inputFileParameters, *streamParameters.transcodeParameters, streamParameters.estimateContentLength); + else + resourceHandler = createFileResourceHandler(streamParameters.inputFileParameters.trackPath); + } + else + { + resourceHandler = Wt::cpp17::any_cast>(continuation->data()); + } - continuation = resourceHandler->processRequest(request, response); - if (continuation) - continuation->setData(resourceHandler); - } - catch (const Av::Exception& e) - { - LMS_LOG(API_SUBSONIC, ERROR) << "Caught Av exception: " << e.what(); - } - } + continuation = resourceHandler->processRequest(request, response); + if (continuation) + continuation->setData(resourceHandler); + } + catch (const Av::Exception& e) + { + LMS_LOG(API_SUBSONIC, ERROR) << "Caught Av exception: " << e.what(); + } + } - void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response) - { - // Mandatory params - const auto trackId{ getParameterAs(context.parameters, "id") }; - const auto releaseId{ getParameterAs(context.parameters, "id") }; + void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response) + { + // Mandatory params + const auto trackId{ getParameterAs(context.parameters, "id") }; + const auto releaseId{ getParameterAs(context.parameters, "id") }; - if (!trackId && !releaseId) - throw BadParameterGenericError{ "id" }; + if (!trackId && !releaseId) + throw BadParameterGenericError{ "id" }; - std::size_t size{ getParameterAs(context.parameters, "size").value_or(1024) }; - size = ::Utils::clamp(size, std::size_t{ 32 }, std::size_t{ 2048 }); + std::size_t size{ getParameterAs(context.parameters, "size").value_or(1024) }; + size = ::Utils::clamp(size, std::size_t{ 32 }, std::size_t{ 2048 }); - std::shared_ptr cover; - if (trackId) - cover = Service::get()->getFromTrack(*trackId, size); - else if (releaseId) - cover = Service::get()->getFromRelease(*releaseId, size); + std::shared_ptr cover; + if (trackId) + cover = Service::get()->getFromTrack(*trackId, size); + else if (releaseId) + cover = Service::get()->getFromRelease(*releaseId, size); - response.out().write(reinterpret_cast(cover->getData()), cover->getDataSize()); - response.setMimeType(std::string{ cover->getMimeType() }); - } + response.out().write(reinterpret_cast(cover->getData()), cover->getDataSize()); + response.setMimeType(std::string{ cover->getMimeType() }); + } } // namespace API::Subsonic diff --git a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp index 6033d290..4d2944a4 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp +++ b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp @@ -26,8 +26,8 @@ namespace 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); + 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); } diff --git a/src/libs/subsonic/impl/responses/Genre.cpp b/src/libs/subsonic/impl/responses/Genre.cpp index 1e274ac5..05d5ca48 100644 --- a/src/libs/subsonic/impl/responses/Genre.cpp +++ b/src/libs/subsonic/impl/responses/Genre.cpp @@ -23,14 +23,14 @@ namespace API::Subsonic { - Response::Node createGenreNode(const Database::Cluster::pointer& cluster) - { - Response::Node clusterNode; + Response::Node createGenreNode(const Database::Cluster::pointer& cluster) + { + Response::Node clusterNode; - clusterNode.setValue(cluster->getName()); - clusterNode.setAttribute("songCount", cluster->getTracksCount()); - clusterNode.setAttribute("albumCount", cluster->getReleasesCount()); + clusterNode.setValue(cluster->getName()); + clusterNode.setAttribute("songCount", cluster->getTracksCount()); + clusterNode.setAttribute("albumCount", cluster->getReleasesCount()); - return clusterNode; - } + return clusterNode; + } } \ No newline at end of file diff --git a/src/libs/utils/impl/String.cpp b/src/libs/utils/impl/String.cpp index f214dcb2..96443f07 100644 --- a/src/libs/utils/impl/String.cpp +++ b/src/libs/utils/impl/String.cpp @@ -33,267 +33,267 @@ namespace StringUtils { - bool readList(const std::string& str, const std::string& separators, std::list& results) - { - std::string curStr; + bool readList(const std::string& str, const std::string& separators, std::list& results) + { + std::string curStr; - for (char c : str) - { - if (separators.find(c) != std::string::npos) { - if (!curStr.empty()) { - results.push_back(curStr); - curStr.clear(); - } - } - else { - if (curStr.empty() && std::isspace(c)) - continue; + for (char c : str) + { + if (separators.find(c) != std::string::npos) { + if (!curStr.empty()) { + results.push_back(curStr); + curStr.clear(); + } + } + else { + if (curStr.empty() && std::isspace(c)) + continue; - curStr.push_back(c); - } - } + curStr.push_back(c); + } + } - if (!curStr.empty()) - results.push_back(curStr); + if (!curStr.empty()) + results.push_back(curStr); - return !str.empty(); - } + return !str.empty(); + } - template<> - std::optional readAs(std::string_view str) - { - return std::string{ str }; - } + template<> + std::optional readAs(std::string_view str) + { + return std::string{ str }; + } - template<> - std::optional readAs(std::string_view str) - { - return str; - } + template<> + std::optional readAs(std::string_view str) + { + return str; + } - template<> - std::optional readAs(std::string_view str) - { - if (str == "1" || str == "true") - return true; - else if (str == "0" || str == "false") - return false; + template<> + std::optional readAs(std::string_view str) + { + if (str == "1" || str == "true") + return true; + else if (str == "0" || str == "false") + return false; - return std::nullopt; - } + return std::nullopt; + } - std::vector splitStringCopy(std::string_view string, std::string_view separators) - { - std::string str{ stringTrim(string, separators) }; + std::vector splitStringCopy(std::string_view string, std::string_view separators) + { + std::string str{ stringTrim(string, separators) }; - std::vector res; - boost::algorithm::split(res, str, boost::is_any_of(separators), boost::token_compress_on); + std::vector res; + boost::algorithm::split(res, str, boost::is_any_of(separators), boost::token_compress_on); - return res; - } + return res; + } - std::vector splitString(std::string_view str, std::string_view separators) - { - std::vector res; + std::vector splitString(std::string_view str, std::string_view separators) + { + std::vector res; - std::string_view::size_type strBegin{}; + std::string_view::size_type strBegin{}; - while ((strBegin = str.find_first_not_of(separators, strBegin)) != std::string_view::npos) - { - auto strEnd{ str.find_first_of(separators, strBegin + 1) }; - if (strEnd == std::string_view::npos) - { - res.push_back(str.substr(strBegin, str.size() - strBegin)); - break; - } + while ((strBegin = str.find_first_not_of(separators, strBegin)) != std::string_view::npos) + { + auto strEnd{ str.find_first_of(separators, strBegin + 1) }; + if (strEnd == std::string_view::npos) + { + res.push_back(str.substr(strBegin, str.size() - strBegin)); + break; + } - res.push_back(str.substr(strBegin, strEnd - strBegin)); - strBegin = strEnd + 1; - } + res.push_back(str.substr(strBegin, strEnd - strBegin)); + strBegin = strEnd + 1; + } - return res; - } + return res; + } - std::string joinStrings(const std::vector& strings, const std::string& delimiter) - { - return boost::algorithm::join(strings, delimiter); - } + std::string joinStrings(const std::vector& strings, const std::string& delimiter) + { + return boost::algorithm::join(strings, delimiter); + } - std::string_view stringTrim(std::string_view str, std::string_view whitespaces) - { - std::string_view res; + std::string_view stringTrim(std::string_view str, std::string_view whitespaces) + { + std::string_view res; - const auto strBegin = str.find_first_not_of(whitespaces); - if (strBegin != std::string_view::npos) - { - const auto strEnd{ str.find_last_not_of(whitespaces) }; - const auto strRange{ strEnd - strBegin + 1 }; + const auto strBegin = str.find_first_not_of(whitespaces); + if (strBegin != std::string_view::npos) + { + const auto strEnd{ str.find_last_not_of(whitespaces) }; + const auto strRange{ strEnd - strBegin + 1 }; - res = str.substr(strBegin, strRange); - } + res = str.substr(strBegin, strRange); + } - return res; - } + return res; + } - std::string_view stringTrimEnd(std::string_view str, std::string_view whitespaces) - { - return str.substr(0, str.find_last_not_of(whitespaces) + 1); - } + std::string_view stringTrimEnd(std::string_view str, std::string_view whitespaces) + { + return str.substr(0, str.find_last_not_of(whitespaces) + 1); + } - std::string stringToLower(std::string_view str) - { - std::string res; - res.reserve(str.size()); + std::string stringToLower(std::string_view str) + { + std::string res; + res.reserve(str.size()); - std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](unsigned char c) { return std::tolower(c);}); + std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](unsigned char c) { return std::tolower(c);}); - return res; - } + return res; + } - void stringToLower(std::string& str) - { - std::transform(std::cbegin(str), std::cend(str), std::begin(str), [](unsigned char c) { return std::tolower(c);}); - } + void stringToLower(std::string& str) + { + std::transform(std::cbegin(str), std::cend(str), std::begin(str), [](unsigned char c) { return std::tolower(c);}); + } - std::string stringToUpper(const std::string& str) - { - std::string res; - res.reserve(str.size()); + std::string stringToUpper(const std::string& str) + { + std::string res; + res.reserve(str.size()); - std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](char c) { return std::toupper(c);}); + std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](char c) { return std::toupper(c);}); - return res; - } + return res; + } - std::string bufferToString(const std::vector& data) - { - std::ostringstream oss; + std::string bufferToString(const std::vector& data) + { + std::ostringstream oss; - for (unsigned char c : data) - { - oss << std::setw(2) << std::setfill('0') << std::hex << (int)c; - } + for (unsigned char c : data) + { + oss << std::setw(2) << std::setfill('0') << std::hex << (int)c; + } - return oss.str(); - } + return oss.str(); + } - void capitalize(std::string& str) - { - for (auto it{ std::begin(str) }; it != std::end(str); ++it) - { - if (std::isspace(*it)) - continue; + void capitalize(std::string& str) + { + for (auto it{ std::begin(str) }; it != std::end(str); ++it) + { + if (std::isspace(*it)) + continue; - if (std::isalpha(*it)) - *it = std::toupper(*it); + if (std::isalpha(*it)) + *it = std::toupper(*it); - break; - } - } + break; + } + } - std::string replaceInString(std::string_view str, const std::string& from, const std::string& to) - { - std::string res{ str }; - size_t pos = 0; + std::string replaceInString(std::string_view str, const std::string& from, const std::string& to) + { + std::string res{ str }; + size_t pos = 0; - while ((pos = res.find(from, pos)) != std::string::npos) - { - res.replace(pos, from.length(), to); - pos += to.length(); - } + while ((pos = res.find(from, pos)) != std::string::npos) + { + res.replace(pos, from.length(), to); + pos += to.length(); + } - return res; - } + return res; + } - std::string jsEscape(const std::string& str) - { - static const std::unordered_map escapeMap - { - { '\\', "\\\\" }, - { '\n', "\\n" }, - { '\r', "\\r" }, - { '\t', "\\t" }, - { '"', "\\\"" }, - { '\'', "\\\'" }, - }; + std::string jsEscape(const std::string& str) + { + static const std::unordered_map escapeMap + { + { '\\', "\\\\" }, + { '\n', "\\n" }, + { '\r', "\\r" }, + { '\t', "\\t" }, + { '"', "\\\"" }, + { '\'', "\\\'" }, + }; - std::string escaped; - escaped.reserve(str.length()); + std::string escaped; + escaped.reserve(str.length()); - for (const char c : str) - { - auto it{ escapeMap.find(c) }; - if (it == std::cend(escapeMap)) - { - escaped += c; - continue; - } + for (const char c : str) + { + auto it{ escapeMap.find(c) }; + if (it == std::cend(escapeMap)) + { + escaped += c; + continue; + } - escaped += it->second; - } + escaped += it->second; + } - return escaped; - } + return escaped; + } - std::string escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar) - { - std::string res; - res.reserve(str.size()); + std::string escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar) + { + std::string res; + res.reserve(str.size()); - for (const char c : str) - { - if (std::any_of(std::cbegin(charsToEscape), std::cend(charsToEscape), [c](char charToEscape) { return c == charToEscape; })) - res += escapeChar; + for (const char c : str) + { + if (std::any_of(std::cbegin(charsToEscape), std::cend(charsToEscape), [c](char charToEscape) { return c == charToEscape; })) + res += escapeChar; - res += c; - } + res += c; + } - return res; - } + return res; + } - bool stringEndsWith(const std::string& str, const std::string& ending) - { - return boost::algorithm::ends_with(str, ending); - } + bool stringEndsWith(const std::string& str, const std::string& ending) + { + return boost::algorithm::ends_with(str, ending); + } - std::optional stringFromHex(const std::string& str) - { - static const char lut[]{ "0123456789ABCDEF" }; + std::optional stringFromHex(const std::string& str) + { + static const char lut[]{ "0123456789ABCDEF" }; - if (str.length() % 2 != 0) - return std::nullopt; + if (str.length() % 2 != 0) + return std::nullopt; - std::string res; - res.reserve(str.length() / 2); + std::string res; + res.reserve(str.length() / 2); - auto it{ std::cbegin(str) }; - while (it != std::cend(str)) - { - unsigned val{}; + auto it{ std::cbegin(str) }; + while (it != std::cend(str)) + { + unsigned val{}; - auto itHigh{ std::lower_bound(std::cbegin(lut), std::cend(lut), std::toupper(*(it++))) }; - auto itLow{ std::lower_bound(std::cbegin(lut), std::cend(lut), std::toupper(*(it++))) }; + auto itHigh{ std::lower_bound(std::cbegin(lut), std::cend(lut), std::toupper(*(it++))) }; + auto itLow{ std::lower_bound(std::cbegin(lut), std::cend(lut), std::toupper(*(it++))) }; - if (itHigh == std::cend(lut) || itLow == std::cend(lut)) - return {}; + if (itHigh == std::cend(lut) || itLow == std::cend(lut)) + return {}; - val = std::distance(std::cbegin(lut), itHigh) << 4; - val += std::distance(std::cbegin(lut), itLow); + val = std::distance(std::cbegin(lut), itHigh) << 4; + val += std::distance(std::cbegin(lut), itLow); - res.push_back(static_cast(val)); - } + res.push_back(static_cast(val)); + } - return res; - } + return res; + } - std::string toISO8601String(const Wt::WDateTime& dateTime) - { - return dateTime.toString("yyyy-MM-ddThh:mm:ss.zzz", false).toUTF8(); - } - - std::string toISO8601String(const Wt::WDate& date) - { - return date.toString("yyyy-MM-dd").toUTF8(); - } + std::string toISO8601String(const Wt::WDateTime& dateTime) + { + return dateTime.toString("yyyy-MM-ddThh:mm:ss.zzz", false).toUTF8(); + } + + std::string toISO8601String(const Wt::WDate& date) + { + return date.toString("yyyy-MM-dd").toUTF8(); + } } // StringUtils diff --git a/src/libs/utils/include/utils/String.hpp b/src/libs/utils/include/utils/String.hpp index c5741866..670ea89a 100644 --- a/src/libs/utils/include/utils/String.hpp +++ b/src/libs/utils/include/utils/String.hpp @@ -31,65 +31,65 @@ namespace Wt { - class WDate; - class WDateTime; + class WDate; + class WDateTime; } namespace StringUtils { - [[nodiscard]] std::vector splitStringCopy(std::string_view string, std::string_view separators); + [[nodiscard]] std::vector splitStringCopy(std::string_view string, std::string_view separators); - [[nodiscard]] std::vector splitString(std::string_view string, std::string_view separators); + [[nodiscard]] std::vector splitString(std::string_view string, std::string_view separators); - [[nodiscard]] std::string joinStrings(const std::vector& strings, const std::string& delimiter); + [[nodiscard]] std::string joinStrings(const std::vector& strings, const std::string& delimiter); - [[nodiscard]] std::string_view stringTrim(std::string_view str, std::string_view whitespaces = " \t"); + [[nodiscard]] std::string_view stringTrim(std::string_view str, std::string_view whitespaces = " \t"); - [[nodiscard]] std::string_view stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t"); + [[nodiscard]] std::string_view stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t"); - [[nodiscard]] std::string stringToLower(std::string_view str); + [[nodiscard]] std::string stringToLower(std::string_view str); - void stringToLower(std::string& str); + void stringToLower(std::string& str); - [[nodiscard]] std::string stringToUpper(const std::string& str); + [[nodiscard]] std::string stringToUpper(const std::string& str); - [[nodiscard]] std::string bufferToString(const std::vector& data); + [[nodiscard]] std::string bufferToString(const std::vector& data); - void capitalize(std::string& str); + void capitalize(std::string& str); - template - [[nodiscard]] std::optional readAs(std::string_view str) - { - T res; + template + [[nodiscard]] std::optional readAs(std::string_view str) + { + T res; - std::istringstream iss{ std::string {str} }; - iss >> res; - if (iss.fail()) - return std::nullopt; + std::istringstream iss{ std::string {str} }; + iss >> res; + if (iss.fail()) + return std::nullopt; - return res; - } + return res; + } - template<> - [[nodiscard]] std::optional readAs(std::string_view str); + template<> + [[nodiscard]] std::optional readAs(std::string_view str); - template<> - [[nodiscard]] std::optional readAs(std::string_view str); + template<> + [[nodiscard]] std::optional readAs(std::string_view str); - template<> - [[nodiscard]] std::optional readAs(std::string_view str); + template<> + [[nodiscard]] std::optional readAs(std::string_view str); - [[nodiscard]] std::string replaceInString(std::string_view str, const std::string& from, const std::string& to); + [[nodiscard]] std::string replaceInString(std::string_view str, const std::string& from, const std::string& to); - [[nodiscard]] std::string jsEscape(const std::string& str); + [[nodiscard]] std::string jsEscape(const std::string& str); - [[nodiscard]] std::string escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar); + [[nodiscard]] std::string escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar); - [[nodiscard]] bool stringEndsWith(const std::string& str, const std::string& ending); + [[nodiscard]] bool stringEndsWith(const std::string& str, const std::string& ending); - [[nodiscard]] std::optional stringFromHex(const std::string& str); + [[nodiscard]] std::optional stringFromHex(const std::string& str); - [[nodiscard]] std::string toISO8601String(const Wt::WDateTime& dateTime); - [[nodiscard]] std::string toISO8601String(const Wt::WDate& date); + [[nodiscard]] std::string toISO8601String(const Wt::WDateTime& dateTime); + [[nodiscard]] std::string toISO8601String(const Wt::WDate& date); } // StringUtils \ No newline at end of file