Changed coding style

This commit is contained in:
emeric
2023-10-02 20:32:28 +02:00
parent 4fbacd691d
commit 7c7fbba319
18 changed files with 1265 additions and 1311 deletions
+5 -6
View File
@@ -22,29 +22,28 @@
namespace StringUtils namespace StringUtils
{ {
template<> template<>
std::optional<API::Subsonic::ProtocolVersion> std::optional<API::Subsonic::ProtocolVersion> readAs(std::string_view str)
readAs(std::string_view str)
{ {
// Expects "X.Y.Z" // Expects "X.Y.Z"
const auto numbers {StringUtils::splitString(str, ".")}; const auto numbers{ StringUtils::splitString(str, ".") };
if (numbers.size() < 2 || numbers.size() > 3) if (numbers.size() < 2 || numbers.size() > 3)
return std::nullopt; return std::nullopt;
API::Subsonic::ProtocolVersion version; API::Subsonic::ProtocolVersion version;
auto number {StringUtils::readAs<unsigned>(numbers[0])}; auto number{ StringUtils::readAs<unsigned>(numbers[0]) };
if (!number) if (!number)
return std::nullopt; return std::nullopt;
version.major = *number; version.major = *number;
number = {StringUtils::readAs<unsigned>(numbers[1])}; number = { StringUtils::readAs<unsigned>(numbers[1]) };
if (!number) if (!number)
return std::nullopt; return std::nullopt;
version.minor = *number; version.minor = *number;
if (numbers.size() == 3) if (numbers.size() == 3)
{ {
number = {StringUtils::readAs<unsigned>(numbers[2])}; number = { StringUtils::readAs<unsigned>(numbers[2]) };
if (!number) if (!number)
return std::nullopt; return std::nullopt;
version.patch = *number; version.patch = *number;
+6 -5
View File
@@ -25,16 +25,17 @@ namespace API::Subsonic
{ {
struct ProtocolVersion struct ProtocolVersion
{ {
unsigned major {}; unsigned major{};
unsigned minor {}; unsigned minor{};
unsigned patch {}; unsigned patch{};
}; };
static inline constexpr ProtocolVersion defaultServerProtocolVersion {1, 16, 0}; static inline constexpr ProtocolVersion defaultServerProtocolVersion{ 1, 16, 0 };
} }
namespace StringUtils namespace StringUtils
{ {
template<> std::optional<API::Subsonic::ProtocolVersion> readAs(std::string_view str); template<>
std::optional<API::Subsonic::ProtocolVersion> readAs(std::string_view str);
} }
+23 -33
View File
@@ -26,32 +26,27 @@
namespace API::Subsonic namespace API::Subsonic
{ {
std::string std::string idToString(Database::ArtistId id)
idToString(Database::ArtistId id)
{ {
return "ar-" + id.toString(); return "ar-" + id.toString();
} }
std::string std::string idToString(Database::ReleaseId id)
idToString(Database::ReleaseId id)
{ {
return "al-" + id.toString(); return "al-" + id.toString();
} }
std::string std::string idToString(RootId)
idToString(RootId)
{ {
return "root"; return "root";
} }
std::string std::string idToString(Database::TrackId id)
idToString(Database::TrackId id)
{ {
return "tr-" + id.toString(); return "tr-" + id.toString();
} }
std::string std::string idToString(Database::TrackListId id)
idToString(Database::TrackListId id)
{ {
return "pl-" + id.toString(); return "pl-" + id.toString();
} }
@@ -60,79 +55,74 @@ namespace API::Subsonic
namespace StringUtils namespace StringUtils
{ {
template<> template<>
std::optional<Database::ArtistId> std::optional<Database::ArtistId> readAs(std::string_view str)
readAs(std::string_view str)
{ {
std::vector<std::string_view> values {StringUtils::splitString(str, "-")}; std::vector<std::string_view> values{ StringUtils::splitString(str, "-") };
if (values.size() != 2) if (values.size() != 2)
return std::nullopt; return std::nullopt;
if (values[0] != "ar") if (values[0] != "ar")
return std::nullopt; return std::nullopt;
if (const auto value {StringUtils::readAs<Database::ArtistId::ValueType>(values[1])}) if (const auto value{ StringUtils::readAs<Database::ArtistId::ValueType>(values[1]) })
return Database::ArtistId {*value}; return Database::ArtistId{ *value };
return std::nullopt; return std::nullopt;
} }
template<> template<>
std::optional<Database::ReleaseId> std::optional<Database::ReleaseId> readAs(std::string_view str)
readAs(std::string_view str)
{ {
std::vector<std::string_view> values {StringUtils::splitString(str, "-")}; std::vector<std::string_view> values{ StringUtils::splitString(str, "-") };
if (values.size() != 2) if (values.size() != 2)
return std::nullopt; return std::nullopt;
if (values[0] != "al") if (values[0] != "al")
return std::nullopt; return std::nullopt;
if (const auto value {StringUtils::readAs<Database::ReleaseId::ValueType>(values[1])}) if (const auto value{ StringUtils::readAs<Database::ReleaseId::ValueType>(values[1]) })
return Database::ReleaseId {*value}; return Database::ReleaseId{ *value };
return std::nullopt; return std::nullopt;
} }
template<> template<>
std::optional<API::Subsonic::RootId> std::optional<API::Subsonic::RootId> readAs(std::string_view str)
readAs(std::string_view str)
{ {
if (str == "root") if (str == "root")
return API::Subsonic::RootId {}; return API::Subsonic::RootId{};
return std::nullopt; return std::nullopt;
} }
template<> template<>
std::optional<Database::TrackId> std::optional<Database::TrackId> readAs(std::string_view str)
readAs(std::string_view str)
{ {
std::vector<std::string_view> values {StringUtils::splitString(str, "-")}; std::vector<std::string_view> values{ StringUtils::splitString(str, "-") };
if (values.size() != 2) if (values.size() != 2)
return std::nullopt; return std::nullopt;
if (values[0] != "tr") if (values[0] != "tr")
return std::nullopt; return std::nullopt;
if (const auto value {StringUtils::readAs<Database::TrackId::ValueType>(values[1])}) if (const auto value{ StringUtils::readAs<Database::TrackId::ValueType>(values[1]) })
return Database::TrackId {*value}; return Database::TrackId{ *value };
return std::nullopt; return std::nullopt;
} }
template<> template<>
std::optional<Database::TrackListId> std::optional<Database::TrackListId> readAs(std::string_view str)
readAs(std::string_view str)
{ {
std::vector<std::string_view> values {StringUtils::splitString(str, "-")}; std::vector<std::string_view> values{ StringUtils::splitString(str, "-") };
if (values.size() != 2) if (values.size() != 2)
return std::nullopt; return std::nullopt;
if (values[0] != "pl") if (values[0] != "pl")
return std::nullopt; return std::nullopt;
if (const auto value {StringUtils::readAs<Database::TrackListId::ValueType>(values[1])}) if (const auto value{ StringUtils::readAs<Database::TrackListId::ValueType>(values[1]) })
return Database::TrackListId {*value}; return Database::TrackListId{ *value };
return std::nullopt; return std::nullopt;
} }
+5 -10
View File
@@ -40,23 +40,18 @@ namespace API::Subsonic
namespace StringUtils namespace StringUtils
{ {
template<> template<>
std::optional<API::Subsonic::RootId> std::optional<API::Subsonic::RootId> readAs(std::string_view str);
readAs(std::string_view str);
template<> template<>
std::optional<Database::ArtistId> std::optional<Database::ArtistId> readAs(std::string_view str);
readAs(std::string_view str);
template<> template<>
std::optional<Database::ReleaseId> std::optional<Database::ReleaseId> readAs(std::string_view str);
readAs(std::string_view str);
template<> template<>
std::optional<Database::TrackId> std::optional<Database::TrackId> readAs(std::string_view str);
readAs(std::string_view str);
template<> template<>
std::optional<Database::TrackListId> std::optional<Database::TrackListId> readAs(std::string_view str);
readAs(std::string_view str);
} }
+104 -118
View File
@@ -54,37 +54,29 @@ using namespace Database;
namespace API::Subsonic namespace API::Subsonic
{ {
std::unique_ptr<Wt::WResource> createSubsonicResource(Database::Db& db)
std::unique_ptr<Wt::WResource> {
createSubsonicResource(Database::Db& db)
{
return std::make_unique<SubsonicResource>(db); return std::make_unique<SubsonicResource>(db);
} }
static namespace
std::unordered_map<std::string, ProtocolVersion> {
readConfigProtocolVersions() std::unordered_map<std::string, ProtocolVersion> readConfigProtocolVersions()
{ {
std::unordered_map<std::string, ProtocolVersion> res; std::unordered_map<std::string, ProtocolVersion> res;
Service<IConfig>::get()->visitStrings("api-subsonic-report-old-server-protocol", Service<IConfig>::get()->visitStrings("api-subsonic-report-old-server-protocol",
[&](std::string_view client) [&](std::string_view client)
{ {
res.emplace(std::string {client}, ProtocolVersion {1, 12, 0}); res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 });
}, {"DSub"}); }, { "DSub" });
return res; return res;
} }
SubsonicResource::SubsonicResource(Db& db)
: _serverProtocolVersionsByClient {readConfigProtocolVersions()}
, _db {db}
{
}
static std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap)
std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap) {
{
auto censorValue = [](const std::string& type, const std::string& value) -> std::string auto censorValue = [](const std::string& type, const std::string& value) -> std::string
{ {
if (type == "p" || type == "password") if (type == "p" || type == "password")
@@ -113,61 +105,54 @@ std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap
} }
return res; return res;
} }
static void checkUserTypeIsAllowed(RequestContext& context, EnumSet<Database::UserType> allowedUserTypes)
void {
checkUserTypeIsAllowed(RequestContext& context, EnumSet<Database::UserType> allowedUserTypes) auto transaction{ context.dbSession.createSharedTransaction() };
{
auto transaction {context.dbSession.createSharedTransaction()};
User::pointer currentUser {User::find(context.dbSession, context.userId)}; User::pointer currentUser{ User::find(context.dbSession, context.userId) };
if (!currentUser) if (!currentUser)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError{};
if (!allowedUserTypes.contains(currentUser->getType())) if (!allowedUserTypes.contains(currentUser->getType()))
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError{};
} }
static Response handlePingRequest(RequestContext& context)
Response {
handlePingRequest(RequestContext& context)
{
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.serverProtocolVersion);
} }
static Response handleGetLicenseRequest(RequestContext& context)
Response {
handleGetLicenseRequest(RequestContext& context) Response response{ Response::createOkResponse(context.serverProtocolVersion) };
{
Response response {Response::createOkResponse(context.serverProtocolVersion)};
Response::Node& licenseNode {response.createNode("license")}; Response::Node& licenseNode{ response.createNode("license") };
licenseNode.setAttribute("licenseExpires", "2025-09-03T14:46:43"); licenseNode.setAttribute("licenseExpires", "2025-09-03T14:46:43");
licenseNode.setAttribute("email", "foo@bar.com"); licenseNode.setAttribute("email", "foo@bar.com");
licenseNode.setAttribute("valid", true); licenseNode.setAttribute("valid", true);
return response; return response;
} }
static Response handleNotImplemented(RequestContext&)
Response {
handleNotImplemented(RequestContext&) throw NotImplementedGenericError{};
{ }
throw NotImplementedGenericError {};
}
using RequestHandlerFunc = std::function<Response(RequestContext& context)>;
using CheckImplementedFunc = std::function<void()>; using RequestHandlerFunc = std::function<Response(RequestContext& context)>;
struct RequestEntryPointInfo using CheckImplementedFunc = std::function<void()>;
{ struct RequestEntryPointInfo
{
RequestHandlerFunc func; RequestHandlerFunc func;
EnumSet<UserType> allowedUserTypes {UserType::DEMO, UserType::REGULAR, UserType::ADMIN}; EnumSet<UserType> allowedUserTypes{ UserType::DEMO, UserType::REGULAR, UserType::ADMIN };
CheckImplementedFunc checkFunc {}; CheckImplementedFunc checkFunc{};
}; };
static const std::unordered_map<std::string_view, RequestEntryPointInfo> requestEntryPoints static const std::unordered_map<std::string_view, RequestEntryPointInfo> requestEntryPoints
{ {
// System // System
{"/ping", {handlePingRequest}}, {"/ping", {handlePingRequest}},
{"/getLicense", {handleGetLicenseRequest}}, {"/getLicense", {handleGetLicenseRequest}},
@@ -269,42 +254,49 @@ static const std::unordered_map<std::string_view, RequestEntryPointInfo> request
// Media library scanning // Media library scanning
{"/getScanStatus", {Scan::handleGetScanStatus, {UserType::ADMIN}}}, {"/getScanStatus", {Scan::handleGetScanStatus, {UserType::ADMIN}}},
{"/startScan", {Scan::handleStartScan, {UserType::ADMIN}}}, {"/startScan", {Scan::handleStartScan, {UserType::ADMIN}}},
}; };
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>; using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
static std::unordered_map<std::string, MediaRetrievalHandlerFunc> mediaRetrievalHandlers static std::unordered_map<std::string, MediaRetrievalHandlerFunc> mediaRetrievalHandlers
{ {
// Media retrieval // Media retrieval
{"/download", handleDownload}, {"/download", handleDownload},
{"/stream", handleStream}, {"/stream", handleStream},
{"/getCoverArt", handleGetCoverArt}, {"/getCoverArt", handleGetCoverArt},
}; };
}
void
SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response)
{
static std::atomic<std::size_t> curRequestId {};
const std::size_t requestId {curRequestId++}; SubsonicResource::SubsonicResource(Db& db)
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
, _db{ db }
{
}
void SubsonicResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{
static std::atomic<std::size_t> curRequestId{};
const std::size_t requestId{ curRequestId++ };
LMS_LOG(API_SUBSONIC, DEBUG) << "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap()); LMS_LOG(API_SUBSONIC, DEBUG) << "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap());
std::string requestPath {request.pathInfo()}; std::string requestPath{ request.pathInfo() };
if (StringUtils::stringEndsWith(requestPath, ".view")) if (StringUtils::stringEndsWith(requestPath, ".view"))
requestPath.resize(requestPath.length() - 5); requestPath.resize(requestPath.length() - 5);
// Optional parameters // Optional parameters
const ResponseFormat format {getParameterAs<std::string>(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml}; const ResponseFormat format{ getParameterAs<std::string>(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml };
ProtocolVersion protocolVersion {defaultServerProtocolVersion}; ProtocolVersion protocolVersion{ defaultServerProtocolVersion };
try try
{ {
// We need to parse client a soon as possible to make sure to answer with the right protocol version // We need to parse client a soon as possible to make sure to answer with the right protocol version
protocolVersion = getServerProtocolVersion(getMandatoryParameterAs<std::string>(request.getParameterMap(), "c")); protocolVersion = getServerProtocolVersion(getMandatoryParameterAs<std::string>(request.getParameterMap(), "c"));
RequestContext requestContext {buildRequestContext(request)}; RequestContext requestContext{ buildRequestContext(request) };
auto itEntryPoint {requestEntryPoints.find(requestPath)}; auto itEntryPoint{ requestEntryPoints.find(requestPath) };
if (itEntryPoint != requestEntryPoints.end()) if (itEntryPoint != requestEntryPoints.end())
{ {
if (itEntryPoint->second.checkFunc) if (itEntryPoint->second.checkFunc)
@@ -312,7 +304,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
checkUserTypeIsAllowed(requestContext, itEntryPoint->second.allowedUserTypes); checkUserTypeIsAllowed(requestContext, itEntryPoint->second.allowedUserTypes);
Response resp {(itEntryPoint->second.func)(requestContext)}; Response resp{ (itEntryPoint->second.func)(requestContext) };
resp.write(response.out(), format); resp.write(response.out(), format);
response.setMimeType(ResponseFormatToMimeType(format)); response.setMimeType(ResponseFormatToMimeType(format));
@@ -321,7 +313,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
return; return;
} }
auto itStreamHandler {mediaRetrievalHandlers.find(requestPath)}; auto itStreamHandler{ mediaRetrievalHandlers.find(requestPath) };
if (itStreamHandler != mediaRetrievalHandlers.end()) if (itStreamHandler != mediaRetrievalHandlers.end())
{ {
itStreamHandler->second(requestContext, request, response); itStreamHandler->second(requestContext, request, response);
@@ -330,52 +322,49 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
} }
LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'"; LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'";
throw UnknownEntryPointGenericError {}; throw UnknownEntryPointGenericError{};
} }
catch (const Error& e) catch (const Error& e)
{ {
LMS_LOG(API_SUBSONIC, ERROR) << "Error while processing request '" << requestPath << "'" LMS_LOG(API_SUBSONIC, ERROR) << "Error while processing request '" << requestPath << "'"
<< ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]" << ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]"
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'"; << ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'";
Response resp {Response::createFailedResponse(protocolVersion, e)}; Response resp{ Response::createFailedResponse(protocolVersion, e) };
resp.write(response.out(), format); resp.write(response.out(), format);
response.setMimeType(ResponseFormatToMimeType(format)); response.setMimeType(ResponseFormatToMimeType(format));
} }
} }
ProtocolVersion ProtocolVersion SubsonicResource::getServerProtocolVersion(const std::string& clientName) const
SubsonicResource::getServerProtocolVersion(const std::string& clientName) const {
{ auto it{ _serverProtocolVersionsByClient.find(clientName) };
auto it {_serverProtocolVersionsByClient.find(clientName)};
if (it == std::cend(_serverProtocolVersionsByClient)) if (it == std::cend(_serverProtocolVersionsByClient))
return defaultServerProtocolVersion; return defaultServerProtocolVersion;
return it->second; return it->second;
} }
void void SubsonicResource::checkProtocolVersion(ProtocolVersion client, ProtocolVersion server)
SubsonicResource::checkProtocolVersion(ProtocolVersion client, ProtocolVersion server) {
{
if (client.major > server.major) if (client.major > server.major)
throw ServerMustUpgradeError {}; throw ServerMustUpgradeError{};
if (client.major < server.major) if (client.major < server.major)
throw ClientMustUpgradeError {}; throw ClientMustUpgradeError{};
if (client.minor > server.minor) if (client.minor > server.minor)
throw ServerMustUpgradeError {}; throw ServerMustUpgradeError{};
else if (client.minor == server.minor) else if (client.minor == server.minor)
{ {
if (client.patch > server.patch) if (client.patch > server.patch)
throw ServerMustUpgradeError {}; throw ServerMustUpgradeError{};
}
} }
}
ClientInfo ClientInfo SubsonicResource::getClientInfo(const Wt::Http::ParameterMap& parameters)
SubsonicResource::getClientInfo(const Wt::Http::ParameterMap& parameters) {
{
ClientInfo res; ClientInfo res;
if (hasParameter(parameters, "t")) if (hasParameter(parameters, "t"))
throw TokenAuthenticationNotSupportedForLDAPUsersError {}; throw TokenAuthenticationNotSupportedForLDAPUsersError{};
// Mandatory parameters // Mandatory parameters
res.name = getMandatoryParameterAs<std::string>(parameters, "c"); res.name = getMandatoryParameterAs<std::string>(parameters, "c");
@@ -384,33 +373,30 @@ SubsonicResource::getClientInfo(const Wt::Http::ParameterMap& parameters)
res.password = decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(parameters, "p")); res.password = decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(parameters, "p"));
return res; return res;
} }
RequestContext RequestContext SubsonicResource::buildRequestContext(const Wt::Http::Request& request)
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 {parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name)};
}
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)}; const Wt::Http::ParameterMap& parameters{ request.getParameterMap() };
const ClientInfo clientInfo{ getClientInfo(parameters) };
const Database::UserId userId{ authenticateUser(request, clientInfo) };
return { parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name) };
}
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) if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError{};
return *checkResult.userId; return *checkResult.userId;
} }
else if (auto *authPasswordService {Service<::Auth::IPasswordService>::get()}) else if (auto * authPasswordService{ Service<::Auth::IPasswordService>::get() })
{ {
const auto checkResult {authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), const auto checkResult{ authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), clientInfo.user, clientInfo.password) };
clientInfo.user, clientInfo.password)};
switch (checkResult.state) switch (checkResult.state)
{ {
@@ -418,14 +404,14 @@ SubsonicResource::authenticateUser(const Wt::Http::Request& request, const Clien
return *checkResult.userId; return *checkResult.userId;
break; break;
case Auth::IPasswordService::CheckResult::State::Denied: case Auth::IPasswordService::CheckResult::State::Denied:
throw WrongUsernameOrPasswordError {}; throw WrongUsernameOrPasswordError{};
case Auth::IPasswordService::CheckResult::State::Throttled: case Auth::IPasswordService::CheckResult::State::Throttled:
throw LoginThrottledGenericError {}; throw LoginThrottledGenericError{};
} }
} }
throw InternalErrorGenericError {"No service avalaible to authenticate user"}; throw InternalErrorGenericError{ "No service avalaible to authenticate user" };
} }
} // namespace api::subsonic } // namespace api::subsonic
+71 -88
View File
@@ -34,9 +34,8 @@
namespace API::Subsonic namespace API::Subsonic
{ {
std::string std::string ResponseFormatToMimeType(ResponseFormat format)
ResponseFormatToMimeType(ResponseFormat format) {
{
switch (format) switch (format)
{ {
case ResponseFormat::xml: return "text/xml"; case ResponseFormat::xml: return "text/xml";
@@ -44,122 +43,108 @@ ResponseFormatToMimeType(ResponseFormat format)
} }
return ""; return "";
} }
void void Response::Node::setValue(std::string_view value)
Response::Node::setValue(std::string_view value) {
{
if (!_children.empty() || !_childrenArrays.empty()) if (!_children.empty() || !_childrenArrays.empty())
throw LmsException {"Node already has children"}; throw LmsException{ "Node already has children" };
_value = std::string {value}; _value = std::string{ value };
} }
void void Response::Node::setValue(long long value)
Response::Node::setValue(long long value) {
{
if (!_children.empty() || !_childrenArrays.empty()) if (!_children.empty() || !_childrenArrays.empty())
throw LmsException {"Node already has children"}; throw LmsException{ "Node already has children" };
_value = value; _value = value;
} }
void void Response::Node::setAttribute(std::string_view key, std::string_view value)
Response::Node::setAttribute(std::string_view key, std::string_view value) {
{ _attributes[std::string{ key }] = std::string{ value };
_attributes[std::string {key}] = std::string {value}; }
}
void void Response::Node::addChild(const std::string& key, Node node)
Response::Node::addChild(const std::string& key, Node node) {
{
if (_value) if (_value)
throw LmsException {"Node already has a value"}; throw LmsException{ "Node already has a value" };
_children[key].emplace_back(std::move(node)); _children[key].emplace_back(std::move(node));
} }
void void Response::Node::addArrayChild(const std::string& key, Node node)
Response::Node::addArrayChild(const std::string& key, Node node) {
{
if (_value) if (_value)
throw LmsException {"Node already has a 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& Response::Node::createChild(const std::string& key)
Response::Node::createChild(const std::string& key) {
{
_children[key].emplace_back(); _children[key].emplace_back();
return _children[key].back(); return _children[key].back();
} }
Response::Node& Response::Node& Response::Node::createArrayChild(const std::string& key)
Response::Node::createArrayChild(const std::string& key) {
{
_childrenArrays[key].emplace_back(); _childrenArrays[key].emplace_back();
return _childrenArrays[key].back(); return _childrenArrays[key].back();
} }
void void Response::Node::setVersionAttribute(ProtocolVersion protocolVersion)
Response::Node::setVersionAttribute(ProtocolVersion protocolVersion) {
{
setAttribute("version", std::to_string(protocolVersion.major) + "." + std::to_string(protocolVersion.minor) + "." + std::to_string(protocolVersion.patch)); setAttribute("version", std::to_string(protocolVersion.major) + "." + std::to_string(protocolVersion.minor) + "." + std::to_string(protocolVersion.patch));
} }
Response Response Response::createOkResponse(ProtocolVersion protocolVersion)
Response::createOkResponse(ProtocolVersion protocolVersion) {
{
Response response; Response response;
Node& responseNode {response._root.createChild("subsonic-response")}; Node& responseNode{ response._root.createChild("subsonic-response") };
responseNode.setAttribute("status", "ok"); responseNode.setAttribute("status", "ok");
responseNode.setVersionAttribute(protocolVersion); responseNode.setVersionAttribute(protocolVersion);
responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks
return response; return response;
} }
Response Response Response::createFailedResponse(ProtocolVersion protocolVersion, const Error& error)
Response::createFailedResponse(ProtocolVersion protocolVersion, const Error& error) {
{
Response response; Response response;
Node& responseNode {response._root.createChild("subsonic-response")}; Node& responseNode{ response._root.createChild("subsonic-response") };
responseNode.setAttribute("status", "failed"); responseNode.setAttribute("status", "failed");
responseNode.setVersionAttribute(protocolVersion); responseNode.setVersionAttribute(protocolVersion);
responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks
Node& errorNode {responseNode.createChild("error")}; Node& errorNode{ responseNode.createChild("error") };
errorNode.setAttribute("code", static_cast<int>(error.getCode())); errorNode.setAttribute("code", static_cast<int>(error.getCode()));
errorNode.setAttribute("message", error.getMessage()); errorNode.setAttribute("message", error.getMessage());
return response; return response;
} }
void void Response::addNode(const std::string& key, Node node)
Response::addNode(const std::string& key, Node node) {
{
return _root._children["subsonic-response"].front().addChild(key, std::move(node)); return _root._children["subsonic-response"].front().addChild(key, std::move(node));
} }
Response::Node& Response::Node& Response::createNode(const std::string& key)
Response::createNode(const std::string& key) {
{
return _root._children["subsonic-response"].front().createChild(key); return _root._children["subsonic-response"].front().createChild(key);
} }
Response::Node& Response::Node& Response::createArrayNode(const std::string& key)
Response::createArrayNode(const std::string& key) {
{
return _root._children["subsonic-response"].front().createArrayChild(key); return _root._children["subsonic-response"].front().createArrayChild(key);
} }
void void Response::write(std::ostream& os, ResponseFormat format)
Response::write(std::ostream& os, ResponseFormat format) {
{
switch (format) switch (format)
{ {
case ResponseFormat::xml: case ResponseFormat::xml:
@@ -169,12 +154,11 @@ Response::write(std::ostream& os, ResponseFormat format)
writeJSON(os); writeJSON(os);
break; break;
} }
} }
void void Response::writeXML(std::ostream& os)
Response::writeXML(std::ostream& os) {
{ std::function<boost::property_tree::ptree(const Response::Node&)> nodeToPropertyTree = [&](const Response::Node& node)
std::function<boost::property_tree::ptree(const Response::Node&)> nodeToPropertyTree = [&] (const Response::Node& node)
{ {
boost::property_tree::ptree res; boost::property_tree::ptree res;
@@ -190,7 +174,7 @@ Response::writeXML(std::ostream& os)
if (node._value) if (node._value)
{ {
const auto& value {*node._value}; const auto& value{ *node._value };
if (std::holds_alternative<std::string>(value)) if (std::holds_alternative<std::string>(value))
res.put_value(std::get<std::string>(value)); res.put_value(std::get<std::string>(value));
@@ -209,9 +193,9 @@ Response::writeXML(std::ostream& os)
for (auto itChildArrayNode : node._childrenArrays) for (auto itChildArrayNode : node._childrenArrays)
{ {
const std::vector<Response::Node>& childArrayNodes {itChildArrayNode.second}; const std::vector<Response::Node>& childArrayNodes{ itChildArrayNode.second };
for (const Response::Node& childNode : childArrayNodes ) for (const Response::Node& childNode : childArrayNodes)
res.add_child(itChildArrayNode.first, nodeToPropertyTree(childNode)); res.add_child(itChildArrayNode.first, nodeToPropertyTree(childNode));
} }
} }
@@ -219,20 +203,19 @@ Response::writeXML(std::ostream& os)
return res; return res;
}; };
boost::property_tree::ptree root {nodeToPropertyTree(_root)}; boost::property_tree::ptree root{ nodeToPropertyTree(_root) };
boost::property_tree::write_xml(os, root); boost::property_tree::write_xml(os, root);
} }
void void Response::writeJSON(std::ostream& os)
Response::writeJSON(std::ostream& os) {
{
namespace Json = Wt::Json; namespace Json = Wt::Json;
std::function<Json::Object(const Response::Node&)> nodeToJsonObject = [&] (const Response::Node& node) std::function<Json::Object(const Response::Node&)> nodeToJsonObject = [&](const Response::Node& node)
{ {
Json::Object res; Json::Object res;
auto valueToJsonValue {[](const Node::ValueType& value) -> Json::Value auto valueToJsonValue{ [](const Node::ValueType& value) -> Json::Value
{ {
if (std::holds_alternative<std::string>(value)) if (std::holds_alternative<std::string>(value))
return Json::Value {std::get<std::string>(value)}; return Json::Value {std::get<std::string>(value)};
@@ -242,7 +225,7 @@ Response::writeJSON(std::ostream& os)
return Json::Value {std::get<long long>(value)}; return Json::Value {std::get<long long>(value)};
throw LmsException("Unexpected value type"); throw LmsException("Unexpected value type");
}}; } };
for (auto itAttribute : node._attributes) for (auto itAttribute : node._attributes)
res[itAttribute.first] = valueToJsonValue(itAttribute.second); res[itAttribute.first] = valueToJsonValue(itAttribute.second);
@@ -261,10 +244,10 @@ Response::writeJSON(std::ostream& os)
for (auto itChildArrayNode : node._childrenArrays) for (auto itChildArrayNode : node._childrenArrays)
{ {
const std::vector<Response::Node>& childArrayNodes {itChildArrayNode .second}; const std::vector<Response::Node>& childArrayNodes{ itChildArrayNode.second };
Json::Array array; Json::Array array;
for (const Response::Node& childNode : childArrayNodes ) for (const Response::Node& childNode : childArrayNodes)
array.emplace_back(nodeToJsonObject(childNode)); array.emplace_back(nodeToJsonObject(childNode));
res[itChildArrayNode.first] = std::move(array); res[itChildArrayNode.first] = std::move(array);
@@ -274,9 +257,9 @@ Response::writeJSON(std::ostream& os)
return res; return res;
}; };
Json::Object root {nodeToJsonObject(_root)}; Json::Object root{ nodeToJsonObject(_root) };
os << Json::serialize(root); os << Json::serialize(root);
} }
} // namespace } // namespace
+75 -75
View File
@@ -30,16 +30,16 @@
namespace API::Subsonic namespace API::Subsonic
{ {
enum class ResponseFormat enum class ResponseFormat
{ {
xml, xml,
json, json,
}; };
std::string ResponseFormatToMimeType(ResponseFormat format); std::string ResponseFormatToMimeType(ResponseFormat format);
class Error class Error
{ {
public: public:
enum class Code enum class Code
{ {
@@ -53,7 +53,7 @@ class Error
RequestedDataNotFound = 70, RequestedDataNotFound = 70,
}; };
Error(Code code) : _code {code} {} Error(Code code) : _code{ code } {}
virtual std::string getMessage() const = 0; virtual std::string getMessage() const = 0;
@@ -61,132 +61,132 @@ class Error
private: private:
const Code _code; const Code _code;
}; };
class GenericError : public Error class GenericError : public Error
{ {
public: public:
GenericError() : Error {Code::Generic} {} GenericError() : Error{ Code::Generic } {}
}; };
class RequiredParameterMissingError : public Error class RequiredParameterMissingError : public Error
{ {
public: public:
RequiredParameterMissingError(std::string_view param) RequiredParameterMissingError(std::string_view param)
: Error {Code::RequiredParameterMissing} : Error{ Code::RequiredParameterMissing }
, _param {param} , _param{ param }
{} {}
private: private:
std::string getMessage() const override { return "Required parameter '" + _param + "' is missing."; } std::string getMessage() const override { return "Required parameter '" + _param + "' is missing."; }
std::string _param; std::string _param;
}; };
class ClientMustUpgradeError : public Error class ClientMustUpgradeError : public Error
{ {
public: public:
ClientMustUpgradeError() : Error {Code::ClientMustUpgrade} {} ClientMustUpgradeError() : Error{ Code::ClientMustUpgrade } {}
private: private:
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; } std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; }
}; };
class ServerMustUpgradeError : public Error class ServerMustUpgradeError : public Error
{ {
public: public:
ServerMustUpgradeError() : Error {Code::ServerMustUpgrade} {} ServerMustUpgradeError() : Error{ Code::ServerMustUpgrade } {}
private: private:
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; } std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; }
}; };
class WrongUsernameOrPasswordError : public Error class WrongUsernameOrPasswordError : public Error
{ {
public: public:
WrongUsernameOrPasswordError() : Error {Code::WrongUsernameOrPassword} {} WrongUsernameOrPasswordError() : Error{ Code::WrongUsernameOrPassword } {}
private: private:
std::string getMessage() const override { return "Wrong username or password."; } std::string getMessage() const override { return "Wrong username or password."; }
}; };
class TokenAuthenticationNotSupportedForLDAPUsersError : public Error class TokenAuthenticationNotSupportedForLDAPUsersError : public Error
{ {
public: public:
TokenAuthenticationNotSupportedForLDAPUsersError() : Error {Code::TokenAuthenticationNotSupportedForLDAPUsers} {} TokenAuthenticationNotSupportedForLDAPUsersError() : Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {}
private: private:
std::string getMessage() const override { return "Token authentication not supported for LDAP users."; } std::string getMessage() const override { return "Token authentication not supported for LDAP users."; }
}; };
class UserNotAuthorizedError : public Error class UserNotAuthorizedError : public Error
{ {
public: public:
UserNotAuthorizedError () : Error {Code::UserNotAuthorized} {} UserNotAuthorizedError() : Error{ Code::UserNotAuthorized } {}
private: private:
std::string getMessage() const override { return "User is not authorized for the given operation."; } std::string getMessage() const override { return "User is not authorized for the given operation."; }
}; };
class RequestedDataNotFoundError : public Error class RequestedDataNotFoundError : public Error
{ {
public: public:
RequestedDataNotFoundError() : Error {Code::RequestedDataNotFound} {} RequestedDataNotFoundError() : Error{ Code::RequestedDataNotFound } {}
private: private:
std::string getMessage() const override { return "The requested data was not found."; } std::string getMessage() const override { return "The requested data was not found."; }
}; };
class InternalErrorGenericError : public GenericError class InternalErrorGenericError : public GenericError
{ {
public: public:
InternalErrorGenericError(const std::string& message) : _message {message} {} InternalErrorGenericError(const std::string& message) : _message{ message } {}
private: private:
std::string getMessage() const override { return "Internal error: " + _message; } std::string getMessage() const override { return "Internal error: " + _message; }
const std::string _message; const std::string _message;
}; };
class LoginThrottledGenericError : public GenericError class LoginThrottledGenericError : public GenericError
{ {
std::string getMessage() const override { return "Login throttled, too many attempts"; } std::string getMessage() const override { return "Login throttled, too many attempts"; }
}; };
class NotImplementedGenericError : public GenericError class NotImplementedGenericError : public GenericError
{ {
std::string getMessage() const override { return "Not implemented"; } std::string getMessage() const override { return "Not implemented"; }
}; };
class UnknownEntryPointGenericError : public GenericError class UnknownEntryPointGenericError : public GenericError
{ {
std::string getMessage() const override { return "Unknown API method"; } std::string getMessage() const override { return "Unknown API method"; }
}; };
class PasswordTooWeakGenericError : public GenericError class PasswordTooWeakGenericError : public GenericError
{ {
std::string getMessage() const override { return "Password too weak"; } std::string getMessage() const override { return "Password too weak"; }
}; };
class PasswordMustMatchLoginNameGenericError : public GenericError class PasswordMustMatchLoginNameGenericError : public GenericError
{ {
std::string getMessage() const override { return "Password must match login name"; } std::string getMessage() const override { return "Password must match login name"; }
}; };
class DemoUserCannotChangePasswordGenericError : public GenericError class DemoUserCannotChangePasswordGenericError : public GenericError
{ {
std::string getMessage() const override { return "Demo user cannot change its password"; } std::string getMessage() const override { return "Demo user cannot change its password"; }
}; };
class UserAlreadyExistsGenericError : public GenericError class UserAlreadyExistsGenericError : public GenericError
{ {
std::string getMessage() const override { return "User already exists"; } std::string getMessage() const override { return "User already exists"; }
}; };
class BadParameterGenericError : public GenericError class BadParameterGenericError : public GenericError
{ {
public: public:
BadParameterGenericError(const std::string& parameterName) : _parameterName {parameterName} {} BadParameterGenericError(const std::string& parameterName) : _parameterName{ parameterName } {}
private: private:
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; } std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; }
const std::string _parameterName; const std::string _parameterName;
}; };
class Response class Response
{ {
public: public:
class Node class Node
{ {
@@ -197,9 +197,9 @@ class Response
void setAttribute(std::string_view key, T value) void setAttribute(std::string_view key, T value)
{ {
if constexpr (std::is_same<bool, T>::value) if constexpr (std::is_same<bool, T>::value)
_attributes[std::string {key}] = value; _attributes[std::string{ key }] = value;
else else
_attributes[std::string {key}] = static_cast<long long>(value); _attributes[std::string{ key }] = static_cast<long long>(value);
} }
// A Node has either a value or some children // A Node has either a value or some children
@@ -243,7 +243,7 @@ class Response
Response() = default; Response() = default;
Node _root; Node _root;
}; };
} // namespace } // namespace