Refactored request context handling

This commit is contained in:
emeric
2025-10-01 22:11:32 +02:00
parent c62ac1d109
commit 0ccd095534
27 changed files with 594 additions and 473 deletions
+2
View File
@@ -28,11 +28,13 @@ add_library(lmssubsonic STATIC
impl/responses/Song.cpp impl/responses/Song.cpp
impl/responses/User.cpp impl/responses/User.cpp
impl/CoverArtId.cpp impl/CoverArtId.cpp
impl/RequestContext.cpp
impl/ResponseFormat.cpp impl/ResponseFormat.cpp
impl/ProtocolVersion.cpp impl/ProtocolVersion.cpp
impl/ParameterParsing.cpp impl/ParameterParsing.cpp
impl/SubsonicId.cpp impl/SubsonicId.cpp
impl/SubsonicResource.cpp impl/SubsonicResource.cpp
impl/SubsonicResourceConfig.cpp
impl/SubsonicResponse.cpp impl/SubsonicResponse.cpp
) )
+107
View File
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2025 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "RequestContext.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicResourceConfig.hpp"
#include "SubsonicResponse.hpp"
namespace lms::api::subsonic
{
namespace
{
void 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{};
if (client.minor == server.minor)
{
if (client.patch > server.patch)
throw ServerMustUpgradeError{};
}
}
} // namespace
RequestContext::RequestContext(const Wt::Http::Request& request, db::Session& dbSession, db::ObjectPtr<db::User> user, const SubsonicResourceConfig& config)
: _request{ request }
, _dbSession{ dbSession }
, _user{ user }
, _config{ config }
, _clientName{ getMandatoryParameterAs<std::string>(_request.getParameterMap(), "c") }
, _clientProtocolVersion{ getMandatoryParameterAs<ProtocolVersion>(_request.getParameterMap(), "v") }
, _responseFormat{ getParameterAs<std::string>(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml }
, _serverProtocolVersion{ _config.serverProtocolVersionsByClient.contains(_clientName) ? _config.serverProtocolVersionsByClient.at(_clientName) : defaultServerProtocolVersion }
, _isOpenSubsonicEnabled{ !_config.openSubsonicDisabledClients.contains(_clientName) }
{
checkProtocolVersion(_clientProtocolVersion, _serverProtocolVersion);
}
RequestContext::~RequestContext() = default;
const RequestContext::ParameterMap& RequestContext::getParameters() const
{
return _request.getParameterMap();
}
std::istream& RequestContext::getBody() const
{
return _request.in();
}
db::Session& RequestContext::getDbSession()
{
return _dbSession;
}
db::ObjectPtr<db::User> RequestContext::getUser() const
{
return _user;
}
std::string RequestContext::getClientIpAddr() const
{
return _request.clientAddress();
}
std::string_view RequestContext::getClientName() const
{
return _clientName;
}
ProtocolVersion RequestContext::getServerProtocolVersion() const
{
return _serverProtocolVersion;
}
ResponseFormat RequestContext::getResponseFormat() const
{
return _responseFormat;
}
bool RequestContext::isOpenSubsonicEnabled() const
{
return _isOpenSubsonicEnabled;
}
} // namespace lms::api::subsonic
+38 -10
View File
@@ -19,15 +19,17 @@
#pragma once #pragma once
#include <iosfwd>
#include <string> #include <string>
#include <string_view>
#include <Wt/Http/Request.h> #include <Wt/Http/Request.h>
#include "database/Object.hpp" #include "database/Object.hpp"
#include "ClientInfo.hpp"
#include "ProtocolVersion.hpp" #include "ProtocolVersion.hpp"
#include "ResponseFormat.hpp" #include "ResponseFormat.hpp"
#include "SubsonicResourceConfig.hpp"
namespace lms::db namespace lms::db
{ {
@@ -37,15 +39,41 @@ namespace lms::db
namespace lms::api::subsonic namespace lms::api::subsonic
{ {
struct RequestContext class RequestContext
{ {
const Wt::Http::ParameterMap& parameters; public:
db::Session& dbSession; RequestContext(const Wt::Http::Request& request, db::Session& dbSession, db::ObjectPtr<db::User> user, const SubsonicResourceConfig& config);
db::ObjectPtr<db::User> user; ~RequestContext();
std::string clientIpAddr; RequestContext(const RequestContext&) = delete;
ClientInfo clientInfo; RequestContext& operator=(const RequestContext&) = delete;
ProtocolVersion serverProtocolVersion;
ResponseFormat responseFormat; using ParameterMap = Wt::Http::ParameterMap;
bool enableOpenSubsonic{ true };
const ParameterMap& getParameters() const;
std::istream& getBody() const;
db::Session& getDbSession();
db::ObjectPtr<db::User> getUser() const;
std::string getClientIpAddr() const;
std::string_view getClientName() const;
ProtocolVersion getServerProtocolVersion() const;
ResponseFormat getResponseFormat() const;
bool isOpenSubsonicEnabled() const;
private:
const Wt::Http::Request& _request;
db::Session& _dbSession;
db::ObjectPtr<db::User> _user;
const SubsonicResourceConfig& _config;
const std::string _clientName;
const ProtocolVersion _clientProtocolVersion;
const ResponseFormat _responseFormat;
const ProtocolVersion _serverProtocolVersion;
const bool _isOpenSubsonicEnabled;
}; };
} // namespace lms::api::subsonic } // namespace lms::api::subsonic
+14 -94
View File
@@ -60,32 +60,6 @@ namespace lms::api::subsonic
namespace namespace
{ {
std::unordered_map<std::string, ProtocolVersion> readConfigProtocolVersions()
{
std::unordered_map<std::string, ProtocolVersion> res;
core::Service<core::IConfig>::get()->visitStrings("api-subsonic-old-server-protocol-clients",
[&](std::string_view client) {
res.emplace(std::string{ client }, ProtocolVersion{ .major = 1, .minor = 12, .patch = 0 });
},
{ "DSub" });
return res;
}
std::unordered_set<std::string> readOpenSubsonicDisabledClients()
{
std::unordered_set<std::string> res;
core::Service<core::IConfig>::get()->visitStrings("api-open-subsonic-disabled-clients",
[&](std::string_view client) {
res.emplace(std::string{ client });
},
{ "DSub" });
return res;
}
std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap) std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap)
{ {
constexpr std::string_view redactedStr{ "*REDACTED*" }; constexpr std::string_view redactedStr{ "*REDACTED*" };
@@ -292,24 +266,10 @@ namespace lms::api::subsonic
throw UserNotAuthorizedError{}; throw UserNotAuthorizedError{};
} }
ClientInfo getClientInfo(const Wt::Http::Request& request)
{
const auto& parameters{ request.getParameterMap() };
ClientInfo res;
// Mandatory parameters
res.name = getMandatoryParameterAs<std::string>(parameters, "c");
res.version = getMandatoryParameterAs<ProtocolVersion>(parameters, "v");
return res;
}
} // namespace } // namespace
SubsonicResource::SubsonicResource(db::IDb& db) SubsonicResource::SubsonicResource(db::IDb& db)
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() } : _config{ readSubsonicResourceConfig(*core::Service<core::IConfig>::get()) }
, _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() }
, _supportUserPasswordAuthentication{ core::Service<core::IConfig>::get()->getBool("api-subsonic-support-user-password-auth", true) }
, _db{ db } , _db{ db }
{ {
} }
@@ -334,21 +294,20 @@ namespace lms::api::subsonic
try try
{ {
// We need to parse client a soon as possible to make sure to answer with the right protocol version if (auto itEntryPoint{ requestEntryPoints.find(requestPath) }; itEntryPoint != requestEntryPoints.end())
protocolVersion = getServerProtocolVersion(getMandatoryParameterAs<std::string>(request.getParameterMap(), "c"));
RequestContext requestContext{ buildRequestContext(request) };
auto itEntryPoint{ requestEntryPoints.find(requestPath) };
if (itEntryPoint != requestEntryPoints.end())
{ {
LMS_SCOPED_TRACE_OVERVIEW("Subsonic", itEntryPoint->first); LMS_SCOPED_TRACE_OVERVIEW("Subsonic", itEntryPoint->first);
db::User::pointer user;
if (itEntryPoint->second.authMode == AuthenticationMode::Authenticated) if (itEntryPoint->second.authMode == AuthenticationMode::Authenticated)
{ {
requestContext.user = getUserFromUserId(_db.getTLSSession(), authenticateUser(request)); user = getUserFromUserId(_db.getTLSSession(), authenticateUser(request));
checkUserTypeIsAllowed(requestContext.user, itEntryPoint->second.allowedUserTypes); checkUserTypeIsAllowed(user, itEntryPoint->second.allowedUserTypes);
} }
RequestContext requestContext{ request, _db.getTLSSession(), user, _config };
protocolVersion = requestContext.getServerProtocolVersion();
const Response resp{ [&] { const Response resp{ [&] {
LMS_SCOPED_TRACE_DETAILED("Subsonic", "HandleRequest"); LMS_SCOPED_TRACE_DETAILED("Subsonic", "HandleRequest");
return itEntryPoint->second.func(requestContext); return itEntryPoint->second.func(requestContext);
@@ -372,8 +331,12 @@ namespace lms::api::subsonic
// Media retrieval endpoints are always authenticated // Media retrieval endpoints are always authenticated
// Optim: no need to reauth user for each continuation // Optim: no need to reauth user for each continuation
db::User::pointer user;
if (!request.continuation()) if (!request.continuation())
requestContext.user = getUserFromUserId(_db.getTLSSession(), authenticateUser(request)); user = getUserFromUserId(_db.getTLSSession(), authenticateUser(request));
RequestContext requestContext{ request, _db.getTLSSession(), user, _config };
protocolVersion = requestContext.getServerProtocolVersion();
itStreamHandler->second(requestContext, request, response); itStreamHandler->second(requestContext, request, response);
LMS_LOG(API_SUBSONIC, DEBUG, "Request " << requestId << " '" << requestPath << "' handled!"); LMS_LOG(API_SUBSONIC, DEBUG, "Request " << requestId << " '" << requestPath << "' handled!");
@@ -397,49 +360,6 @@ namespace lms::api::subsonic
} }
} }
ProtocolVersion SubsonicResource::getServerProtocolVersion(const std::string& clientName) const
{
auto it{ _serverProtocolVersionsByClient.find(clientName) };
if (it == std::cend(_serverProtocolVersionsByClient))
return defaultServerProtocolVersion;
return it->second;
}
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{};
if (client.minor == server.minor)
{
if (client.patch > server.patch)
throw ServerMustUpgradeError{};
}
}
RequestContext SubsonicResource::buildRequestContext(const Wt::Http::Request& request)
{
const Wt::Http::ParameterMap& parameters{ request.getParameterMap() };
const ClientInfo clientInfo{ getClientInfo(request) };
bool enableOpenSubsonic{ !_openSubsonicDisabledClients.contains(clientInfo.name) };
const ResponseFormat format{ getParameterAs<std::string>(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml };
return RequestContext{
.parameters = parameters,
.dbSession = _db.getTLSSession(),
.user = db::User::pointer{},
.clientIpAddr = request.clientAddress(),
.clientInfo = clientInfo,
.serverProtocolVersion = getServerProtocolVersion(clientInfo.name),
.responseFormat = format,
.enableOpenSubsonic = enableOpenSubsonic,
};
}
db::UserId SubsonicResource::authenticateUser(const Wt::Http::Request& request) db::UserId SubsonicResource::authenticateUser(const Wt::Http::Request& request)
{ {
const auto& parameters{ request.getParameterMap() }; const auto& parameters{ request.getParameterMap() };
@@ -449,7 +369,7 @@ namespace lms::api::subsonic
const auto user{ getParameterAs<std::string>(parameters, "u") }; const auto user{ getParameterAs<std::string>(parameters, "u") };
const auto password{ getParameterAs<std::string>(parameters, "p") }; const auto password{ getParameterAs<std::string>(parameters, "p") };
if (!_supportUserPasswordAuthentication && (password || user)) if (!_config.supportUserPasswordAuthentication && (password || user))
throw ProvidedAuthenticationMechanismNotSupportedError{}; throw ProvidedAuthenticationMechanismNotSupportedError{};
const auto apiKey{ getParameterAs<std::string>(parameters, "apiKey") }; const auto apiKey{ getParameterAs<std::string>(parameters, "apiKey") };
+3 -12
View File
@@ -18,16 +18,13 @@
*/ */
#pragma once #pragma once
#include <string> #include <Wt/Http/Request.h>
#include <unordered_map>
#include <unordered_set>
#include <Wt/Http/Response.h> #include <Wt/Http/Response.h>
#include <Wt/WResource.h> #include <Wt/WResource.h>
#include "database/objects/UserId.hpp" #include "database/objects/UserId.hpp"
#include "RequestContext.hpp" #include "SubsonicResourceConfig.hpp"
namespace lms::db namespace lms::db
{ {
@@ -43,16 +40,10 @@ namespace lms::api::subsonic
private: private:
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; 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);
RequestContext buildRequestContext(const Wt::Http::Request& request);
db::UserId authenticateUser(const Wt::Http::Request& request); db::UserId authenticateUser(const Wt::Http::Request& request);
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient; const SubsonicResourceConfig _config;
const std::unordered_set<std::string> _openSubsonicDisabledClients;
const bool _supportUserPasswordAuthentication;
db::IDb& _db; db::IDb& _db;
}; };
} // namespace lms::api::subsonic } // namespace lms::api::subsonic
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2025 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SubsonicResourceConfig.hpp"
#include "core/IConfig.hpp"
namespace lms::api::subsonic
{
namespace
{
std::unordered_map<std::string, ProtocolVersion> readConfigProtocolVersions(core::IConfig& config)
{
std::unordered_map<std::string, ProtocolVersion> res;
config.visitStrings("api-subsonic-old-server-protocol-clients",
[&](std::string_view client) {
res.emplace(std::string{ client }, ProtocolVersion{ .major = 1, .minor = 12, .patch = 0 });
},
{ "DSub" });
return res;
}
std::unordered_set<std::string> readOpenSubsonicDisabledClients(core::IConfig& config)
{
std::unordered_set<std::string> res;
config.visitStrings("api-open-subsonic-disabled-clients",
[&](std::string_view client) {
res.emplace(std::string{ client });
},
{ "DSub" });
return res;
}
} // namespace
SubsonicResourceConfig readSubsonicResourceConfig(core::IConfig& config)
{
return SubsonicResourceConfig{
.serverProtocolVersionsByClient = readConfigProtocolVersions(config),
.openSubsonicDisabledClients = readOpenSubsonicDisabledClients(config),
.supportUserPasswordAuthentication = config.getBool("api-subsonic-support-user-password-auth", true)
};
}
} // namespace lms::api::subsonic
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2021 Emeric Poupon * Copyright (C) 2025 Emeric Poupon
* *
* This file is part of LMS. * This file is part of LMS.
* *
@@ -20,14 +20,24 @@
#pragma once #pragma once
#include <string> #include <string>
#include <unordered_map>
#include <unordered_set>
#include "ProtocolVersion.hpp" #include "ProtocolVersion.hpp"
namespace lms::core
{
class IConfig;
}
namespace lms::api::subsonic namespace lms::api::subsonic
{ {
struct ClientInfo struct SubsonicResourceConfig
{ {
std::string name; std::unordered_map<std::string, ProtocolVersion> serverProtocolVersionsByClient;
ProtocolVersion version; std::unordered_set<std::string> openSubsonicDisabledClients;
bool supportUserPasswordAuthentication;
}; };
} // namespace lms::api::subsonic
SubsonicResourceConfig readSubsonicResourceConfig(core::IConfig& _config);
} // namespace lms::api::subsonic
@@ -47,12 +47,12 @@ namespace lms::api::subsonic
Response handleGetAlbumListRequestCommon(RequestContext& context, bool id3) Response handleGetAlbumListRequestCommon(RequestContext& context, bool id3)
{ {
// Mandatory params // Mandatory params
const std::string type{ getMandatoryParameterAs<std::string>(context.parameters, "type") }; const std::string type{ getMandatoryParameterAs<std::string>(context.getParameters(), "type") };
// Optional params // Optional params
const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) }; const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
const std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(10) }; const std::size_t size{ getParameterAs<std::size_t>(context.getParameters(), "size").value_or(10) };
const std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) }; const std::size_t offset{ getParameterAs<std::size_t>(context.getParameters(), "offset").value_or(0) };
if (size > defaultMaxCountSize) if (size > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
@@ -62,7 +62,7 @@ namespace lms::api::subsonic
scrobbling::IScrobblingService& scrobblingService{ *core::Service<scrobbling::IScrobblingService>::get() }; scrobbling::IScrobblingService& scrobblingService{ *core::Service<scrobbling::IScrobblingService>::get() };
feedback::IFeedbackService& feedbackService{ *core::Service<feedback::IFeedbackService>::get() }; feedback::IFeedbackService& feedbackService{ *core::Service<feedback::IFeedbackService>::get() };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
if (type == "alphabeticalByName") if (type == "alphabeticalByName")
{ {
@@ -71,7 +71,7 @@ namespace lms::api::subsonic
params.setRange(range); params.setRange(range);
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params); releases = Release::findIds(context.getDbSession(), params);
} }
else if (type == "alphabeticalByArtist") else if (type == "alphabeticalByArtist")
{ {
@@ -80,14 +80,14 @@ namespace lms::api::subsonic
params.setRange(range); params.setRange(range);
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params); releases = Release::findIds(context.getDbSession(), params);
} }
else if (type == "byGenre") else if (type == "byGenre")
{ {
// Mandatory param // Mandatory param
const std::string genre{ getMandatoryParameterAs<std::string>(context.parameters, "genre") }; const std::string genre{ getMandatoryParameterAs<std::string>(context.getParameters(), "genre") };
if (const ClusterType::pointer clusterType{ ClusterType::find(context.dbSession, "GENRE") }) if (const ClusterType::pointer clusterType{ ClusterType::find(context.getDbSession(), "GENRE") })
{ {
if (const Cluster::pointer cluster{ clusterType->getCluster(genre) }) if (const Cluster::pointer cluster{ clusterType->getCluster(genre) })
{ {
@@ -97,14 +97,14 @@ namespace lms::api::subsonic
params.setSortMethod(ReleaseSortMethod::Name); params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range); params.setRange(range);
releases = Release::findIds(context.dbSession, params); releases = Release::findIds(context.getDbSession(), params);
} }
} }
} }
else if (type == "byYear") else if (type == "byYear")
{ {
const int fromYear{ getMandatoryParameterAs<int>(context.parameters, "fromYear") }; const int fromYear{ getMandatoryParameterAs<int>(context.getParameters(), "fromYear") };
const int toYear{ getMandatoryParameterAs<int>(context.parameters, "toYear") }; const int toYear{ getMandatoryParameterAs<int>(context.getParameters(), "toYear") };
Release::FindParameters params; Release::FindParameters params;
params.setSortMethod(fromYear > toYear ? ReleaseSortMethod::DateDesc : ReleaseSortMethod::DateAsc); params.setSortMethod(fromYear > toYear ? ReleaseSortMethod::DateDesc : ReleaseSortMethod::DateAsc);
@@ -112,12 +112,12 @@ namespace lms::api::subsonic
params.setDateRange(YearRange{ std::min(fromYear, toYear), std::max(fromYear, toYear) }); params.setDateRange(YearRange{ std::min(fromYear, toYear), std::max(fromYear, toYear) });
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params); releases = Release::findIds(context.getDbSession(), params);
} }
else if (type == "frequent") else if (type == "frequent")
{ {
scrobbling::IScrobblingService::FindParameters params; scrobbling::IScrobblingService::FindParameters params;
params.setUser(context.user->getId()); params.setUser(context.getUser()->getId());
params.setRange(range); params.setRange(range);
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
@@ -130,7 +130,7 @@ namespace lms::api::subsonic
params.setRange(range); params.setRange(range);
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params); releases = Release::findIds(context.getDbSession(), params);
} }
else if (type == "random") else if (type == "random")
{ {
@@ -141,12 +141,12 @@ namespace lms::api::subsonic
params.setRange(Range{ 0, size }); params.setRange(Range{ 0, size });
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params); releases = Release::findIds(context.getDbSession(), params);
} }
else if (type == "recent") else if (type == "recent")
{ {
scrobbling::IScrobblingService::FindParameters params; scrobbling::IScrobblingService::FindParameters params;
params.setUser(context.user->getId()); params.setUser(context.getUser()->getId());
params.setRange(range); params.setRange(range);
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
@@ -155,7 +155,7 @@ namespace lms::api::subsonic
else if (type == "starred") else if (type == "starred")
{ {
feedback::IFeedbackService::FindParameters params; feedback::IFeedbackService::FindParameters params;
params.setUser(context.user->getId()); params.setUser(context.getUser()->getId());
params.setRange(range); params.setRange(range);
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
@@ -166,12 +166,12 @@ namespace lms::api::subsonic
throw NotImplementedGenericError{}; throw NotImplementedGenericError{};
} }
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& albumListNode{ response.createNode(id3 ? Response::Node::Key{ "albumList2" } : Response::Node::Key{ "albumList" }) }; Response::Node& albumListNode{ response.createNode(id3 ? Response::Node::Key{ "albumList2" } : Response::Node::Key{ "albumList" }) };
for (const ReleaseId releaseId : releases.results) for (const ReleaseId releaseId : releases.results)
{ {
const Release::pointer release{ Release::find(context.dbSession, releaseId) }; const Release::pointer release{ Release::find(context.getDbSession(), releaseId) };
albumListNode.addArrayChild("album", createAlbumNode(context, release, id3)); albumListNode.addArrayChild("album", createAlbumNode(context, release, id3));
} }
@@ -181,11 +181,11 @@ namespace lms::api::subsonic
Response handleGetStarredRequestCommon(RequestContext& context, bool id3) Response handleGetStarredRequestCommon(RequestContext& context, bool id3)
{ {
// Optional parameters // Optional parameters
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) }; const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& starredNode{ response.createNode(id3 ? Response::Node::Key{ "starred2" } : Response::Node::Key{ "starred" }) }; Response::Node& starredNode{ response.createNode(id3 ? Response::Node::Key{ "starred2" } : Response::Node::Key{ "starred" }) };
feedback::IFeedbackService& feedbackService{ *core::Service<feedback::IFeedbackService>::get() }; feedback::IFeedbackService& feedbackService{ *core::Service<feedback::IFeedbackService>::get() };
@@ -194,29 +194,29 @@ namespace lms::api::subsonic
if (id3) if (id3)
{ {
feedback::IFeedbackService::ArtistFindParameters artistFindParams; feedback::IFeedbackService::ArtistFindParameters artistFindParams;
artistFindParams.setUser(context.user->getId()); artistFindParams.setUser(context.getUser()->getId());
artistFindParams.setSortMethod(ArtistSortMethod::SortName); artistFindParams.setSortMethod(ArtistSortMethod::SortName);
for (const ArtistId artistId : feedbackService.findStarredArtists(artistFindParams).results) for (const ArtistId artistId : feedbackService.findStarredArtists(artistFindParams).results)
{ {
if (auto artist{ Artist::find(context.dbSession, artistId) }) if (auto artist{ Artist::find(context.getDbSession(), artistId) })
starredNode.addArrayChild("artist", createArtistNode(context, artist)); starredNode.addArrayChild("artist", createArtistNode(context, artist));
} }
} }
feedback::IFeedbackService::FindParameters findParameters; feedback::IFeedbackService::FindParameters findParameters;
findParameters.setUser(context.user->getId()); findParameters.setUser(context.getUser()->getId());
findParameters.filters.setMediaLibrary(mediaLibrary); findParameters.filters.setMediaLibrary(mediaLibrary);
for (const ReleaseId releaseId : feedbackService.findStarredReleases(findParameters).results) for (const ReleaseId releaseId : feedbackService.findStarredReleases(findParameters).results)
{ {
if (auto release{ Release::find(context.dbSession, releaseId) }) if (auto release{ Release::find(context.getDbSession(), releaseId) })
starredNode.addArrayChild("album", createAlbumNode(context, release, id3)); starredNode.addArrayChild("album", createAlbumNode(context, release, id3));
} }
for (const TrackId trackId : feedbackService.findStarredTracks(findParameters).results) for (const TrackId trackId : feedbackService.findStarredTracks(findParameters).results)
{ {
if (auto track{ Track::find(context.dbSession, trackId) }) if (auto track{ Track::find(context.getDbSession(), trackId) })
starredNode.addArrayChild("song", createSongNode(context, track, context.user)); starredNode.addArrayChild("song", createSongNode(context, track, context.getUser()));
} }
return response; return response;
@@ -236,23 +236,23 @@ namespace lms::api::subsonic
Response handleGetRandomSongsRequest(RequestContext& context) Response handleGetRandomSongsRequest(RequestContext& context)
{ {
// Optional params // Optional params
const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) }; const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(50) }; std::size_t size{ getParameterAs<std::size_t>(context.getParameters(), "size").value_or(50) };
if (size > defaultMaxCountSize) if (size > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& randomSongsNode{ response.createNode("randomSongs") }; Response::Node& randomSongsNode{ response.createNode("randomSongs") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
Track::FindParameters params; Track::FindParameters params;
params.setSortMethod(TrackSortMethod::Random); params.setSortMethod(TrackSortMethod::Random);
params.setRange(Range{ 0, size }); params.setRange(Range{ 0, size });
params.filters.setMediaLibrary(mediaLibraryId); params.filters.setMediaLibrary(mediaLibraryId);
Track::find(context.dbSession, params, [&](const Track::pointer& track) { Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
randomSongsNode.addArrayChild("song", createSongNode(context, track, context.user)); randomSongsNode.addArrayChild("song", createSongNode(context, track, context.getUser()));
}); });
return response; return response;
@@ -261,19 +261,19 @@ namespace lms::api::subsonic
Response handleGetSongsByGenreRequest(RequestContext& context) Response handleGetSongsByGenreRequest(RequestContext& context)
{ {
// Mandatory params // Mandatory params
std::string genre{ getMandatoryParameterAs<std::string>(context.parameters, "genre") }; std::string genre{ getMandatoryParameterAs<std::string>(context.getParameters(), "genre") };
// Optional params // Optional params
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) }; const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(10) }; std::size_t count{ getParameterAs<std::size_t>(context.getParameters(), "count").value_or(10) };
if (count > defaultMaxCountSize) if (count > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize };
std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) }; std::size_t offset{ getParameterAs<std::size_t>(context.getParameters(), "offset").value_or(0) };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
auto clusterType{ ClusterType::find(context.dbSession, "GENRE") }; auto clusterType{ ClusterType::find(context.getDbSession(), "GENRE") };
if (!clusterType) if (!clusterType)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
@@ -281,7 +281,7 @@ namespace lms::api::subsonic
if (!cluster) if (!cluster)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& songsByGenreNode{ response.createNode("songsByGenre") }; Response::Node& songsByGenreNode{ response.createNode("songsByGenre") };
Track::FindParameters params; Track::FindParameters params;
@@ -289,8 +289,8 @@ namespace lms::api::subsonic
params.filters.setMediaLibrary(mediaLibrary); params.filters.setMediaLibrary(mediaLibrary);
params.setRange(Range{ offset, count }); params.setRange(Range{ offset, count });
Track::find(context.dbSession, params, [&](const Track::pointer& track) { Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.user)); songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.getUser()));
}); });
return response; return response;
+30 -30
View File
@@ -38,18 +38,18 @@ namespace lms::api::subsonic
Response handleGetBookmarks(RequestContext& context) Response handleGetBookmarks(RequestContext& context)
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const auto bookmarkIds{ TrackBookmark::find(context.dbSession, context.user->getId()) }; const auto bookmarkIds{ TrackBookmark::find(context.getDbSession(), context.getUser()->getId()) };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& bookmarksNode{ response.createNode("bookmarks") }; Response::Node& bookmarksNode{ response.createNode("bookmarks") };
for (const TrackBookmarkId bookmarkId : bookmarkIds.results) for (const TrackBookmarkId bookmarkId : bookmarkIds.results)
{ {
const TrackBookmark::pointer bookmark{ TrackBookmark::find(context.dbSession, bookmarkId) }; const TrackBookmark::pointer bookmark{ TrackBookmark::find(context.getDbSession(), bookmarkId) };
Response::Node bookmarkNode{ createBookmarkNode(bookmark) }; Response::Node bookmarkNode{ createBookmarkNode(bookmark) };
bookmarkNode.addChild("entry", createSongNode(context, bookmark->getTrack(), context.user)); bookmarkNode.addChild("entry", createSongNode(context, bookmark->getTrack(), context.getUser()));
bookmarksNode.addArrayChild("bookmark", std::move(bookmarkNode)); bookmarksNode.addArrayChild("bookmark", std::move(bookmarkNode));
} }
@@ -59,51 +59,51 @@ namespace lms::api::subsonic
Response handleCreateBookmark(RequestContext& context) Response handleCreateBookmark(RequestContext& context)
{ {
// Mandatory params // Mandatory params
TrackId trackId{ getMandatoryParameterAs<TrackId>(context.parameters, "id") }; TrackId trackId{ getMandatoryParameterAs<TrackId>(context.getParameters(), "id") };
unsigned long position{ getMandatoryParameterAs<unsigned long>(context.parameters, "position") }; unsigned long position{ getMandatoryParameterAs<unsigned long>(context.getParameters(), "position") };
const std::optional<std::string> comment{ getParameterAs<std::string>(context.parameters, "comment") }; const std::optional<std::string> comment{ getParameterAs<std::string>(context.getParameters(), "comment") };
auto transaction{ context.dbSession.createWriteTransaction() }; auto transaction{ context.getDbSession().createWriteTransaction() };
const Track::pointer track{ Track::find(context.dbSession, trackId) }; const Track::pointer track{ Track::find(context.getDbSession(), trackId) };
if (!track) if (!track)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
// Replace any existing bookmark // Replace any existing bookmark
auto bookmark{ TrackBookmark::find(context.dbSession, context.user->getId(), trackId) }; auto bookmark{ TrackBookmark::find(context.getDbSession(), context.getUser()->getId(), trackId) };
if (!bookmark) if (!bookmark)
bookmark = context.dbSession.create<TrackBookmark>(context.user, track); bookmark = context.getDbSession().create<TrackBookmark>(context.getUser(), track);
bookmark.modify()->setOffset(std::chrono::milliseconds{ position }); bookmark.modify()->setOffset(std::chrono::milliseconds{ position });
if (comment) if (comment)
bookmark.modify()->setComment(*comment); bookmark.modify()->setComment(*comment);
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleDeleteBookmark(RequestContext& context) Response handleDeleteBookmark(RequestContext& context)
{ {
// Mandatory params // Mandatory params
TrackId trackId{ getMandatoryParameterAs<TrackId>(context.parameters, "id") }; TrackId trackId{ getMandatoryParameterAs<TrackId>(context.getParameters(), "id") };
auto transaction{ context.dbSession.createWriteTransaction() }; auto transaction{ context.getDbSession().createWriteTransaction() };
auto bookmark{ TrackBookmark::find(context.dbSession, context.user->getId(), trackId) }; auto bookmark{ TrackBookmark::find(context.getDbSession(), context.getUser()->getId(), trackId) };
if (!bookmark) if (!bookmark)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
bookmark.remove(); bookmark.remove();
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
// Use a dedicated internal playlist // Use a dedicated internal playlist
Response handleGetPlayQueue(RequestContext& context) Response handleGetPlayQueue(RequestContext& context)
{ {
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const db::PlayQueue::pointer playQueue{ db::PlayQueue::find(context.dbSession, context.user->getId(), "subsonic") }; const db::PlayQueue::pointer playQueue{ db::PlayQueue::find(context.getDbSession(), context.getUser()->getId(), "subsonic") };
if (playQueue) if (playQueue)
{ {
Response::Node& playQueueNode{ response.createNode("playQueue") }; Response::Node& playQueueNode{ response.createNode("playQueue") };
@@ -115,7 +115,7 @@ namespace lms::api::subsonic
} }
// mandatory fields // mandatory fields
playQueueNode.setAttribute("username", context.user->getLoginName()); playQueueNode.setAttribute("username", context.getUser()->getLoginName());
playQueueNode.setAttribute("changed", core::stringUtils::toISO8601String(playQueue->getLastModifiedDateTime())); playQueueNode.setAttribute("changed", core::stringUtils::toISO8601String(playQueue->getLastModifiedDateTime()));
playQueueNode.setAttribute("changedBy", "unknown"); // we don't store the client name (could be several same clients on several devices...) playQueueNode.setAttribute("changedBy", "unknown"); // we don't store the client name (could be several same clients on several devices...)
@@ -130,9 +130,9 @@ namespace lms::api::subsonic
Response handleSavePlayQueue(RequestContext& context) Response handleSavePlayQueue(RequestContext& context)
{ {
// optional params // optional params
std::vector<db::TrackId> trackIds{ getMultiParametersAs<TrackId>(context.parameters, "id") }; std::vector<db::TrackId> trackIds{ getMultiParametersAs<TrackId>(context.getParameters(), "id") };
const std::optional<db::TrackId> currentTrackId{ getParameterAs<db::TrackId>(context.parameters, "current") }; const std::optional<db::TrackId> currentTrackId{ getParameterAs<db::TrackId>(context.getParameters(), "current") };
const std::chrono::milliseconds currentPositionInTrack{ getParameterAs<std::size_t>(context.parameters, "current").value_or(0) }; const std::chrono::milliseconds currentPositionInTrack{ getParameterAs<std::size_t>(context.getParameters(), "current").value_or(0) };
std::vector<db::Track::pointer> tracks; std::vector<db::Track::pointer> tracks;
tracks.reserve(trackIds.size()); tracks.reserve(trackIds.size());
@@ -140,20 +140,20 @@ namespace lms::api::subsonic
// no id means we clear the play queue (see https://github.com/opensubsonic/open-subsonic-api/pull/106) // no id means we clear the play queue (see https://github.com/opensubsonic/open-subsonic-api/pull/106)
if (!trackIds.empty()) if (!trackIds.empty())
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
for (db::TrackId trackId : trackIds) for (db::TrackId trackId : trackIds)
{ {
if (db::Track::pointer track{ db::Track::find(context.dbSession, trackId) }) if (db::Track::pointer track{ db::Track::find(context.getDbSession(), trackId) })
tracks.push_back(track); tracks.push_back(track);
} }
} }
{ {
auto transaction{ context.dbSession.createWriteTransaction() }; auto transaction{ context.getDbSession().createWriteTransaction() };
db::PlayQueue::pointer playQueue{ db::PlayQueue::find(context.dbSession, context.user->getId(), "subsonic") }; db::PlayQueue::pointer playQueue{ db::PlayQueue::find(context.getDbSession(), context.getUser()->getId(), "subsonic") };
if (!playQueue) if (!playQueue)
playQueue = context.dbSession.create<db::PlayQueue>(context.user, "subsonic"); playQueue = context.getDbSession().create<db::PlayQueue>(context.getUser(), "subsonic");
playQueue.modify()->clear(); playQueue.modify()->clear();
std::size_t index{}; std::size_t index{};
@@ -171,6 +171,6 @@ namespace lms::api::subsonic
playQueue.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime()); playQueue.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime());
} }
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
} // namespace lms::api::subsonic } // namespace lms::api::subsonic
+85 -85
View File
@@ -91,7 +91,7 @@ namespace lms::api::subsonic
params.setParentDirectory(parentDirectory->getId()); params.setParentDirectory(parentDirectory->getId());
params.setSortMethod(DirectorySortMethod::Name); params.setSortMethod(DirectorySortMethod::Name);
Directory::find(context.dbSession, params, [&](const Directory::pointer& directory) { Directory::find(context.getDbSession(), params, [&](const Directory::pointer& directory) {
const std::string_view name{ directory->getName() }; const std::string_view name{ directory->getName() };
assert(!name.empty()); assert(!name.empty());
@@ -114,7 +114,7 @@ namespace lms::api::subsonic
const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 }; const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
std::vector<TrackId> tracks; std::vector<TrackId> tracks;
tracks.reserve(count); tracks.reserve(count);
@@ -126,7 +126,7 @@ namespace lms::api::subsonic
params.setRange(Range{ 0, meanTrackCountPerArtist }); params.setRange(Range{ 0, meanTrackCountPerArtist });
params.setSortMethod(TrackSortMethod::Random); params.setSortMethod(TrackSortMethod::Random);
const auto artistTracks{ Track::findIds(context.dbSession, params) }; const auto artistTracks{ Track::findIds(context.getDbSession(), params) };
tracks.insert(std::end(tracks), tracks.insert(std::end(tracks),
std::begin(artistTracks.results), std::begin(artistTracks.results),
std::end(artistTracks.results)); std::end(artistTracks.results));
@@ -145,7 +145,7 @@ namespace lms::api::subsonic
const std::size_t meanTrackCountPerRelease{ (count / releaseIds.size()) + 1 }; const std::size_t meanTrackCountPerRelease{ (count / releaseIds.size()) + 1 };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
std::vector<TrackId> tracks; std::vector<TrackId> tracks;
tracks.reserve(count); tracks.reserve(count);
@@ -157,7 +157,7 @@ namespace lms::api::subsonic
params.setRange(Range{ 0, meanTrackCountPerRelease }); params.setRange(Range{ 0, meanTrackCountPerRelease });
params.setSortMethod(TrackSortMethod::Random); params.setSortMethod(TrackSortMethod::Random);
const auto releaseTracks{ Track::findIds(context.dbSession, params) }; const auto releaseTracks{ Track::findIds(context.getDbSession(), params) };
tracks.insert(std::end(tracks), tracks.insert(std::end(tracks),
std::begin(releaseTracks.results), std::begin(releaseTracks.results),
std::end(releaseTracks.results)); std::end(releaseTracks.results));
@@ -174,31 +174,31 @@ namespace lms::api::subsonic
Response handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3) Response handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
{ {
// Optional params // Optional params
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(50) }; std::size_t count{ getParameterAs<std::size_t>(context.getParameters(), "count").value_or(50) };
if (count > defaultMaxCountSize) if (count > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize };
std::vector<TrackId> tracks; std::vector<TrackId> tracks;
if (const auto artistId{ getParameterAs<ArtistId>(context.parameters, "id") }) if (const auto artistId{ getParameterAs<ArtistId>(context.getParameters(), "id") })
tracks = findSimilarSongs(context, *artistId, count); tracks = findSimilarSongs(context, *artistId, count);
else if (const auto releaseId{ getParameterAs<ReleaseId>(context.parameters, "id") }) else if (const auto releaseId{ getParameterAs<ReleaseId>(context.getParameters(), "id") })
tracks = findSimilarSongs(context, *releaseId, count); tracks = findSimilarSongs(context, *releaseId, count);
else if (const auto trackId{ getParameterAs<TrackId>(context.parameters, "id") }) else if (const auto trackId{ getParameterAs<TrackId>(context.getParameters(), "id") })
tracks = findSimilarSongs(context, *trackId, count); tracks = findSimilarSongs(context, *trackId, count);
else else
throw BadParameterGenericError{ "id" }; throw BadParameterGenericError{ "id" };
core::random::shuffleContainer(tracks); core::random::shuffleContainer(tracks);
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& similarSongsNode{ response.createNode(id3 ? Response::Node::Key{ "similarSongs2" } : Response::Node::Key{ "similarSongs" }) }; Response::Node& similarSongsNode{ response.createNode(id3 ? Response::Node::Key{ "similarSongs2" } : Response::Node::Key{ "similarSongs" }) };
for (const TrackId trackId : tracks) for (const TrackId trackId : tracks)
{ {
const Track::pointer track{ Track::find(context.dbSession, trackId) }; const Track::pointer track{ Track::find(context.getDbSession(), trackId) };
similarSongsNode.addArrayChild("song", createSongNode(context, track, context.user)); similarSongsNode.addArrayChild("song", createSongNode(context, track, context.getUser()));
} }
return response; return response;
@@ -223,11 +223,11 @@ namespace lms::api::subsonic
Response handleGetMusicFoldersRequest(RequestContext& context) Response handleGetMusicFoldersRequest(RequestContext& context)
{ {
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& musicFoldersNode{ response.createNode("musicFolders") }; Response::Node& musicFoldersNode{ response.createNode("musicFolders") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library) { MediaLibrary::find(context.getDbSession(), [&](const MediaLibrary::pointer& library) {
Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") }; Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") };
musicFolderNode.setAttribute("id", library->getId().getValue()); musicFolderNode.setAttribute("id", library->getId().getValue());
@@ -240,16 +240,16 @@ namespace lms::api::subsonic
Response handleGetIndexesRequest(RequestContext& context) Response handleGetIndexesRequest(RequestContext& context)
{ {
// Optional params // Optional params
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) }; const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& indexesNode{ response.createNode("indexes") }; Response::Node& indexesNode{ response.createNode("indexes") };
indexesNode.setAttribute("ignoredArticles", ""); indexesNode.setAttribute("ignoredArticles", "");
indexesNode.setAttribute("lastModified", reportedDummyDateULong); // TODO report last file write? indexesNode.setAttribute("lastModified", reportedDummyDateULong); // TODO report last file write?
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const std::vector<Directory::pointer> rootDirectories{ getRootDirectories(context.dbSession, mediaLibrary) }; const std::vector<Directory::pointer> rootDirectories{ getRootDirectories(context.getDbSession(), mediaLibrary) };
IndexMap indexedDirectories; IndexMap indexedDirectories;
for (const Directory::pointer& rootdirectory : rootDirectories) for (const Directory::pointer& rootdirectory : rootDirectories)
@@ -257,8 +257,8 @@ namespace lms::api::subsonic
Track::FindParameters params; Track::FindParameters params;
params.setDirectory(rootdirectory->getId()); params.setDirectory(rootdirectory->getId());
Track::find(context.dbSession, params, [&](const Track::pointer& track) { Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
indexesNode.addArrayChild("child", createSongNode(context, track, context.user)); indexesNode.addArrayChild("child", createSongNode(context, track, context.getUser()));
}); });
getIndexedChildDirectories(context, rootdirectory, indexedDirectories); getIndexedChildDirectories(context, rootdirectory, indexedDirectories);
@@ -287,21 +287,21 @@ namespace lms::api::subsonic
Response handleGetMusicDirectoryRequest(RequestContext& context) Response handleGetMusicDirectoryRequest(RequestContext& context)
{ {
// Mandatory params // Mandatory params
const auto directoryId{ getMandatoryParameterAs<DirectoryId>(context.parameters, "id") }; const auto directoryId{ getMandatoryParameterAs<DirectoryId>(context.getParameters(), "id") };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& directoryNode{ response.createNode("directory") }; Response::Node& directoryNode{ response.createNode("directory") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const Directory::pointer directory{ Directory::find(context.dbSession, directoryId) }; const Directory::pointer directory{ Directory::find(context.getDbSession(), directoryId) };
if (!directory) if (!directory)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
if (const Release::pointer release{ getReleaseFromDirectory(context.dbSession, directoryId) }) if (const Release::pointer release{ getReleaseFromDirectory(context.getDbSession(), directoryId) })
{ {
directoryNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.user->getId(), release->getId())); directoryNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.getUser()->getId(), release->getId()));
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.user->getId(), release->getId()) }; dateTime.isValid()) if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.getUser()->getId(), release->getId()) }; dateTime.isValid())
directoryNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime)); directoryNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
} }
@@ -317,8 +317,8 @@ namespace lms::api::subsonic
params.setParentDirectory(directory->getId()); params.setParentDirectory(directory->getId());
params.setSortMethod(DirectorySortMethod::Name); params.setSortMethod(DirectorySortMethod::Name);
Directory::find(context.dbSession, params, [&](const Directory::pointer& subDirectory) { Directory::find(context.getDbSession(), params, [&](const Directory::pointer& subDirectory) {
const Release::pointer release{ getReleaseFromDirectory(context.dbSession, subDirectory->getId()) }; const Release::pointer release{ getReleaseFromDirectory(context.getDbSession(), subDirectory->getId()) };
if (release) if (release)
{ {
@@ -343,8 +343,8 @@ namespace lms::api::subsonic
params.setDirectory(directory->getId()); params.setDirectory(directory->getId());
params.setSortMethod(TrackSortMethod::AbsoluteFilePath); params.setSortMethod(TrackSortMethod::AbsoluteFilePath);
Track::find(context.dbSession, params, [&](const Track::pointer& track) { Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
directoryNode.addArrayChild("child", createSongNode(context, track, context.user)); directoryNode.addArrayChild("child", createSongNode(context, track, context.getUser()));
}); });
} }
@@ -353,13 +353,13 @@ namespace lms::api::subsonic
Response handleGetGenresRequest(RequestContext& context) Response handleGetGenresRequest(RequestContext& context)
{ {
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& genresNode{ response.createNode("genres") }; Response::Node& genresNode{ response.createNode("genres") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const ClusterType::pointer clusterType{ ClusterType::find(context.dbSession, "GENRE") }; const ClusterType::pointer clusterType{ ClusterType::find(context.getDbSession(), "GENRE") };
if (clusterType) if (clusterType)
{ {
const auto clusters{ clusterType->getClusters() }; const auto clusters{ clusterType->getClusters() };
@@ -374,9 +374,9 @@ namespace lms::api::subsonic
Response handleGetArtistsRequest(RequestContext& context) Response handleGetArtistsRequest(RequestContext& context)
{ {
// Optional params // Optional params
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) }; const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& artistsNode{ response.createNode("artists") }; Response::Node& artistsNode{ response.createNode("artists") };
artistsNode.setAttribute("ignoredArticles", ""); artistsNode.setAttribute("ignoredArticles", "");
@@ -384,10 +384,10 @@ namespace lms::api::subsonic
Artist::FindParameters parameters; Artist::FindParameters parameters;
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
parameters.setSortMethod(ArtistSortMethod::SortName); parameters.setSortMethod(ArtistSortMethod::SortName);
switch (context.user->getSubsonicArtistListMode()) switch (context.getUser()->getSubsonicArtistListMode())
{ {
case SubsonicArtistListMode::AllArtists: case SubsonicArtistListMode::AllArtists:
break; break;
@@ -411,10 +411,10 @@ namespace lms::api::subsonic
bool hasMoreArtists{ true }; bool hasMoreArtists{ true };
while (hasMoreArtists) while (hasMoreArtists)
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
parameters.setRange(Range{ currentArtistOffset, batchSize }); parameters.setRange(Range{ currentArtistOffset, batchSize });
const auto artists{ Artist::find(context.dbSession, parameters) }; const auto artists{ Artist::find(context.getDbSession(), parameters) };
for (const Artist::pointer& artist : artists.results) for (const Artist::pointer& artist : artists.results)
{ {
std::string_view sortName{ artist->getSortName() }; std::string_view sortName{ artist->getSortName() };
@@ -441,9 +441,9 @@ namespace lms::api::subsonic
for (const ArtistId artistId : artistIds) for (const ArtistId artistId : artistIds)
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
if (const Artist::pointer artist{ Artist::find(context.dbSession, artistId) }) if (const Artist::pointer artist{ Artist::find(context.getDbSession(), artistId) })
indexNode.addArrayChild("artist", createArtistNode(context, artist)); indexNode.addArrayChild("artist", createArtistNode(context, artist));
} }
} }
@@ -454,18 +454,18 @@ namespace lms::api::subsonic
Response handleGetArtistRequest(RequestContext& context) Response handleGetArtistRequest(RequestContext& context)
{ {
// Mandatory params // Mandatory params
ArtistId id{ getMandatoryParameterAs<ArtistId>(context.parameters, "id") }; ArtistId id{ getMandatoryParameterAs<ArtistId>(context.getParameters(), "id") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const Artist::pointer artist{ Artist::find(context.dbSession, id) }; const Artist::pointer artist{ Artist::find(context.getDbSession(), id) };
if (!artist) if (!artist)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node artistNode{ createArtistNode(context, artist) }; Response::Node artistNode{ createArtistNode(context, artist) };
const auto releases{ Release::find(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) }; const auto releases{ Release::find(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId())) };
for (const Release::pointer& release : releases.results) for (const Release::pointer& release : releases.results)
artistNode.addArrayChild("album", createAlbumNode(context, release, true /* id3 */)); artistNode.addArrayChild("album", createAlbumNode(context, release, true /* id3 */));
@@ -477,18 +477,18 @@ namespace lms::api::subsonic
Response handleGetAlbumRequest(RequestContext& context) Response handleGetAlbumRequest(RequestContext& context)
{ {
// Mandatory params // Mandatory params
ReleaseId id{ getMandatoryParameterAs<ReleaseId>(context.parameters, "id") }; ReleaseId id{ getMandatoryParameterAs<ReleaseId>(context.getParameters(), "id") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
Release::pointer release{ Release::find(context.dbSession, id) }; Release::pointer release{ Release::find(context.getDbSession(), id) };
if (!release) if (!release)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node albumNode{ createAlbumNode(context, release, true /* id3 */) }; Response::Node albumNode{ createAlbumNode(context, release, true /* id3 */) };
const auto tracks{ Track::find(context.dbSession, Track::FindParameters{}.setRelease(id).setSortMethod(TrackSortMethod::Release)) }; const auto tracks{ Track::find(context.getDbSession(), Track::FindParameters{}.setRelease(id).setSortMethod(TrackSortMethod::Release)) };
for (const Track::pointer& track : tracks.results) for (const Track::pointer& track : tracks.results)
albumNode.addArrayChild("song", createSongNode(context, track, true /* id3 */)); albumNode.addArrayChild("song", createSongNode(context, track, true /* id3 */));
@@ -500,16 +500,16 @@ namespace lms::api::subsonic
Response handleGetSongRequest(RequestContext& context) Response handleGetSongRequest(RequestContext& context)
{ {
// Mandatory params // Mandatory params
TrackId id{ getMandatoryParameterAs<TrackId>(context.parameters, "id") }; TrackId id{ getMandatoryParameterAs<TrackId>(context.getParameters(), "id") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const Track::pointer track{ Track::find(context.dbSession, id) }; const Track::pointer track{ Track::find(context.getDbSession(), id) };
if (!track) if (!track)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
response.addNode("song", createSongNode(context, track, context.user)); response.addNode("song", createSongNode(context, track, context.getUser()));
return response; return response;
} }
@@ -517,24 +517,24 @@ namespace lms::api::subsonic
Response handleGetArtistInfo2Request(RequestContext& context) Response handleGetArtistInfo2Request(RequestContext& context)
{ {
// Mandatory params // Mandatory params
ArtistId id{ getMandatoryParameterAs<ArtistId>(context.parameters, "id") }; ArtistId id{ getMandatoryParameterAs<ArtistId>(context.getParameters(), "id") };
// Optional params // Optional params
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(20) }; std::size_t count{ getParameterAs<std::size_t>(context.getParameters(), "count").value_or(20) };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& artistInfoNode{ response.createNode(Response::Node::Key{ "artistInfo2" }) }; Response::Node& artistInfoNode{ response.createNode(Response::Node::Key{ "artistInfo2" }) };
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const Artist::pointer artist{ Artist::find(context.dbSession, id) }; const Artist::pointer artist{ Artist::find(context.getDbSession(), id) };
if (!artist) if (!artist)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
if (const std::optional<core::UUID> artistMBID{ artist->getMBID() }) if (const std::optional<core::UUID> artistMBID{ artist->getMBID() })
{ {
switch (context.responseFormat) switch (context.getResponseFormat())
{ {
case ResponseFormat::json: case ResponseFormat::json:
artistInfoNode.setAttribute("musicBrainzId", artistMBID->getAsString()); artistInfoNode.setAttribute("musicBrainzId", artistMBID->getAsString());
@@ -545,10 +545,10 @@ namespace lms::api::subsonic
} }
} }
ArtistInfo::find(context.dbSession, id, Range{ .offset = 0, .size = 1 }, [&](const ArtistInfo::pointer& artistInfo) { ArtistInfo::find(context.getDbSession(), id, Range{ .offset = 0, .size = 1 }, [&](const ArtistInfo::pointer& artistInfo) {
if (!artistInfo->getBiography().empty()) if (!artistInfo->getBiography().empty())
{ {
switch (context.responseFormat) switch (context.getResponseFormat())
{ {
case ResponseFormat::json: case ResponseFormat::json:
artistInfoNode.setAttribute("biography", artistInfo->getBiography()); artistInfoNode.setAttribute("biography", artistInfo->getBiography());
@@ -564,11 +564,11 @@ namespace lms::api::subsonic
auto similarArtistsId{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(id, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, count) }; auto similarArtistsId{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(id, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, count) };
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
for (const ArtistId similarArtistId : similarArtistsId) for (const ArtistId similarArtistId : similarArtistsId)
{ {
const Artist::pointer similarArtist{ Artist::find(context.dbSession, similarArtistId) }; const Artist::pointer similarArtist{ Artist::find(context.getDbSession(), similarArtistId) };
if (similarArtist) if (similarArtist)
artistInfoNode.addArrayChild("similarArtist", createArtistNode(context, similarArtist)); artistInfoNode.addArrayChild("similarArtist", createArtistNode(context, similarArtist));
} }
@@ -579,14 +579,14 @@ namespace lms::api::subsonic
Response handleGetAlbumInfo(RequestContext& context) Response handleGetAlbumInfo(RequestContext& context)
{ {
const db::DirectoryId directoryId{ getMandatoryParameterAs<db::DirectoryId>(context.parameters, "id") }; const db::DirectoryId directoryId{ getMandatoryParameterAs<db::DirectoryId>(context.getParameters(), "id") };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
if (db::Release::pointer release{ getReleaseFromDirectory(context.dbSession, directoryId) }) if (db::Release::pointer release{ getReleaseFromDirectory(context.getDbSession(), directoryId) })
response.addNode("albumInfo", createAlbumInfoNode(context, release)); response.addNode("albumInfo", createAlbumInfoNode(context, release));
} }
return response; return response;
@@ -594,14 +594,14 @@ namespace lms::api::subsonic
Response handleGetAlbumInfo2(RequestContext& context) Response handleGetAlbumInfo2(RequestContext& context)
{ {
const db::ReleaseId releaseId{ getMandatoryParameterAs<db::ReleaseId>(context.parameters, "id") }; const db::ReleaseId releaseId{ getMandatoryParameterAs<db::ReleaseId>(context.getParameters(), "id") };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
if (db::Release::pointer release{ db::Release::find(context.dbSession, releaseId) }) if (db::Release::pointer release{ db::Release::find(context.getDbSession(), releaseId) })
response.addNode("albumInfo", createAlbumInfoNode(context, release)); response.addNode("albumInfo", createAlbumInfoNode(context, release));
} }
@@ -621,29 +621,29 @@ namespace lms::api::subsonic
Response handleGetTopSongs(RequestContext& context) Response handleGetTopSongs(RequestContext& context)
{ {
// Mandatory params // Mandatory params
std::string_view artistName{ getMandatoryParameterAs<std::string_view>(context.parameters, "artist") }; std::string_view artistName{ getMandatoryParameterAs<std::string_view>(context.getParameters(), "artist") };
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(50) }; std::size_t count{ getParameterAs<std::size_t>(context.getParameters(), "count").value_or(50) };
if (count > defaultMaxCountSize) if (count > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& topSongs{ response.createNode("topSongs") }; Response::Node& topSongs{ response.createNode("topSongs") };
const auto artists{ Artist::find(context.dbSession, artistName) }; const auto artists{ Artist::find(context.getDbSession(), artistName) };
if (artists.size() == 1) if (artists.size() == 1)
{ {
scrobbling::IScrobblingService::FindParameters params; scrobbling::IScrobblingService::FindParameters params;
params.setUser(context.user->getId()); params.setUser(context.getUser()->getId());
params.setRange(db::Range{ 0, count }); params.setRange(db::Range{ 0, count });
params.setArtist(artists.front()->getId()); params.setArtist(artists.front()->getId());
const auto trackIds{ core::Service<scrobbling::IScrobblingService>::get()->getTopTracks(params) }; const auto trackIds{ core::Service<scrobbling::IScrobblingService>::get()->getTopTracks(params) };
for (const TrackId trackId : trackIds.results) for (const TrackId trackId : trackIds.results)
{ {
if (Track::pointer track{ Track::find(context.dbSession, trackId) }) if (Track::pointer track{ Track::find(context.getDbSession(), trackId) })
topSongs.addArrayChild("song", createSongNode(context, track, context.user)); topSongs.addArrayChild("song", createSongNode(context, track, context.getUser()));
} }
} }
@@ -113,72 +113,72 @@ namespace lms::api::subsonic
Response handleStarRequest(RequestContext& context) Response handleStarRequest(RequestContext& context)
{ {
StarParameters params{ getStarParameters(context.parameters) }; StarParameters params{ getStarParameters(context.getParameters()) };
for (const DirectoryId id : params.directoryIds) for (const DirectoryId id : params.directoryIds)
{ {
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.dbSession, id) }; releaseId.isValid()) if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.getDbSession(), id) }; releaseId.isValid())
core::Service<feedback::IFeedbackService>::get()->star(context.user->getId(), releaseId); core::Service<feedback::IFeedbackService>::get()->star(context.getUser()->getId(), releaseId);
} }
for (const ArtistId id : params.artistIds) for (const ArtistId id : params.artistIds)
core::Service<feedback::IFeedbackService>::get()->star(context.user->getId(), id); core::Service<feedback::IFeedbackService>::get()->star(context.getUser()->getId(), id);
for (const ReleaseId id : params.releaseIds) for (const ReleaseId id : params.releaseIds)
core::Service<feedback::IFeedbackService>::get()->star(context.user->getId(), id); core::Service<feedback::IFeedbackService>::get()->star(context.getUser()->getId(), id);
for (const TrackId id : params.trackIds) for (const TrackId id : params.trackIds)
core::Service<feedback::IFeedbackService>::get()->star(context.user->getId(), id); core::Service<feedback::IFeedbackService>::get()->star(context.getUser()->getId(), id);
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleUnstarRequest(RequestContext& context) Response handleUnstarRequest(RequestContext& context)
{ {
const StarParameters params{ getStarParameters(context.parameters) }; const StarParameters params{ getStarParameters(context.getParameters()) };
for (const DirectoryId id : params.directoryIds) for (const DirectoryId id : params.directoryIds)
{ {
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.dbSession, id) }; releaseId.isValid()) if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.getDbSession(), id) }; releaseId.isValid())
core::Service<feedback::IFeedbackService>::get()->unstar(context.user->getId(), releaseId); core::Service<feedback::IFeedbackService>::get()->unstar(context.getUser()->getId(), releaseId);
} }
for (const ArtistId id : params.artistIds) for (const ArtistId id : params.artistIds)
core::Service<feedback::IFeedbackService>::get()->unstar(context.user->getId(), id); core::Service<feedback::IFeedbackService>::get()->unstar(context.getUser()->getId(), id);
for (const ReleaseId id : params.releaseIds) for (const ReleaseId id : params.releaseIds)
core::Service<feedback::IFeedbackService>::get()->unstar(context.user->getId(), id); core::Service<feedback::IFeedbackService>::get()->unstar(context.getUser()->getId(), id);
for (const TrackId id : params.trackIds) for (const TrackId id : params.trackIds)
core::Service<feedback::IFeedbackService>::get()->unstar(context.user->getId(), id); core::Service<feedback::IFeedbackService>::get()->unstar(context.getUser()->getId(), id);
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleSetRating(RequestContext& context) Response handleSetRating(RequestContext& context)
{ {
const RatingParameters params{ getRatingParameters(context.parameters) }; const RatingParameters params{ getRatingParameters(context.getParameters()) };
if (const ArtistId * artistId{ std::get_if<ArtistId>(&params.id) }) if (const ArtistId * artistId{ std::get_if<ArtistId>(&params.id) })
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), *artistId, params.rating); core::Service<feedback::IFeedbackService>::get()->setRating(context.getUser()->getId(), *artistId, params.rating);
else if (const DirectoryId * directoryId{ std::get_if<DirectoryId>(&params.id) }) else if (const DirectoryId * directoryId{ std::get_if<DirectoryId>(&params.id) })
{ {
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.dbSession, *directoryId) }; releaseId.isValid()) if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.getDbSession(), *directoryId) }; releaseId.isValid())
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), releaseId, params.rating); core::Service<feedback::IFeedbackService>::get()->setRating(context.getUser()->getId(), releaseId, params.rating);
} }
else if (const ReleaseId * releaseId{ std::get_if<ReleaseId>(&params.id) }) else if (const ReleaseId * releaseId{ std::get_if<ReleaseId>(&params.id) })
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), *releaseId, params.rating); core::Service<feedback::IFeedbackService>::get()->setRating(context.getUser()->getId(), *releaseId, params.rating);
else if (const TrackId * trackId{ std::get_if<TrackId>(&params.id) }) else if (const TrackId * trackId{ std::get_if<TrackId>(&params.id) })
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), *trackId, params.rating); core::Service<feedback::IFeedbackService>::get()->setRating(context.getUser()->getId(), *trackId, params.rating);
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleScrobble(RequestContext& context) Response handleScrobble(RequestContext& context)
{ {
const std::vector<TrackId> ids{ getMandatoryMultiParametersAs<TrackId>(context.parameters, "id") }; const std::vector<TrackId> ids{ getMandatoryMultiParametersAs<TrackId>(context.getParameters(), "id") };
const std::vector<unsigned long> times{ getMultiParametersAs<unsigned long>(context.parameters, "time") }; const std::vector<unsigned long> times{ getMultiParametersAs<unsigned long>(context.getParameters(), "time") };
const bool submission{ getParameterAs<bool>(context.parameters, "submission").value_or(true) }; const bool submission{ getParameterAs<bool>(context.getParameters(), "submission").value_or(true) };
// playing now => only one at a time // playing now => only one at a time
if (!submission && ids.size() > 1) if (!submission && ids.size() > 1)
@@ -190,13 +190,13 @@ namespace lms::api::subsonic
if (!submission) if (!submission)
{ {
core::Service<scrobbling::IScrobblingService>::get()->listenStarted({ context.user->getId(), ids.front() }); core::Service<scrobbling::IScrobblingService>::get()->listenStarted({ context.getUser()->getId(), ids.front() });
} }
else else
{ {
if (times.empty()) if (times.empty())
{ {
core::Service<scrobbling::IScrobblingService>::get()->listenFinished({ context.user->getId(), ids.front() }); core::Service<scrobbling::IScrobblingService>::get()->listenFinished({ context.getUser()->getId(), ids.front() });
} }
else else
{ {
@@ -204,11 +204,11 @@ namespace lms::api::subsonic
{ {
const TrackId trackId{ ids[i] }; const TrackId trackId{ ids[i] };
const unsigned long time{ times[i] }; const unsigned long time{ times[i] };
core::Service<scrobbling::IScrobblingService>::get()->addTimedListen({ { context.user->getId(), trackId }, Wt::WDateTime::fromTime_t(static_cast<std::time_t>(time / 1000)) }); core::Service<scrobbling::IScrobblingService>::get()->addTimedListen({ { context.getUser()->getId(), trackId }, Wt::WDateTime::fromTime_t(static_cast<std::time_t>(time / 1000)) });
} }
} }
} }
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
} // namespace lms::api::subsonic } // namespace lms::api::subsonic
@@ -51,7 +51,7 @@ namespace lms::api::subsonic::Scan
Response handleGetScanStatus(RequestContext& context) Response handleGetScanStatus(RequestContext& context)
{ {
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
response.addNode("scanStatus", createStatusResponseNode()); response.addNode("scanStatus", createStatusResponseNode());
return response; return response;
@@ -61,7 +61,7 @@ namespace lms::api::subsonic::Scan
{ {
core::Service<IScannerService>::get()->requestImmediateScan(); core::Service<IScannerService>::get()->requestImmediateScan();
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
response.addNode("scanStatus", createStatusResponseNode()); response.addNode("scanStatus", createStatusResponseNode());
return response; return response;
@@ -176,20 +176,20 @@ namespace lms::api::subsonic
StreamParameters getStreamParameters(RequestContext& context) StreamParameters getStreamParameters(RequestContext& context)
{ {
// Mandatory params // Mandatory params
const auto trackId{ getParameterAs<db::TrackId>(context.parameters, "id") }; const auto trackId{ getParameterAs<db::TrackId>(context.getParameters(), "id") };
const auto podcastEpisodeId{ getParameterAs<db::PodcastEpisodeId>(context.parameters, "id") }; const auto podcastEpisodeId{ getParameterAs<db::PodcastEpisodeId>(context.getParameters(), "id") };
if (!trackId && !podcastEpisodeId) if (!trackId && !podcastEpisodeId)
throw RequiredParameterMissingError{ "id" }; throw RequiredParameterMissingError{ "id" };
const AudioFileId audioId{ trackId ? AudioFileId{ *trackId } : AudioFileId{ *podcastEpisodeId } }; const AudioFileId audioId{ trackId ? AudioFileId{ *trackId } : AudioFileId{ *podcastEpisodeId } };
// Optional params // Optional params
std::size_t maxBitRate{ getParameterAs<std::size_t>(context.parameters, "maxBitRate").value_or(0) * 1000 }; // "If set to zero, no limit is imposed", given in kpbs std::size_t maxBitRate{ getParameterAs<std::size_t>(context.getParameters(), "maxBitRate").value_or(0) * 1000 }; // "If set to zero, no limit is imposed", given in kpbs
const std::string format{ getParameterAs<std::string>(context.parameters, "format").value_or("") }; const std::string format{ getParameterAs<std::string>(context.getParameters(), "format").value_or("") };
std::size_t timeOffset{ getParameterAs<std::size_t>(context.parameters, "timeOffset").value_or(0) }; std::size_t timeOffset{ getParameterAs<std::size_t>(context.getParameters(), "timeOffset").value_or(0) };
bool estimateContentLength{ getParameterAs<bool>(context.parameters, "estimateContentLength").value_or(false) }; bool estimateContentLength{ getParameterAs<bool>(context.getParameters(), "estimateContentLength").value_or(false) };
const AudioFileInfo audioFileInfo{ getAudioFileInfo(context.dbSession, audioId) }; const AudioFileInfo audioFileInfo{ getAudioFileInfo(context.getDbSession(), audioId) };
StreamParameters parameters; StreamParameters parameters;
@@ -205,8 +205,8 @@ namespace lms::api::subsonic
std::optional<transcoding::OutputFormat> requestedFormat{ subsonicStreamFormatToAvOutputFormat(format) }; std::optional<transcoding::OutputFormat> requestedFormat{ subsonicStreamFormatToAvOutputFormat(format) };
if (!requestedFormat) if (!requestedFormat)
{ {
if (context.user->getSubsonicEnableTranscodingByDefault()) if (context.getUser()->getSubsonicEnableTranscodingByDefault())
requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat()); requestedFormat = userTranscodeFormatToAvFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat());
} }
if (!requestedFormat && (maxBitRate == 0 || audioFileInfo.bitrate <= maxBitRate)) if (!requestedFormat && (maxBitRate == 0 || audioFileInfo.bitrate <= maxBitRate))
@@ -231,9 +231,9 @@ namespace lms::api::subsonic
// Need to transcode here // Need to transcode here
if (!requestedFormat) if (!requestedFormat)
requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat()); requestedFormat = userTranscodeFormatToAvFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat());
if (!bitrate) if (!bitrate)
bitrate = context.user->getSubsonicDefaultTranscodingOutputBitrate(); bitrate = context.getUser()->getSubsonicDefaultTranscodingOutputBitrate();
if (maxBitRate) if (maxBitRate)
bitrate = std::min<std::size_t>(bitrate, maxBitRate); bitrate = std::min<std::size_t>(bitrate, maxBitRate);
@@ -248,13 +248,13 @@ namespace lms::api::subsonic
Response handleGetLyrics(RequestContext& context) Response handleGetLyrics(RequestContext& context)
{ {
std::string artistName{ getParameterAs<std::string>(context.parameters, "artist").value_or("") }; std::string artistName{ getParameterAs<std::string>(context.getParameters(), "artist").value_or("") };
std::string titleName{ getParameterAs<std::string>(context.parameters, "title").value_or("") }; std::string titleName{ getParameterAs<std::string>(context.getParameters(), "title").value_or("") };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
// best effort search, as this API is really limited // best effort search, as this API is really limited
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
db::Track::FindParameters params; db::Track::FindParameters params;
params.setName(titleName); params.setName(titleName);
@@ -262,7 +262,7 @@ namespace lms::api::subsonic
params.setRange(db::Range{ .offset = 0, .size = 2 }); params.setRange(db::Range{ .offset = 0, .size = 2 });
// Choice: we return nothing if there are too many results // Choice: we return nothing if there are too many results
const auto tracks{ db::Track::findIds(context.dbSession, params) }; const auto tracks{ db::Track::findIds(context.getDbSession(), params) };
if (tracks.results.size() == 1) if (tracks.results.size() == 1)
{ {
// Choice: we return only the first lyrics if the track has many lyrics // Choice: we return only the first lyrics if the track has many lyrics
@@ -271,7 +271,7 @@ namespace lms::api::subsonic
lyricsParams.setSortMethod(db::TrackLyricsSortMethod::ExternalFirst); lyricsParams.setSortMethod(db::TrackLyricsSortMethod::ExternalFirst);
lyricsParams.setRange(db::Range{ 0, 1 }); lyricsParams.setRange(db::Range{ 0, 1 });
db::TrackLyrics::find(context.dbSession, lyricsParams, [&](const db::TrackLyrics::pointer& lyrics) { db::TrackLyrics::find(context.getDbSession(), lyricsParams, [&](const db::TrackLyrics::pointer& lyrics) {
response.addNode("lyrics", createLyricsNode(context, lyrics)); response.addNode("lyrics", createLyricsNode(context, lyrics));
}); });
} }
@@ -282,14 +282,14 @@ namespace lms::api::subsonic
Response handleGetLyricsBySongId(RequestContext& context) Response handleGetLyricsBySongId(RequestContext& context)
{ {
// mandatory params // mandatory params
db::TrackId id{ getMandatoryParameterAs<db::TrackId>(context.parameters, "id") }; db::TrackId id{ getMandatoryParameterAs<db::TrackId>(context.getParameters(), "id") };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& lyricsList{ response.createNode("lyricsList") }; Response::Node& lyricsList{ response.createNode("lyricsList") };
lyricsList.createEmptyArrayChild("structuredLyrics"); lyricsList.createEmptyArrayChild("structuredLyrics");
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const db::Track::pointer track{ db::Track::find(context.dbSession, id) }; const db::Track::pointer track{ db::Track::find(context.getDbSession(), id) };
if (track) if (track)
{ {
db::TrackLyrics::FindParameters params; db::TrackLyrics::FindParameters params;
@@ -297,7 +297,7 @@ namespace lms::api::subsonic
params.setExternal(true); // First try to only report external lyrics as they are often duplicate of embedded lyrics and support more features params.setExternal(true); // First try to only report external lyrics as they are often duplicate of embedded lyrics and support more features
bool hasExternalLyrics{}; bool hasExternalLyrics{};
db::TrackLyrics::find(context.dbSession, params, [&](const db::TrackLyrics::pointer& lyrics) { db::TrackLyrics::find(context.getDbSession(), params, [&](const db::TrackLyrics::pointer& lyrics) {
lyricsList.addArrayChild("structuredLyrics", createStructuredLyricsNode(context, lyrics)); lyricsList.addArrayChild("structuredLyrics", createStructuredLyricsNode(context, lyrics));
hasExternalLyrics = true; hasExternalLyrics = true;
}); });
@@ -305,7 +305,7 @@ namespace lms::api::subsonic
if (!hasExternalLyrics) if (!hasExternalLyrics)
{ {
params.setExternal(false); params.setExternal(false);
db::TrackLyrics::find(context.dbSession, params, [&](const db::TrackLyrics::pointer& lyrics) { db::TrackLyrics::find(context.getDbSession(), params, [&](const db::TrackLyrics::pointer& lyrics) {
lyricsList.addArrayChild("structuredLyrics", createStructuredLyricsNode(context, lyrics)); lyricsList.addArrayChild("structuredLyrics", createStructuredLyricsNode(context, lyrics));
}); });
} }
@@ -322,13 +322,13 @@ namespace lms::api::subsonic
if (!continuation) if (!continuation)
{ {
// Mandatory params // Mandatory params
db::TrackId id{ getMandatoryParameterAs<db::TrackId>(context.parameters, "id") }; db::TrackId id{ getMandatoryParameterAs<db::TrackId>(context.getParameters(), "id") };
std::filesystem::path trackPath; std::filesystem::path trackPath;
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
auto track{ db::Track::find(context.dbSession, id) }; auto track{ db::Track::find(context.getDbSession(), id) };
if (!track) if (!track)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
@@ -381,9 +381,9 @@ namespace lms::api::subsonic
void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response) void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
{ {
// Mandatory params // Mandatory params
const CoverArtId coverArtId{ getMandatoryParameterAs<CoverArtId>(context.parameters, "id") }; const CoverArtId coverArtId{ getMandatoryParameterAs<CoverArtId>(context.getParameters(), "id") };
std::optional<std::size_t> size{ getParameterAs<std::size_t>(context.parameters, "size") }; std::optional<std::size_t> size{ getParameterAs<std::size_t>(context.getParameters(), "size") };
if (size) if (size)
*size = std::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 }); *size = std::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 });
+41 -41
View File
@@ -53,9 +53,9 @@ namespace lms::api::subsonic
Response handleGetPlaylistsRequest(RequestContext& context) Response handleGetPlaylistsRequest(RequestContext& context)
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& playlistsNode{ response.createNode("playlists") }; Response::Node& playlistsNode{ response.createNode("playlists") };
auto addTrackList{ [&](const db::TrackList::pointer& trackList) { auto addTrackList{ [&](const db::TrackList::pointer& trackList) {
@@ -65,10 +65,10 @@ namespace lms::api::subsonic
// First add user's playlists // First add user's playlists
{ {
TrackList::FindParameters params; TrackList::FindParameters params;
params.setUser(context.user->getId()); params.setUser(context.getUser()->getId());
params.setType(TrackListType::PlayList); params.setType(TrackListType::PlayList);
db::TrackList::find(context.dbSession, params, [&](const db::TrackList::pointer& trackList) { db::TrackList::find(context.getDbSession(), params, [&](const db::TrackList::pointer& trackList) {
addTrackList(trackList); addTrackList(trackList);
}); });
} }
@@ -78,10 +78,10 @@ namespace lms::api::subsonic
TrackList::FindParameters params; TrackList::FindParameters params;
params.setVisibility(TrackList::Visibility::Public); params.setVisibility(TrackList::Visibility::Public);
params.setType(TrackListType::PlayList); params.setType(TrackListType::PlayList);
params.setExcludedUser(context.user->getId()); params.setExcludedUser(context.getUser()->getId());
db::TrackList::find(context.dbSession, params, [&](const db::TrackList::pointer& trackList) { db::TrackList::find(context.getDbSession(), params, [&](const db::TrackList::pointer& trackList) {
assert(trackList->getUserId() != context.user->getId()); assert(trackList->getUserId() != context.getUser()->getId());
addTrackList(trackList); addTrackList(trackList);
}); });
} }
@@ -92,23 +92,23 @@ namespace lms::api::subsonic
Response handleGetPlaylistRequest(RequestContext& context) Response handleGetPlaylistRequest(RequestContext& context)
{ {
// Mandatory params // Mandatory params
TrackListId trackListId{ getMandatoryParameterAs<TrackListId>(context.parameters, "id") }; TrackListId trackListId{ getMandatoryParameterAs<TrackListId>(context.getParameters(), "id") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
TrackList::pointer trackList{ TrackList::find(context.dbSession, trackListId) }; TrackList::pointer trackList{ TrackList::find(context.getDbSession(), trackListId) };
if (!trackList || trackList->getType() != TrackListType::PlayList) if (!trackList || trackList->getType() != TrackListType::PlayList)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
if (trackList->getUserId() != context.user->getId() && trackList->getVisibility() != TrackList::Visibility::Public) if (trackList->getUserId() != context.getUser()->getId() && trackList->getVisibility() != TrackList::Visibility::Public)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node playlistNode{ createPlaylistNode(context, trackList) }; Response::Node playlistNode{ createPlaylistNode(context, trackList) };
auto entries{ trackList->getEntries() }; auto entries{ trackList->getEntries() };
for (const TrackListEntry::pointer& entry : entries.results) for (const TrackListEntry::pointer& entry : entries.results)
playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.user)); playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.getUser()));
response.addNode("playlist", std::move(playlistNode)); response.addNode("playlist", std::move(playlistNode));
@@ -118,21 +118,21 @@ namespace lms::api::subsonic
Response handleCreatePlaylistRequest(RequestContext& context) Response handleCreatePlaylistRequest(RequestContext& context)
{ {
// Optional params // Optional params
const auto id{ getParameterAs<TrackListId>(context.parameters, "playlistId") }; const auto id{ getParameterAs<TrackListId>(context.getParameters(), "playlistId") };
auto name{ getParameterAs<std::string>(context.parameters, "name") }; auto name{ getParameterAs<std::string>(context.getParameters(), "name") };
std::vector<TrackId> trackIds{ getMultiParametersAs<TrackId>(context.parameters, "songId") }; std::vector<TrackId> trackIds{ getMultiParametersAs<TrackId>(context.getParameters(), "songId") };
if (!name && !id) if (!name && !id)
throw RequiredParameterMissingError{ "name or playlistId" }; throw RequiredParameterMissingError{ "name or playlistId" };
auto transaction{ context.dbSession.createWriteTransaction() }; auto transaction{ context.getDbSession().createWriteTransaction() };
TrackList::pointer trackList; TrackList::pointer trackList;
if (id) if (id)
{ {
trackList = TrackList::find(context.dbSession, *id); trackList = TrackList::find(context.getDbSession(), *id);
checkTrackListModificationAccess(trackList, context.user->getId()); checkTrackListModificationAccess(trackList, context.getUser()->getId());
if (name) if (name)
trackList.modify()->setName(*name); trackList.modify()->setName(*name);
@@ -142,26 +142,26 @@ namespace lms::api::subsonic
} }
else else
{ {
trackList = context.dbSession.create<TrackList>(*name, TrackListType::PlayList); trackList = context.getDbSession().create<TrackList>(*name, TrackListType::PlayList);
trackList.modify()->setUser(context.user); trackList.modify()->setUser(context.getUser());
trackList.modify()->setVisibility(TrackList::Visibility::Private); trackList.modify()->setVisibility(TrackList::Visibility::Private);
} }
for (const TrackId trackId : trackIds) for (const TrackId trackId : trackIds)
{ {
Track::pointer track{ Track::find(context.dbSession, trackId) }; Track::pointer track{ Track::find(context.getDbSession(), trackId) };
if (!track) if (!track)
continue; continue;
context.dbSession.create<TrackListEntry>(track, trackList); context.getDbSession().create<TrackListEntry>(track, trackList);
} }
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node playlistNode{ createPlaylistNode(context, trackList) }; Response::Node playlistNode{ createPlaylistNode(context, trackList) };
auto entries{ trackList->getEntries() }; auto entries{ trackList->getEntries() };
for (const TrackListEntry::pointer& entry : entries.results) for (const TrackListEntry::pointer& entry : entries.results)
playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.user)); playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.getUser()));
response.addNode("playlist", std::move(playlistNode)); response.addNode("playlist", std::move(playlistNode));
@@ -171,19 +171,19 @@ namespace lms::api::subsonic
Response handleUpdatePlaylistRequest(RequestContext& context) Response handleUpdatePlaylistRequest(RequestContext& context)
{ {
// Mandatory params // Mandatory params
TrackListId id{ getMandatoryParameterAs<TrackListId>(context.parameters, "playlistId") }; TrackListId id{ getMandatoryParameterAs<TrackListId>(context.getParameters(), "playlistId") };
// Optional parameters // Optional parameters
auto name{ getParameterAs<std::string>(context.parameters, "name") }; auto name{ getParameterAs<std::string>(context.getParameters(), "name") };
auto isPublic{ getParameterAs<bool>(context.parameters, "public") }; auto isPublic{ getParameterAs<bool>(context.getParameters(), "public") };
std::vector<TrackId> trackIdsToAdd{ getMultiParametersAs<TrackId>(context.parameters, "songIdToAdd") }; std::vector<TrackId> trackIdsToAdd{ getMultiParametersAs<TrackId>(context.getParameters(), "songIdToAdd") };
std::vector<std::size_t> trackPositionsToRemove{ getMultiParametersAs<std::size_t>(context.parameters, "songIndexToRemove") }; std::vector<std::size_t> trackPositionsToRemove{ getMultiParametersAs<std::size_t>(context.getParameters(), "songIndexToRemove") };
auto transaction{ context.dbSession.createWriteTransaction() }; auto transaction{ context.getDbSession().createWriteTransaction() };
TrackList::pointer trackList{ TrackList::find(context.dbSession, id) }; TrackList::pointer trackList{ TrackList::find(context.getDbSession(), id) };
checkTrackListModificationAccess(trackList, context.user->getId()); checkTrackListModificationAccess(trackList, context.getUser()->getId());
if (name) if (name)
trackList.modify()->setName(*name); trackList.modify()->setName(*name);
@@ -206,27 +206,27 @@ namespace lms::api::subsonic
// Add tracks // Add tracks
for (const TrackId trackIdToAdd : trackIdsToAdd) for (const TrackId trackIdToAdd : trackIdsToAdd)
{ {
Track::pointer track{ Track::find(context.dbSession, trackIdToAdd) }; Track::pointer track{ Track::find(context.getDbSession(), trackIdToAdd) };
if (!track) if (!track)
continue; continue;
context.dbSession.create<TrackListEntry>(track, trackList); context.getDbSession().create<TrackListEntry>(track, trackList);
} }
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleDeletePlaylistRequest(RequestContext& context) Response handleDeletePlaylistRequest(RequestContext& context)
{ {
TrackListId id{ getMandatoryParameterAs<TrackListId>(context.parameters, "id") }; TrackListId id{ getMandatoryParameterAs<TrackListId>(context.getParameters(), "id") };
auto transaction{ context.dbSession.createWriteTransaction() }; auto transaction{ context.getDbSession().createWriteTransaction() };
TrackList::pointer trackList{ TrackList::find(context.dbSession, id) }; TrackList::pointer trackList{ TrackList::find(context.getDbSession(), id) };
checkTrackListModificationAccess(trackList, context.user->getId()); checkTrackListModificationAccess(trackList, context.getUser()->getId());
trackList.remove(); trackList.remove();
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
} // namespace lms::api::subsonic } // namespace lms::api::subsonic
+23 -23
View File
@@ -34,14 +34,14 @@ namespace lms::api::subsonic
{ {
Response handleGetPodcasts(RequestContext& context) Response handleGetPodcasts(RequestContext& context)
{ {
const bool includeEpisodes{ getParameterAs<bool>(context.parameters, "includeEpisodes").value_or(true) }; const bool includeEpisodes{ getParameterAs<bool>(context.getParameters(), "includeEpisodes").value_or(true) };
const std::optional<db::PodcastId> podcastId{ getParameterAs<db::PodcastId>(context.parameters, "id") }; const std::optional<db::PodcastId> podcastId{ getParameterAs<db::PodcastId>(context.getParameters(), "id") };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& podcastsNode{ response.createNode("podcasts") }; Response::Node& podcastsNode{ response.createNode("podcasts") };
podcastsNode.createEmptyArrayChild("channel"); podcastsNode.createEmptyArrayChild("channel");
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
auto processPodcast{ [&](const db::Podcast::pointer& podcast) { auto processPodcast{ [&](const db::Podcast::pointer& podcast) {
podcastsNode.addArrayChild("channel", createPodcastNode(context, podcast, includeEpisodes)); podcastsNode.addArrayChild("channel", createPodcastNode(context, podcast, includeEpisodes));
@@ -49,34 +49,34 @@ namespace lms::api::subsonic
if (podcastId.has_value()) if (podcastId.has_value())
{ {
db::Podcast::pointer podcast{ db::Podcast::find(context.dbSession, podcastId.value()) }; db::Podcast::pointer podcast{ db::Podcast::find(context.getDbSession(), podcastId.value()) };
if (!podcast) if (!podcast)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
processPodcast(podcast); processPodcast(podcast);
} }
else else
db::Podcast::find(context.dbSession, processPodcast); db::Podcast::find(context.getDbSession(), processPodcast);
return response; return response;
} }
Response handleGetNewestPodcasts(RequestContext& context) Response handleGetNewestPodcasts(RequestContext& context)
{ {
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(20) }; std::size_t count{ getParameterAs<std::size_t>(context.getParameters(), "count").value_or(20) };
count = std::min<std::size_t>(count, 100); count = std::min<std::size_t>(count, 100);
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& newestPodcastsNode{ response.createNode("newestPodcasts") }; Response::Node& newestPodcastsNode{ response.createNode("newestPodcasts") };
newestPodcastsNode.createEmptyArrayChild("episode"); newestPodcastsNode.createEmptyArrayChild("episode");
{ {
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
db::PodcastEpisode::FindParameters findParameters; db::PodcastEpisode::FindParameters findParameters;
findParameters.setRange(db::Range{ .offset = 0, .size = count }); findParameters.setRange(db::Range{ .offset = 0, .size = count });
db::PodcastEpisode::find(context.dbSession, findParameters, [&](const db::PodcastEpisode::pointer& episode) { db::PodcastEpisode::find(context.getDbSession(), findParameters, [&](const db::PodcastEpisode::pointer& episode) {
newestPodcastsNode.addArrayChild("episode", createPodcastEpisodeNode(episode)); newestPodcastsNode.addArrayChild("episode", createPodcastEpisodeNode(episode));
}); });
} }
@@ -88,13 +88,13 @@ namespace lms::api::subsonic
{ {
core::Service<podcast::IPodcastService>::get()->refreshPodcasts(); core::Service<podcast::IPodcastService>::get()->refreshPodcasts();
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleCreatePodcastChannel(RequestContext& context) Response handleCreatePodcastChannel(RequestContext& context)
{ {
// Mandatory parameters // Mandatory parameters
const std::string url{ getMandatoryParameterAs<std::string>(context.parameters, "url") }; const std::string url{ getMandatoryParameterAs<std::string>(context.getParameters(), "url") };
if (url.empty() || !(url.starts_with("http://") || url.starts_with("https://"))) if (url.empty() || !(url.starts_with("http://") || url.starts_with("https://")))
throw BadParameterGenericError{ "Invalid url" }; throw BadParameterGenericError{ "Invalid url" };
@@ -102,52 +102,52 @@ namespace lms::api::subsonic
// no effect if podcast already exists // no effect if podcast already exists
core::Service<podcast::IPodcastService>::get()->addPodcast(url); core::Service<podcast::IPodcastService>::get()->addPodcast(url);
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleDeletePodcastChannel(RequestContext& context) Response handleDeletePodcastChannel(RequestContext& context)
{ {
// Mandatory parameters // Mandatory parameters
const db::PodcastId podcastId{ getMandatoryParameterAs<db::PodcastId>(context.parameters, "id") }; const db::PodcastId podcastId{ getMandatoryParameterAs<db::PodcastId>(context.getParameters(), "id") };
if (!core::Service<podcast::IPodcastService>::get()->removePodcast(podcastId)) if (!core::Service<podcast::IPodcastService>::get()->removePodcast(podcastId))
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleDeletePodcastEpisode(RequestContext& context) Response handleDeletePodcastEpisode(RequestContext& context)
{ {
// Mandatory parameters // Mandatory parameters
const db::PodcastEpisodeId episodeId{ getMandatoryParameterAs<db::PodcastEpisodeId>(context.parameters, "id") }; const db::PodcastEpisodeId episodeId{ getMandatoryParameterAs<db::PodcastEpisodeId>(context.getParameters(), "id") };
if (!core::Service<podcast::IPodcastService>::get()->deletePodcastEpisode(episodeId)) if (!core::Service<podcast::IPodcastService>::get()->deletePodcastEpisode(episodeId))
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleDownloadPodcastEpisode(RequestContext& context) Response handleDownloadPodcastEpisode(RequestContext& context)
{ {
// Mandatory parameters // Mandatory parameters
const db::PodcastEpisodeId episodeId{ getMandatoryParameterAs<db::PodcastEpisodeId>(context.parameters, "id") }; const db::PodcastEpisodeId episodeId{ getMandatoryParameterAs<db::PodcastEpisodeId>(context.getParameters(), "id") };
if (!core::Service<podcast::IPodcastService>::get()->downloadPodcastEpisode(episodeId)) if (!core::Service<podcast::IPodcastService>::get()->downloadPodcastEpisode(episodeId))
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleGetPodcastEpisode(RequestContext& context) Response handleGetPodcastEpisode(RequestContext& context)
{ {
// Mandatory parameters // Mandatory parameters
const db::PodcastEpisodeId episodeId{ getMandatoryParameterAs<db::PodcastEpisodeId>(context.parameters, "id") }; const db::PodcastEpisodeId episodeId{ getMandatoryParameterAs<db::PodcastEpisodeId>(context.getParameters(), "id") };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
const db::PodcastEpisode::pointer episode{ db::PodcastEpisode::find(context.dbSession, episodeId) }; const db::PodcastEpisode::pointer episode{ db::PodcastEpisode::find(context.getDbSession(), episodeId) };
if (!episode) if (!episode)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
+29 -29
View File
@@ -118,14 +118,14 @@ namespace lms::api::subsonic
{ {
// For now, no need to optimize all this // For now, no need to optimize all this
// Find all the directories that match the name and that do not contain any track (considered by the legacy API as artists) // Find all the directories that match the name and that do not contain any track (considered by the legacy API as artists)
const std::size_t artistCount{ getParameterAs<std::size_t>(context.parameters, "artistCount").value_or(20) }; const std::size_t artistCount{ getParameterAs<std::size_t>(context.getParameters(), "artistCount").value_or(20) };
if (artistCount == 0) if (artistCount == 0)
return; return;
if (artistCount > defaultMaxCountSize) if (artistCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "artistCount", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "artistCount", defaultMaxCountSize };
const std::size_t artistOffset{ getParameterAs<std::size_t>(context.parameters, "artistOffset").value_or(0) }; const std::size_t artistOffset{ getParameterAs<std::size_t>(context.getParameters(), "artistOffset").value_or(0) };
Directory::FindParameters params; Directory::FindParameters params;
params.setKeywords(keywords); params.setKeywords(keywords);
@@ -133,7 +133,7 @@ namespace lms::api::subsonic
params.setWithNoTrack(true); params.setWithNoTrack(true);
params.setMediaLibrary(mediaLibrary); params.setMediaLibrary(mediaLibrary);
Directory::find(context.dbSession, params, [&](const Directory::pointer& directory) { Directory::find(context.getDbSession(), params, [&](const Directory::pointer& directory) {
Response::Node childNode; Response::Node childNode;
childNode.setAttribute("id", idToString(directory->getId())); childNode.setAttribute("id", idToString(directory->getId()));
childNode.setAttribute("name", directory->getName()); childNode.setAttribute("name", directory->getName());
@@ -147,14 +147,14 @@ namespace lms::api::subsonic
{ {
static ScanTracker<ArtistId> currentScansInProgress; static ScanTracker<ArtistId> currentScansInProgress;
const std::size_t artistCount{ getParameterAs<std::size_t>(context.parameters, "artistCount").value_or(20) }; const std::size_t artistCount{ getParameterAs<std::size_t>(context.getParameters(), "artistCount").value_or(20) };
if (artistCount == 0) if (artistCount == 0)
return; return;
if (artistCount > defaultMaxCountSize) if (artistCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "artistCount", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "artistCount", defaultMaxCountSize };
const std::size_t artistOffset{ getParameterAs<std::size_t>(context.parameters, "artistOffset").value_or(0) }; const std::size_t artistOffset{ getParameterAs<std::size_t>(context.getParameters(), "artistOffset").value_or(0) };
ArtistId lastRetrievedId; ArtistId lastRetrievedId;
auto findArtists{ [&] { auto findArtists{ [&] {
@@ -164,7 +164,7 @@ namespace lms::api::subsonic
params.setRange(Range{ artistOffset, artistCount }); params.setRange(Range{ artistOffset, artistCount });
params.setSortMethod(ArtistSortMethod::Id); // must be consistent with both methods params.setSortMethod(ArtistSortMethod::Id); // must be consistent with both methods
Artist::find(context.dbSession, params, [&](const Artist::pointer& artist) { Artist::find(context.getDbSession(), params, [&](const Artist::pointer& artist) {
searchResultNode.addArrayChild("artist", createArtistNode(context, artist)); searchResultNode.addArrayChild("artist", createArtistNode(context, artist));
lastRetrievedId = artist->getId(); lastRetrievedId = artist->getId();
}); });
@@ -177,9 +177,9 @@ namespace lms::api::subsonic
else else
{ {
ScanTracker<ArtistId>::ScanInfo scanInfo{ ScanTracker<ArtistId>::ScanInfo scanInfo{
.clientAddress = context.clientIpAddr, .clientAddress = context.getClientIpAddr(),
.clientName = context.clientInfo.name, .clientName = std::string{ context.getClientName() },
.user = context.user->getId(), .user = context.getUser()->getId(),
.library = mediaLibrary, .library = mediaLibrary,
.offset = artistOffset .offset = artistOffset
}; };
@@ -187,7 +187,7 @@ namespace lms::api::subsonic
if (ArtistId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid()) if (ArtistId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
{ {
Artist::find( Artist::find(
context.dbSession, cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist) { context.getDbSession(), cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist) {
searchResultNode.addArrayChild("artist", createArtistNode(context, artist)); searchResultNode.addArrayChild("artist", createArtistNode(context, artist));
}, },
mediaLibrary); mediaLibrary);
@@ -210,14 +210,14 @@ namespace lms::api::subsonic
{ {
static ScanTracker<ReleaseId> currentScansInProgress; static ScanTracker<ReleaseId> currentScansInProgress;
const std::size_t albumCount{ getParameterAs<std::size_t>(context.parameters, "albumCount").value_or(20) }; const std::size_t albumCount{ getParameterAs<std::size_t>(context.getParameters(), "albumCount").value_or(20) };
if (albumCount == 0) if (albumCount == 0)
return; return;
if (albumCount > defaultMaxCountSize) if (albumCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "albumCount", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "albumCount", defaultMaxCountSize };
const std::size_t albumOffset{ getParameterAs<std::size_t>(context.parameters, "albumOffset").value_or(0) }; const std::size_t albumOffset{ getParameterAs<std::size_t>(context.getParameters(), "albumOffset").value_or(0) };
ReleaseId lastRetrievedId; ReleaseId lastRetrievedId;
@@ -228,7 +228,7 @@ namespace lms::api::subsonic
params.filters.setMediaLibrary(mediaLibrary); params.filters.setMediaLibrary(mediaLibrary);
params.setSortMethod(ReleaseSortMethod::Id); // must be consistent with both methods params.setSortMethod(ReleaseSortMethod::Id); // must be consistent with both methods
Release::find(context.dbSession, params, [&](const Release::pointer& release) { Release::find(context.getDbSession(), params, [&](const Release::pointer& release) {
searchResultNode.addArrayChild("album", createAlbumNode(context, release, id3)); searchResultNode.addArrayChild("album", createAlbumNode(context, release, id3));
lastRetrievedId = release->getId(); lastRetrievedId = release->getId();
}); });
@@ -241,9 +241,9 @@ namespace lms::api::subsonic
else else
{ {
ScanTracker<ReleaseId>::ScanInfo scanInfo{ ScanTracker<ReleaseId>::ScanInfo scanInfo{
.clientAddress = context.clientIpAddr, .clientAddress = context.getClientIpAddr(),
.clientName = context.clientInfo.name, .clientName = std::string{ context.getClientName() },
.user = context.user->getId(), .user = context.getUser()->getId(),
.library = mediaLibrary, .library = mediaLibrary,
.offset = albumOffset .offset = albumOffset
}; };
@@ -251,7 +251,7 @@ namespace lms::api::subsonic
if (ReleaseId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid()) if (ReleaseId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
{ {
Release::find( Release::find(
context.dbSession, cachedLastRetrievedId, albumCount, [&](const Release::pointer& release) { context.getDbSession(), cachedLastRetrievedId, albumCount, [&](const Release::pointer& release) {
searchResultNode.addArrayChild("album", createAlbumNode(context, release, id3)); searchResultNode.addArrayChild("album", createAlbumNode(context, release, id3));
}, },
mediaLibrary); mediaLibrary);
@@ -274,14 +274,14 @@ namespace lms::api::subsonic
{ {
static ScanTracker<TrackId> currentScansInProgress; static ScanTracker<TrackId> currentScansInProgress;
const std::size_t songCount{ getParameterAs<std::size_t>(context.parameters, "songCount").value_or(20) }; const std::size_t songCount{ getParameterAs<std::size_t>(context.getParameters(), "songCount").value_or(20) };
if (songCount == 0) if (songCount == 0)
return; return;
if (songCount > defaultMaxCountSize) if (songCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "songCount", defaultMaxCountSize }; throw ParameterValueTooHighGenericError{ "songCount", defaultMaxCountSize };
const std::size_t songOffset{ getParameterAs<std::size_t>(context.parameters, "songOffset").value_or(0) }; const std::size_t songOffset{ getParameterAs<std::size_t>(context.getParameters(), "songOffset").value_or(0) };
TrackId lastRetrievedId; TrackId lastRetrievedId;
@@ -292,7 +292,7 @@ namespace lms::api::subsonic
params.filters.setMediaLibrary(mediaLibrary); params.filters.setMediaLibrary(mediaLibrary);
params.setSortMethod(TrackSortMethod::Id); // must be consistent with both methods params.setSortMethod(TrackSortMethod::Id); // must be consistent with both methods
Track::find(context.dbSession, params, [&](const Track::pointer& track) { Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
searchResultNode.addArrayChild("song", createSongNode(context, track, id3)); searchResultNode.addArrayChild("song", createSongNode(context, track, id3));
lastRetrievedId = track->getId(); lastRetrievedId = track->getId();
}); });
@@ -305,9 +305,9 @@ namespace lms::api::subsonic
else else
{ {
ScanTracker<TrackId>::ScanInfo scanInfo{ ScanTracker<TrackId>::ScanInfo scanInfo{
.clientAddress = context.clientIpAddr, .clientAddress = context.getClientIpAddr(),
.clientName = context.clientInfo.name, .clientName = std::string{ context.getClientName() },
.user = context.user->getId(), .user = context.getUser()->getId(),
.library = mediaLibrary, .library = mediaLibrary,
.offset = songOffset .offset = songOffset
}; };
@@ -315,7 +315,7 @@ namespace lms::api::subsonic
if (TrackId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid()) if (TrackId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
{ {
Track::find( Track::find(
context.dbSession, cachedLastRetrievedId, songCount, [&](const Track::pointer& track) { context.getDbSession(), cachedLastRetrievedId, songCount, [&](const Track::pointer& track) {
searchResultNode.addArrayChild("song", createSongNode(context, track, id3)); searchResultNode.addArrayChild("song", createSongNode(context, track, id3));
}, },
mediaLibrary); mediaLibrary);
@@ -337,24 +337,24 @@ namespace lms::api::subsonic
Response handleSearchRequestCommon(RequestContext& context, bool id3) Response handleSearchRequestCommon(RequestContext& context, bool id3)
{ {
// Mandatory params // Mandatory params
const std::string queryString{ getMandatoryParameterAs<std::string>(context.parameters, "query") }; const std::string queryString{ getMandatoryParameterAs<std::string>(context.getParameters(), "query") };
std::string_view query{ queryString }; std::string_view query{ queryString };
// Optional params // Optional params
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) }; const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
// Symfonium adds extra "" // Symfonium adds extra ""
if (context.clientInfo.name == "Symfonium") if (context.getClientName() == "Symfonium")
query = core::stringUtils::stringTrim(query, "\""); query = core::stringUtils::stringTrim(query, "\"");
std::vector<std::string_view> keywords; std::vector<std::string_view> keywords;
if (!query.empty()) if (!query.empty())
keywords = core::stringUtils::splitString(query, ' '); keywords = core::stringUtils::splitString(query, ' ');
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& searchResultNode{ response.createNode(id3 ? "searchResult3" : "searchResult2") }; Response::Node& searchResultNode{ response.createNode(id3 ? "searchResult3" : "searchResult2") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
if (id3) if (id3)
findRequestedArtists(context, keywords, mediaLibrary, searchResultNode); findRequestedArtists(context, keywords, mediaLibrary, searchResultNode);
+3 -3
View File
@@ -4,12 +4,12 @@ namespace lms::api::subsonic
{ {
Response handlePingRequest(RequestContext& context) Response handlePingRequest(RequestContext& context)
{ {
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.getServerProtocolVersion());
} }
Response handleGetLicenseRequest(RequestContext& context) Response handleGetLicenseRequest(RequestContext& context)
{ {
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& licenseNode{ response.createNode("license") }; Response::Node& licenseNode{ response.createNode("license") };
licenseNode.setAttribute("licenseExpires", "2035-09-03T14:46:43"); licenseNode.setAttribute("licenseExpires", "2035-09-03T14:46:43");
@@ -21,7 +21,7 @@ namespace lms::api::subsonic
Response handleGetOpenSubsonicExtensions(RequestContext& context) Response handleGetOpenSubsonicExtensions(RequestContext& context)
{ {
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
{ {
Response::Node& transcodeOffsetNode{ response.createArrayNode("openSubsonicExtensions") }; Response::Node& transcodeOffsetNode{ response.createArrayNode("openSubsonicExtensions") };
@@ -16,24 +16,24 @@ namespace lms::api::subsonic
{ {
void checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username) void checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username)
{ {
if (context.user->getLoginName() != username && !context.user->isAdmin()) if (context.getUser()->getLoginName() != username && !context.getUser()->isAdmin())
throw UserNotAuthorizedError{}; throw UserNotAuthorizedError{};
} }
} // namespace } // namespace
Response handleGetUserRequest(RequestContext& context) Response handleGetUserRequest(RequestContext& context)
{ {
std::string username{ getMandatoryParameterAs<std::string>(context.parameters, "username") }; std::string username{ getMandatoryParameterAs<std::string>(context.getParameters(), "username") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
checkUserIsMySelfOrAdmin(context, username); checkUserIsMySelfOrAdmin(context, username);
const User::pointer user{ User::find(context.dbSession, username) }; const User::pointer user{ User::find(context.getDbSession(), username) };
if (!user) if (!user)
throw RequestedDataNotFoundError{}; throw RequestedDataNotFoundError{};
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
response.addNode("user", createUserNode(context, user)); response.addNode("user", createUserNode(context, user));
return response; return response;
@@ -41,11 +41,11 @@ namespace lms::api::subsonic
Response handleGetUsersRequest(RequestContext& context) Response handleGetUsersRequest(RequestContext& context)
{ {
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
Response::Node& usersNode{ response.createNode("users") }; Response::Node& usersNode{ response.createNode("users") };
auto transaction{ context.dbSession.createReadTransaction() }; auto transaction{ context.getDbSession().createReadTransaction() };
User::find(context.dbSession, User::FindParameters{}, [&](const User::pointer& user) { User::find(context.getDbSession(), User::FindParameters{}, [&](const User::pointer& user) {
usersNode.addArrayChild("user", createUserNode(context, user)); usersNode.addArrayChild("user", createUserNode(context, user));
}); });
+9 -9
View File
@@ -69,7 +69,7 @@ namespace lms::api::subsonic
Directory::FindParameters params; Directory::FindParameters params;
params.setRelease(release->getId()); params.setRelease(release->getId());
params.setRange(Range{ 0, 1 }); // only support 1 directory <-> 1 release params.setRange(Range{ 0, 1 }); // only support 1 directory <-> 1 release
Directory::find(context.dbSession, params, [&](const Directory::pointer& foundDirectory) { Directory::find(context.getDbSession(), params, [&](const Directory::pointer& foundDirectory) {
directoryToReport = foundDirectory; directoryToReport = foundDirectory;
}); });
} }
@@ -116,10 +116,10 @@ namespace lms::api::subsonic
} }
} }
albumNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.user->getId(), release->getId())); albumNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.getUser()->getId(), release->getId()));
// Report the first GENRE for this track // Report the first GENRE for this track
const ClusterType::pointer genreClusterType{ ClusterType::find(context.dbSession, "GENRE") }; const ClusterType::pointer genreClusterType{ ClusterType::find(context.getDbSession(), "GENRE") };
if (genreClusterType) if (genreClusterType)
{ {
const auto clusters{ release->getClusters(genreClusterType->getId(), 1) }; const auto clusters{ release->getClusters(genreClusterType->getId(), 1) };
@@ -127,14 +127,14 @@ namespace lms::api::subsonic
albumNode.setAttribute("genre", clusters.front()->getName()); albumNode.setAttribute("genre", clusters.front()->getName());
} }
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.user->getId(), release->getId()) }; dateTime.isValid()) if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.getUser()->getId(), release->getId()) }; dateTime.isValid())
albumNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime)); albumNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
// Always report user rating, even if legacy API only specified it for directories // Always report user rating, even if legacy API only specified it for directories
if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.user->getId(), release->getId()) }) if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.getUser()->getId(), release->getId()) })
albumNode.setAttribute("userRating", *rating); albumNode.setAttribute("userRating", *rating);
if (!context.enableOpenSubsonic) if (!context.isOpenSubsonicEnabled())
return albumNode; return albumNode;
// OpenSubsonic specific fields (must always be set) // OpenSubsonic specific fields (must always be set)
@@ -143,7 +143,7 @@ namespace lms::api::subsonic
albumNode.setAttribute("mediaType", "album"); albumNode.setAttribute("mediaType", "album");
{ {
const Wt::WDateTime dateTime{ core::Service<scrobbling::IScrobblingService>::get()->getLastListenDateTime(context.user->getId(), release->getId()) }; const Wt::WDateTime dateTime{ core::Service<scrobbling::IScrobblingService>::get()->getLastListenDateTime(context.getUser()->getId(), release->getId()) };
albumNode.setAttribute("played", dateTime.isValid() ? core::stringUtils::toISO8601String(dateTime) : std::string{ "" }); albumNode.setAttribute("played", dateTime.isValid() ? core::stringUtils::toISO8601String(dateTime) : std::string{ "" });
} }
@@ -159,7 +159,7 @@ namespace lms::api::subsonic
params.setRelease(release->getId()); params.setRelease(release->getId());
params.setClusterTypeName(clusterTypeName); params.setClusterTypeName(clusterTypeName);
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) { Cluster::find(context.getDbSession(), params, [&](const Cluster::pointer& cluster) {
albumNode.addArrayValue(field, cluster->getName()); albumNode.addArrayValue(field, cluster->getName());
}); });
} }; } };
@@ -174,7 +174,7 @@ namespace lms::api::subsonic
params.setRelease(release->getId()); params.setRelease(release->getId());
params.setClusterType(genreClusterType->getId()); params.setClusterType(genreClusterType->getId());
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) { Cluster::find(context.getDbSession(), params, [&](const Cluster::pointer& cluster) {
albumNode.addArrayChild("genres", createItemGenreNode(cluster->getName())); albumNode.addArrayChild("genres", createItemGenreNode(cluster->getName()));
}); });
} }
@@ -31,7 +31,7 @@ namespace lms::api::subsonic
if (const auto releaseMBID{ release->getMBID() }) if (const auto releaseMBID{ release->getMBID() })
{ {
switch (context.responseFormat) switch (context.getResponseFormat())
{ {
case ResponseFormat::json: case ResponseFormat::json:
albumInfo.setAttribute("musicBrainzId", releaseMBID->getAsString()); albumInfo.setAttribute("musicBrainzId", releaseMBID->getAsString());
+5 -5
View File
@@ -101,17 +101,17 @@ namespace lms::api::subsonic
artistNode.setAttribute("coverArt", idToString(coverArtId)); artistNode.setAttribute("coverArt", idToString(coverArtId));
} }
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) }; const std::size_t count{ Release::getCount(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId())) };
artistNode.setAttribute("albumCount", count); artistNode.setAttribute("albumCount", count);
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.user->getId(), artist->getId()) }; dateTime.isValid()) if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.getUser()->getId(), artist->getId()) }; dateTime.isValid())
artistNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime)); artistNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.user->getId(), artist->getId()) }) if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.getUser()->getId(), artist->getId()) })
artistNode.setAttribute("userRating", *rating); artistNode.setAttribute("userRating", *rating);
// OpenSubsonic specific fields (must always be set) // OpenSubsonic specific fields (must always be set)
if (context.enableOpenSubsonic) if (context.isOpenSubsonicEnabled())
{ {
artistNode.setAttribute("mediaType", "artist"); artistNode.setAttribute("mediaType", "artist");
@@ -125,7 +125,7 @@ namespace lms::api::subsonic
// roles // roles
Response::Node roles; Response::Node roles;
artistNode.createEmptyArrayValue("roles"); artistNode.createEmptyArrayValue("roles");
for (const TrackArtistLinkType linkType : TrackArtistLink::findUsedTypes(context.dbSession, artist->getId())) for (const TrackArtistLinkType linkType : TrackArtistLink::findUsedTypes(context.getDbSession(), artist->getId()))
artistNode.addArrayValue("roles", utils::toString(linkType)); artistNode.addArrayValue("roles", utils::toString(linkType));
} }
+1 -1
View File
@@ -29,7 +29,7 @@ namespace lms::api::subsonic
{ {
Response::Node clusterNode; Response::Node clusterNode;
switch (context.responseFormat) switch (context.getResponseFormat())
{ {
case ResponseFormat::json: case ResponseFormat::json:
clusterNode.setAttribute("value", cluster->getName()); clusterNode.setAttribute("value", cluster->getName());
+2 -2
View File
@@ -55,7 +55,7 @@ namespace lms::api::subsonic
addLine(std::move(line)); addLine(std::move(line));
} }
switch (context.responseFormat) switch (context.getResponseFormat())
{ {
case ResponseFormat::json: case ResponseFormat::json:
lyricsNode.setAttribute("value", lyricsText); lyricsNode.setAttribute("value", lyricsText);
@@ -89,7 +89,7 @@ namespace lms::api::subsonic
if (timestamp) if (timestamp)
lineNode.setAttribute("start", std::chrono::duration_cast<std::chrono::milliseconds>(*timestamp).count()); lineNode.setAttribute("start", std::chrono::duration_cast<std::chrono::milliseconds>(*timestamp).count());
switch (context.responseFormat) switch (context.getResponseFormat())
{ {
case ResponseFormat::json: case ResponseFormat::json:
lineNode.setAttribute("value", std::move(line)); lineNode.setAttribute("value", std::move(line));
@@ -48,7 +48,7 @@ namespace lms::api::subsonic
if (const db::ArtworkId artworkId{ core::Service<artwork::IArtworkService>::get()->findTrackListImage(tracklist->getId()) }; artworkId.isValid()) if (const db::ArtworkId artworkId{ core::Service<artwork::IArtworkService>::get()->findTrackListImage(tracklist->getId()) }; artworkId.isValid())
{ {
if (const auto artwork{ db::Artwork::find(context.dbSession, artworkId) }) if (const auto artwork{ db::Artwork::find(context.getDbSession(), artworkId) })
{ {
CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() }; CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() };
playlistNode.setAttribute("coverArt", idToString(coverArtId)); playlistNode.setAttribute("coverArt", idToString(coverArtId));
+1 -1
View File
@@ -125,7 +125,7 @@ namespace lms::api::subsonic
params.setPodcast(podcast->getId()); params.setPodcast(podcast->getId());
params.setSortMode(db::PodcastEpisodeSortMode::PubDateDesc); params.setSortMode(db::PodcastEpisodeSortMode::PubDateDesc);
db::PodcastEpisode::find(context.dbSession, params, [&](const db::PodcastEpisode::pointer& episode) { db::PodcastEpisode::find(context.getDbSession(), params, [&](const db::PodcastEpisode::pointer& episode) {
podcastNode.addArrayChild("episode", createPodcastEpisodeNode(episode)); podcastNode.addArrayChild("episode", createPodcastEpisodeNode(episode));
}); });
} }
+10 -10
View File
@@ -100,7 +100,7 @@ namespace lms::api::subsonic
trackResponse.setAttribute("year", *originalYear); trackResponse.setAttribute("year", *originalYear);
else if (const auto year{ track->getYear() }) else if (const auto year{ track->getYear() })
trackResponse.setAttribute("year", *year); trackResponse.setAttribute("year", *year);
trackResponse.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.user->getId(), track->getId())); trackResponse.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.getUser()->getId(), track->getId()));
// maybe not available if user just removed the library without rescanning // maybe not available if user just removed the library without rescanning
if (const db::MediaLibrary::pointer library{ track->getMediaLibrary() }) if (const db::MediaLibrary::pointer library{ track->getMediaLibrary() })
@@ -119,9 +119,9 @@ namespace lms::api::subsonic
trackResponse.setAttribute("suffix", extension.string().substr(1) /* skip leading .*/); trackResponse.setAttribute("suffix", extension.string().substr(1) /* skip leading .*/);
} }
if (context.user->getSubsonicEnableTranscodingByDefault()) if (context.getUser()->getSubsonicEnableTranscodingByDefault())
{ {
const std::string fileSuffix{ formatToSuffix(context.user->getSubsonicDefaultTranscodingOutputFormat()) }; const std::string fileSuffix{ formatToSuffix(context.getUser()->getSubsonicDefaultTranscodingOutputFormat()) };
trackResponse.setAttribute("transcodedSuffix", fileSuffix); trackResponse.setAttribute("transcodedSuffix", fileSuffix);
trackResponse.setAttribute("transcodedContentType", core::getMimeType(std::filesystem::path{ "." + fileSuffix })); trackResponse.setAttribute("transcodedContentType", core::getMimeType(std::filesystem::path{ "." + fileSuffix }));
} }
@@ -160,10 +160,10 @@ namespace lms::api::subsonic
trackResponse.setAttribute("type", "music"); trackResponse.setAttribute("type", "music");
trackResponse.setAttribute("created", core::stringUtils::toISO8601String(track->getAddedTime())); trackResponse.setAttribute("created", core::stringUtils::toISO8601String(track->getAddedTime()));
trackResponse.setAttribute("contentType", core::getMimeType(track->getAbsoluteFilePath().extension())); trackResponse.setAttribute("contentType", core::getMimeType(track->getAbsoluteFilePath().extension()));
if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.user->getId(), track->getId()) }) if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.getUser()->getId(), track->getId()) })
trackResponse.setAttribute("userRating", *rating); trackResponse.setAttribute("userRating", *rating);
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.user->getId(), track->getId()) }; dateTime.isValid()) if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.getUser()->getId(), track->getId()) }; dateTime.isValid())
trackResponse.setAttribute("starred", core::stringUtils::toISO8601String(dateTime)); trackResponse.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
// Report the first GENRE for this track // Report the first GENRE for this track
@@ -173,13 +173,13 @@ namespace lms::api::subsonic
params.setTrack(track->getId()); params.setTrack(track->getId());
params.setClusterTypeName("GENRE"); params.setClusterTypeName("GENRE");
genres = Cluster::find(context.dbSession, params).results; genres = Cluster::find(context.getDbSession(), params).results;
if (!genres.empty()) if (!genres.empty())
trackResponse.setAttribute("genre", genres.front()->getName()); trackResponse.setAttribute("genre", genres.front()->getName());
} }
// OpenSubsonic specific fields (must always be set) // OpenSubsonic specific fields (must always be set)
if (!context.enableOpenSubsonic) if (!context.isOpenSubsonicEnabled())
return trackResponse; return trackResponse;
trackResponse.setAttribute("comment", track->getComment()); trackResponse.setAttribute("comment", track->getComment());
@@ -190,7 +190,7 @@ namespace lms::api::subsonic
trackResponse.setAttribute("mediaType", "song"); trackResponse.setAttribute("mediaType", "song");
{ {
const Wt::WDateTime dateTime{ core::Service<scrobbling::IScrobblingService>::get()->getLastListenDateTime(context.user->getId(), track->getId()) }; const Wt::WDateTime dateTime{ core::Service<scrobbling::IScrobblingService>::get()->getLastListenDateTime(context.getUser()->getId(), track->getId()) };
trackResponse.setAttribute("played", dateTime.isValid() ? core::stringUtils::toISO8601String(dateTime) : ""); trackResponse.setAttribute("played", dateTime.isValid() ? core::stringUtils::toISO8601String(dateTime) : "");
} }
@@ -204,7 +204,7 @@ namespace lms::api::subsonic
trackResponse.createEmptyArrayChild("artists"); trackResponse.createEmptyArrayChild("artists");
trackResponse.createEmptyArrayChild("contributors"); trackResponse.createEmptyArrayChild("contributors");
TrackArtistLink::find(context.dbSession, track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) { TrackArtistLink::find(context.getDbSession(), track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) {
switch (link->getType()) switch (link->getType())
{ {
case TrackArtistLinkType::Artist: case TrackArtistLinkType::Artist:
@@ -230,7 +230,7 @@ namespace lms::api::subsonic
params.setTrack(track->getId()); params.setTrack(track->getId());
params.setClusterTypeName(clusterTypeName); params.setClusterTypeName(clusterTypeName);
for (const auto& cluster : Cluster::find(context.dbSession, params).results) for (const auto& cluster : Cluster::find(context.getDbSession(), params).results)
trackResponse.addArrayValue(field, cluster->getName()); trackResponse.addArrayValue(field, cluster->getName());
} }; } };
+1 -1
View File
@@ -46,7 +46,7 @@ namespace lms::api::subsonic
userNode.setAttribute("shareRole", false); // not supported userNode.setAttribute("shareRole", false); // not supported
// users can access all libraries // users can access all libraries
db::MediaLibrary::find(context.dbSession, [&](const db::MediaLibrary::pointer& library) { db::MediaLibrary::find(context.getDbSession(), [&](const db::MediaLibrary::pointer& library) {
userNode.addArrayValue("folder", library->getId().getValue()); userNode.addArrayValue("folder", library->getId().getValue());
}); });