Refactored request context handling
This commit is contained in:
@@ -28,11 +28,13 @@ add_library(lmssubsonic STATIC
|
||||
impl/responses/Song.cpp
|
||||
impl/responses/User.cpp
|
||||
impl/CoverArtId.cpp
|
||||
impl/RequestContext.cpp
|
||||
impl/ResponseFormat.cpp
|
||||
impl/ProtocolVersion.cpp
|
||||
impl/ParameterParsing.cpp
|
||||
impl/SubsonicId.cpp
|
||||
impl/SubsonicResource.cpp
|
||||
impl/SubsonicResourceConfig.cpp
|
||||
impl/SubsonicResponse.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -19,15 +19,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <Wt/Http/Request.h>
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "ClientInfo.hpp"
|
||||
#include "ProtocolVersion.hpp"
|
||||
#include "ResponseFormat.hpp"
|
||||
#include "SubsonicResourceConfig.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -37,15 +39,41 @@ namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
struct RequestContext
|
||||
class RequestContext
|
||||
{
|
||||
const Wt::Http::ParameterMap& parameters;
|
||||
db::Session& dbSession;
|
||||
db::ObjectPtr<db::User> user;
|
||||
std::string clientIpAddr;
|
||||
ClientInfo clientInfo;
|
||||
ProtocolVersion serverProtocolVersion;
|
||||
ResponseFormat responseFormat;
|
||||
bool enableOpenSubsonic{ true };
|
||||
public:
|
||||
RequestContext(const Wt::Http::Request& request, db::Session& dbSession, db::ObjectPtr<db::User> user, const SubsonicResourceConfig& config);
|
||||
~RequestContext();
|
||||
RequestContext(const RequestContext&) = delete;
|
||||
RequestContext& operator=(const RequestContext&) = delete;
|
||||
|
||||
using ParameterMap = Wt::Http::ParameterMap;
|
||||
|
||||
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
|
||||
|
||||
@@ -60,32 +60,6 @@ namespace lms::api::subsonic
|
||||
|
||||
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)
|
||||
{
|
||||
constexpr std::string_view redactedStr{ "*REDACTED*" };
|
||||
@@ -292,24 +266,10 @@ namespace lms::api::subsonic
|
||||
|
||||
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
|
||||
|
||||
SubsonicResource::SubsonicResource(db::IDb& db)
|
||||
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
|
||||
, _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() }
|
||||
, _supportUserPasswordAuthentication{ core::Service<core::IConfig>::get()->getBool("api-subsonic-support-user-password-auth", true) }
|
||||
: _config{ readSubsonicResourceConfig(*core::Service<core::IConfig>::get()) }
|
||||
, _db{ db }
|
||||
{
|
||||
}
|
||||
@@ -334,21 +294,20 @@ namespace lms::api::subsonic
|
||||
|
||||
try
|
||||
{
|
||||
// We need to parse client a soon as possible to make sure to answer with the right protocol version
|
||||
protocolVersion = getServerProtocolVersion(getMandatoryParameterAs<std::string>(request.getParameterMap(), "c"));
|
||||
RequestContext requestContext{ buildRequestContext(request) };
|
||||
|
||||
auto itEntryPoint{ requestEntryPoints.find(requestPath) };
|
||||
if (itEntryPoint != requestEntryPoints.end())
|
||||
if (auto itEntryPoint{ requestEntryPoints.find(requestPath) }; itEntryPoint != requestEntryPoints.end())
|
||||
{
|
||||
LMS_SCOPED_TRACE_OVERVIEW("Subsonic", itEntryPoint->first);
|
||||
|
||||
db::User::pointer user;
|
||||
if (itEntryPoint->second.authMode == AuthenticationMode::Authenticated)
|
||||
{
|
||||
requestContext.user = getUserFromUserId(_db.getTLSSession(), authenticateUser(request));
|
||||
checkUserTypeIsAllowed(requestContext.user, itEntryPoint->second.allowedUserTypes);
|
||||
user = getUserFromUserId(_db.getTLSSession(), authenticateUser(request));
|
||||
checkUserTypeIsAllowed(user, itEntryPoint->second.allowedUserTypes);
|
||||
}
|
||||
|
||||
RequestContext requestContext{ request, _db.getTLSSession(), user, _config };
|
||||
protocolVersion = requestContext.getServerProtocolVersion();
|
||||
|
||||
const Response resp{ [&] {
|
||||
LMS_SCOPED_TRACE_DETAILED("Subsonic", "HandleRequest");
|
||||
return itEntryPoint->second.func(requestContext);
|
||||
@@ -372,8 +331,12 @@ namespace lms::api::subsonic
|
||||
|
||||
// Media retrieval endpoints are always authenticated
|
||||
// Optim: no need to reauth user for each continuation
|
||||
db::User::pointer user;
|
||||
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);
|
||||
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)
|
||||
{
|
||||
const auto& parameters{ request.getParameterMap() };
|
||||
@@ -449,7 +369,7 @@ namespace lms::api::subsonic
|
||||
|
||||
const auto user{ getParameterAs<std::string>(parameters, "u") };
|
||||
const auto password{ getParameterAs<std::string>(parameters, "p") };
|
||||
if (!_supportUserPasswordAuthentication && (password || user))
|
||||
if (!_config.supportUserPasswordAuthentication && (password || user))
|
||||
throw ProvidedAuthenticationMechanismNotSupportedError{};
|
||||
|
||||
const auto apiKey{ getParameterAs<std::string>(parameters, "apiKey") };
|
||||
|
||||
@@ -18,16 +18,13 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <Wt/Http/Request.h>
|
||||
#include <Wt/Http/Response.h>
|
||||
#include <Wt/WResource.h>
|
||||
|
||||
#include "database/objects/UserId.hpp"
|
||||
|
||||
#include "RequestContext.hpp"
|
||||
#include "SubsonicResourceConfig.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -43,16 +40,10 @@ namespace lms::api::subsonic
|
||||
|
||||
private:
|
||||
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
|
||||
ProtocolVersion getServerProtocolVersion(const std::string& clientName) const;
|
||||
|
||||
static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server);
|
||||
RequestContext buildRequestContext(const Wt::Http::Request& request);
|
||||
db::UserId authenticateUser(const Wt::Http::Request& request);
|
||||
|
||||
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
|
||||
const std::unordered_set<std::string> _openSubsonicDisabledClients;
|
||||
const bool _supportUserPasswordAuthentication;
|
||||
|
||||
const SubsonicResourceConfig _config;
|
||||
db::IDb& _db;
|
||||
};
|
||||
} // 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
|
||||
+15
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Emeric Poupon
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
@@ -20,14 +20,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ProtocolVersion.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
class IConfig;
|
||||
}
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
struct ClientInfo
|
||||
struct SubsonicResourceConfig
|
||||
{
|
||||
std::string name;
|
||||
ProtocolVersion version;
|
||||
std::unordered_map<std::string, ProtocolVersion> serverProtocolVersionsByClient;
|
||||
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)
|
||||
{
|
||||
// Mandatory params
|
||||
const std::string type{ getMandatoryParameterAs<std::string>(context.parameters, "type") };
|
||||
const std::string type{ getMandatoryParameterAs<std::string>(context.getParameters(), "type") };
|
||||
|
||||
// Optional params
|
||||
const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
|
||||
const std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(10) };
|
||||
const std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) };
|
||||
const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
|
||||
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.getParameters(), "offset").value_or(0) };
|
||||
if (size > defaultMaxCountSize)
|
||||
throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace lms::api::subsonic
|
||||
scrobbling::IScrobblingService& scrobblingService{ *core::Service<scrobbling::IScrobblingService>::get() };
|
||||
feedback::IFeedbackService& feedbackService{ *core::Service<feedback::IFeedbackService>::get() };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
if (type == "alphabeticalByName")
|
||||
{
|
||||
@@ -71,7 +71,7 @@ namespace lms::api::subsonic
|
||||
params.setRange(range);
|
||||
params.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
releases = Release::findIds(context.dbSession, params);
|
||||
releases = Release::findIds(context.getDbSession(), params);
|
||||
}
|
||||
else if (type == "alphabeticalByArtist")
|
||||
{
|
||||
@@ -80,14 +80,14 @@ namespace lms::api::subsonic
|
||||
params.setRange(range);
|
||||
params.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
releases = Release::findIds(context.dbSession, params);
|
||||
releases = Release::findIds(context.getDbSession(), params);
|
||||
}
|
||||
else if (type == "byGenre")
|
||||
{
|
||||
// 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) })
|
||||
{
|
||||
@@ -97,14 +97,14 @@ namespace lms::api::subsonic
|
||||
params.setSortMethod(ReleaseSortMethod::Name);
|
||||
params.setRange(range);
|
||||
|
||||
releases = Release::findIds(context.dbSession, params);
|
||||
releases = Release::findIds(context.getDbSession(), params);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == "byYear")
|
||||
{
|
||||
const int fromYear{ getMandatoryParameterAs<int>(context.parameters, "fromYear") };
|
||||
const int toYear{ getMandatoryParameterAs<int>(context.parameters, "toYear") };
|
||||
const int fromYear{ getMandatoryParameterAs<int>(context.getParameters(), "fromYear") };
|
||||
const int toYear{ getMandatoryParameterAs<int>(context.getParameters(), "toYear") };
|
||||
|
||||
Release::FindParameters params;
|
||||
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.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
releases = Release::findIds(context.dbSession, params);
|
||||
releases = Release::findIds(context.getDbSession(), params);
|
||||
}
|
||||
else if (type == "frequent")
|
||||
{
|
||||
scrobbling::IScrobblingService::FindParameters params;
|
||||
params.setUser(context.user->getId());
|
||||
params.setUser(context.getUser()->getId());
|
||||
params.setRange(range);
|
||||
params.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace lms::api::subsonic
|
||||
params.setRange(range);
|
||||
params.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
releases = Release::findIds(context.dbSession, params);
|
||||
releases = Release::findIds(context.getDbSession(), params);
|
||||
}
|
||||
else if (type == "random")
|
||||
{
|
||||
@@ -141,12 +141,12 @@ namespace lms::api::subsonic
|
||||
params.setRange(Range{ 0, size });
|
||||
params.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
releases = Release::findIds(context.dbSession, params);
|
||||
releases = Release::findIds(context.getDbSession(), params);
|
||||
}
|
||||
else if (type == "recent")
|
||||
{
|
||||
scrobbling::IScrobblingService::FindParameters params;
|
||||
params.setUser(context.user->getId());
|
||||
params.setUser(context.getUser()->getId());
|
||||
params.setRange(range);
|
||||
params.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace lms::api::subsonic
|
||||
else if (type == "starred")
|
||||
{
|
||||
feedback::IFeedbackService::FindParameters params;
|
||||
params.setUser(context.user->getId());
|
||||
params.setUser(context.getUser()->getId());
|
||||
params.setRange(range);
|
||||
params.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
@@ -166,12 +166,12 @@ namespace lms::api::subsonic
|
||||
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" }) };
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -181,11 +181,11 @@ namespace lms::api::subsonic
|
||||
Response handleGetStarredRequestCommon(RequestContext& context, bool id3)
|
||||
{
|
||||
// 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" }) };
|
||||
|
||||
feedback::IFeedbackService& feedbackService{ *core::Service<feedback::IFeedbackService>::get() };
|
||||
@@ -194,29 +194,29 @@ namespace lms::api::subsonic
|
||||
if (id3)
|
||||
{
|
||||
feedback::IFeedbackService::ArtistFindParameters artistFindParams;
|
||||
artistFindParams.setUser(context.user->getId());
|
||||
artistFindParams.setUser(context.getUser()->getId());
|
||||
artistFindParams.setSortMethod(ArtistSortMethod::SortName);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
feedback::IFeedbackService::FindParameters findParameters;
|
||||
findParameters.setUser(context.user->getId());
|
||||
findParameters.setUser(context.getUser()->getId());
|
||||
findParameters.filters.setMediaLibrary(mediaLibrary);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
for (const TrackId trackId : feedbackService.findStarredTracks(findParameters).results)
|
||||
{
|
||||
if (auto track{ Track::find(context.dbSession, trackId) })
|
||||
starredNode.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
if (auto track{ Track::find(context.getDbSession(), trackId) })
|
||||
starredNode.addArrayChild("song", createSongNode(context, track, context.getUser()));
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -236,23 +236,23 @@ namespace lms::api::subsonic
|
||||
Response handleGetRandomSongsRequest(RequestContext& context)
|
||||
{
|
||||
// Optional params
|
||||
const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
|
||||
std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(50) };
|
||||
const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
|
||||
std::size_t size{ getParameterAs<std::size_t>(context.getParameters(), "size").value_or(50) };
|
||||
if (size > defaultMaxCountSize)
|
||||
throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
Response::Node& randomSongsNode{ response.createNode("randomSongs") };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
Track::FindParameters params;
|
||||
params.setSortMethod(TrackSortMethod::Random);
|
||||
params.setRange(Range{ 0, size });
|
||||
params.filters.setMediaLibrary(mediaLibraryId);
|
||||
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
|
||||
randomSongsNode.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
|
||||
randomSongsNode.addArrayChild("song", createSongNode(context, track, context.getUser()));
|
||||
});
|
||||
|
||||
return response;
|
||||
@@ -261,19 +261,19 @@ namespace lms::api::subsonic
|
||||
Response handleGetSongsByGenreRequest(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
std::string genre{ getMandatoryParameterAs<std::string>(context.parameters, "genre") };
|
||||
std::string genre{ getMandatoryParameterAs<std::string>(context.getParameters(), "genre") };
|
||||
|
||||
// Optional params
|
||||
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
|
||||
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(10) };
|
||||
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.getParameters(), "musicFolderId").value_or(MediaLibraryId{}) };
|
||||
std::size_t count{ getParameterAs<std::size_t>(context.getParameters(), "count").value_or(10) };
|
||||
if (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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
@@ -281,7 +281,7 @@ namespace lms::api::subsonic
|
||||
if (!cluster)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
Response::Node& songsByGenreNode{ response.createNode("songsByGenre") };
|
||||
|
||||
Track::FindParameters params;
|
||||
@@ -289,8 +289,8 @@ namespace lms::api::subsonic
|
||||
params.filters.setMediaLibrary(mediaLibrary);
|
||||
params.setRange(Range{ offset, count });
|
||||
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
|
||||
songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
|
||||
songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.getUser()));
|
||||
});
|
||||
|
||||
return response;
|
||||
|
||||
@@ -38,18 +38,18 @@ namespace lms::api::subsonic
|
||||
|
||||
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") };
|
||||
|
||||
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) };
|
||||
bookmarkNode.addChild("entry", createSongNode(context, bookmark->getTrack(), context.user));
|
||||
bookmarkNode.addChild("entry", createSongNode(context, bookmark->getTrack(), context.getUser()));
|
||||
bookmarksNode.addArrayChild("bookmark", std::move(bookmarkNode));
|
||||
}
|
||||
|
||||
@@ -59,51 +59,51 @@ namespace lms::api::subsonic
|
||||
Response handleCreateBookmark(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
TrackId trackId{ getMandatoryParameterAs<TrackId>(context.parameters, "id") };
|
||||
unsigned long position{ getMandatoryParameterAs<unsigned long>(context.parameters, "position") };
|
||||
const std::optional<std::string> comment{ getParameterAs<std::string>(context.parameters, "comment") };
|
||||
TrackId trackId{ getMandatoryParameterAs<TrackId>(context.getParameters(), "id") };
|
||||
unsigned long position{ getMandatoryParameterAs<unsigned long>(context.getParameters(), "position") };
|
||||
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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
// 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)
|
||||
bookmark = context.dbSession.create<TrackBookmark>(context.user, track);
|
||||
bookmark = context.getDbSession().create<TrackBookmark>(context.getUser(), track);
|
||||
|
||||
bookmark.modify()->setOffset(std::chrono::milliseconds{ position });
|
||||
if (comment)
|
||||
bookmark.modify()->setComment(*comment);
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
|
||||
Response handleDeleteBookmark(RequestContext& context)
|
||||
{
|
||||
// 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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
bookmark.remove();
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
|
||||
// Use a dedicated internal playlist
|
||||
Response handleGetPlayQueue(RequestContext& context)
|
||||
{
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
const db::PlayQueue::pointer playQueue{ db::PlayQueue::find(context.dbSession, context.user->getId(), "subsonic") };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
const db::PlayQueue::pointer playQueue{ db::PlayQueue::find(context.getDbSession(), context.getUser()->getId(), "subsonic") };
|
||||
if (playQueue)
|
||||
{
|
||||
Response::Node& playQueueNode{ response.createNode("playQueue") };
|
||||
@@ -115,7 +115,7 @@ namespace lms::api::subsonic
|
||||
}
|
||||
|
||||
// mandatory fields
|
||||
playQueueNode.setAttribute("username", context.user->getLoginName());
|
||||
playQueueNode.setAttribute("username", context.getUser()->getLoginName());
|
||||
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...)
|
||||
|
||||
@@ -130,9 +130,9 @@ namespace lms::api::subsonic
|
||||
Response handleSavePlayQueue(RequestContext& context)
|
||||
{
|
||||
// optional params
|
||||
std::vector<db::TrackId> trackIds{ getMultiParametersAs<TrackId>(context.parameters, "id") };
|
||||
const std::optional<db::TrackId> currentTrackId{ getParameterAs<db::TrackId>(context.parameters, "current") };
|
||||
const std::chrono::milliseconds currentPositionInTrack{ getParameterAs<std::size_t>(context.parameters, "current").value_or(0) };
|
||||
std::vector<db::TrackId> trackIds{ getMultiParametersAs<TrackId>(context.getParameters(), "id") };
|
||||
const std::optional<db::TrackId> currentTrackId{ getParameterAs<db::TrackId>(context.getParameters(), "current") };
|
||||
const std::chrono::milliseconds currentPositionInTrack{ getParameterAs<std::size_t>(context.getParameters(), "current").value_or(0) };
|
||||
|
||||
std::vector<db::Track::pointer> tracks;
|
||||
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)
|
||||
if (!trackIds.empty())
|
||||
{
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
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)
|
||||
playQueue = context.dbSession.create<db::PlayQueue>(context.user, "subsonic");
|
||||
playQueue = context.getDbSession().create<db::PlayQueue>(context.getUser(), "subsonic");
|
||||
|
||||
playQueue.modify()->clear();
|
||||
std::size_t index{};
|
||||
@@ -171,6 +171,6 @@ namespace lms::api::subsonic
|
||||
playQueue.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime());
|
||||
}
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -91,7 +91,7 @@ namespace lms::api::subsonic
|
||||
params.setParentDirectory(parentDirectory->getId());
|
||||
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() };
|
||||
assert(!name.empty());
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace lms::api::subsonic
|
||||
|
||||
const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
std::vector<TrackId> tracks;
|
||||
tracks.reserve(count);
|
||||
@@ -126,7 +126,7 @@ namespace lms::api::subsonic
|
||||
params.setRange(Range{ 0, meanTrackCountPerArtist });
|
||||
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),
|
||||
std::begin(artistTracks.results),
|
||||
std::end(artistTracks.results));
|
||||
@@ -145,7 +145,7 @@ namespace lms::api::subsonic
|
||||
|
||||
const std::size_t meanTrackCountPerRelease{ (count / releaseIds.size()) + 1 };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
std::vector<TrackId> tracks;
|
||||
tracks.reserve(count);
|
||||
@@ -157,7 +157,7 @@ namespace lms::api::subsonic
|
||||
params.setRange(Range{ 0, meanTrackCountPerRelease });
|
||||
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),
|
||||
std::begin(releaseTracks.results),
|
||||
std::end(releaseTracks.results));
|
||||
@@ -174,31 +174,31 @@ namespace lms::api::subsonic
|
||||
Response handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
|
||||
{
|
||||
// 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)
|
||||
throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize };
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
else
|
||||
throw BadParameterGenericError{ "id" };
|
||||
|
||||
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" }) };
|
||||
for (const TrackId trackId : tracks)
|
||||
{
|
||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
||||
similarSongsNode.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
const Track::pointer track{ Track::find(context.getDbSession(), trackId) };
|
||||
similarSongsNode.addArrayChild("song", createSongNode(context, track, context.getUser()));
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -223,11 +223,11 @@ namespace lms::api::subsonic
|
||||
|
||||
Response handleGetMusicFoldersRequest(RequestContext& context)
|
||||
{
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
Response::Node& musicFoldersNode{ response.createNode("musicFolders") };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library) {
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
MediaLibrary::find(context.getDbSession(), [&](const MediaLibrary::pointer& library) {
|
||||
Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") };
|
||||
|
||||
musicFolderNode.setAttribute("id", library->getId().getValue());
|
||||
@@ -240,16 +240,16 @@ namespace lms::api::subsonic
|
||||
Response handleGetIndexesRequest(RequestContext& context)
|
||||
{
|
||||
// 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") };
|
||||
indexesNode.setAttribute("ignoredArticles", "");
|
||||
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;
|
||||
for (const Directory::pointer& rootdirectory : rootDirectories)
|
||||
@@ -257,8 +257,8 @@ namespace lms::api::subsonic
|
||||
Track::FindParameters params;
|
||||
params.setDirectory(rootdirectory->getId());
|
||||
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
|
||||
indexesNode.addArrayChild("child", createSongNode(context, track, context.user));
|
||||
Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
|
||||
indexesNode.addArrayChild("child", createSongNode(context, track, context.getUser()));
|
||||
});
|
||||
|
||||
getIndexedChildDirectories(context, rootdirectory, indexedDirectories);
|
||||
@@ -287,21 +287,21 @@ namespace lms::api::subsonic
|
||||
Response handleGetMusicDirectoryRequest(RequestContext& context)
|
||||
{
|
||||
// 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") };
|
||||
|
||||
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)
|
||||
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()));
|
||||
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.user->getId(), release->getId()) }; dateTime.isValid())
|
||||
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.getUser()->getId(), release->getId()) }; dateTime.isValid())
|
||||
directoryNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
|
||||
}
|
||||
|
||||
@@ -317,8 +317,8 @@ namespace lms::api::subsonic
|
||||
params.setParentDirectory(directory->getId());
|
||||
params.setSortMethod(DirectorySortMethod::Name);
|
||||
|
||||
Directory::find(context.dbSession, params, [&](const Directory::pointer& subDirectory) {
|
||||
const Release::pointer release{ getReleaseFromDirectory(context.dbSession, subDirectory->getId()) };
|
||||
Directory::find(context.getDbSession(), params, [&](const Directory::pointer& subDirectory) {
|
||||
const Release::pointer release{ getReleaseFromDirectory(context.getDbSession(), subDirectory->getId()) };
|
||||
|
||||
if (release)
|
||||
{
|
||||
@@ -343,8 +343,8 @@ namespace lms::api::subsonic
|
||||
params.setDirectory(directory->getId());
|
||||
params.setSortMethod(TrackSortMethod::AbsoluteFilePath);
|
||||
|
||||
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
|
||||
directoryNode.addArrayChild("child", createSongNode(context, track, context.user));
|
||||
Track::find(context.getDbSession(), params, [&](const Track::pointer& track) {
|
||||
directoryNode.addArrayChild("child", createSongNode(context, track, context.getUser()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -353,13 +353,13 @@ namespace lms::api::subsonic
|
||||
|
||||
Response handleGetGenresRequest(RequestContext& context)
|
||||
{
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
|
||||
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)
|
||||
{
|
||||
const auto clusters{ clusterType->getClusters() };
|
||||
@@ -374,9 +374,9 @@ namespace lms::api::subsonic
|
||||
Response handleGetArtistsRequest(RequestContext& context)
|
||||
{
|
||||
// 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") };
|
||||
artistsNode.setAttribute("ignoredArticles", "");
|
||||
@@ -384,10 +384,10 @@ namespace lms::api::subsonic
|
||||
|
||||
Artist::FindParameters parameters;
|
||||
{
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
parameters.setSortMethod(ArtistSortMethod::SortName);
|
||||
switch (context.user->getSubsonicArtistListMode())
|
||||
switch (context.getUser()->getSubsonicArtistListMode())
|
||||
{
|
||||
case SubsonicArtistListMode::AllArtists:
|
||||
break;
|
||||
@@ -411,10 +411,10 @@ namespace lms::api::subsonic
|
||||
bool hasMoreArtists{ true };
|
||||
while (hasMoreArtists)
|
||||
{
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
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)
|
||||
{
|
||||
std::string_view sortName{ artist->getSortName() };
|
||||
@@ -441,9 +441,9 @@ namespace lms::api::subsonic
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -454,18 +454,18 @@ namespace lms::api::subsonic
|
||||
Response handleGetArtistRequest(RequestContext& context)
|
||||
{
|
||||
// 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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
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)
|
||||
artistNode.addArrayChild("album", createAlbumNode(context, release, true /* id3 */));
|
||||
|
||||
@@ -477,18 +477,18 @@ namespace lms::api::subsonic
|
||||
Response handleGetAlbumRequest(RequestContext& context)
|
||||
{
|
||||
// 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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
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)
|
||||
albumNode.addArrayChild("song", createSongNode(context, track, true /* id3 */));
|
||||
|
||||
@@ -500,16 +500,16 @@ namespace lms::api::subsonic
|
||||
Response handleGetSongRequest(RequestContext& context)
|
||||
{
|
||||
// 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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
response.addNode("song", createSongNode(context, track, context.user));
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
response.addNode("song", createSongNode(context, track, context.getUser()));
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -517,24 +517,24 @@ namespace lms::api::subsonic
|
||||
Response handleGetArtistInfo2Request(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
ArtistId id{ getMandatoryParameterAs<ArtistId>(context.parameters, "id") };
|
||||
ArtistId id{ getMandatoryParameterAs<ArtistId>(context.getParameters(), "id") };
|
||||
|
||||
// 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" }) };
|
||||
|
||||
{
|
||||
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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
if (const std::optional<core::UUID> artistMBID{ artist->getMBID() })
|
||||
{
|
||||
switch (context.responseFormat)
|
||||
switch (context.getResponseFormat())
|
||||
{
|
||||
case ResponseFormat::json:
|
||||
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())
|
||||
{
|
||||
switch (context.responseFormat)
|
||||
switch (context.getResponseFormat())
|
||||
{
|
||||
case ResponseFormat::json:
|
||||
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 transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
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)
|
||||
artistInfoNode.addArrayChild("similarArtist", createArtistNode(context, similarArtist));
|
||||
}
|
||||
@@ -579,14 +579,14 @@ namespace lms::api::subsonic
|
||||
|
||||
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));
|
||||
}
|
||||
return response;
|
||||
@@ -594,14 +594,14 @@ namespace lms::api::subsonic
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -621,29 +621,29 @@ namespace lms::api::subsonic
|
||||
Response handleGetTopSongs(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
std::string_view artistName{ getMandatoryParameterAs<std::string_view>(context.parameters, "artist") };
|
||||
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(50) };
|
||||
std::string_view artistName{ getMandatoryParameterAs<std::string_view>(context.getParameters(), "artist") };
|
||||
std::size_t count{ getParameterAs<std::size_t>(context.getParameters(), "count").value_or(50) };
|
||||
if (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") };
|
||||
|
||||
const auto artists{ Artist::find(context.dbSession, artistName) };
|
||||
const auto artists{ Artist::find(context.getDbSession(), artistName) };
|
||||
if (artists.size() == 1)
|
||||
{
|
||||
scrobbling::IScrobblingService::FindParameters params;
|
||||
params.setUser(context.user->getId());
|
||||
params.setUser(context.getUser()->getId());
|
||||
params.setRange(db::Range{ 0, count });
|
||||
params.setArtist(artists.front()->getId());
|
||||
|
||||
const auto trackIds{ core::Service<scrobbling::IScrobblingService>::get()->getTopTracks(params) };
|
||||
for (const TrackId trackId : trackIds.results)
|
||||
{
|
||||
if (Track::pointer track{ Track::find(context.dbSession, trackId) })
|
||||
topSongs.addArrayChild("song", createSongNode(context, track, context.user));
|
||||
if (Track::pointer track{ Track::find(context.getDbSession(), trackId) })
|
||||
topSongs.addArrayChild("song", createSongNode(context, track, context.getUser()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,72 +113,72 @@ namespace lms::api::subsonic
|
||||
|
||||
Response handleStarRequest(RequestContext& context)
|
||||
{
|
||||
StarParameters params{ getStarParameters(context.parameters) };
|
||||
StarParameters params{ getStarParameters(context.getParameters()) };
|
||||
|
||||
for (const DirectoryId id : params.directoryIds)
|
||||
{
|
||||
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.dbSession, id) }; releaseId.isValid())
|
||||
core::Service<feedback::IFeedbackService>::get()->star(context.user->getId(), releaseId);
|
||||
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.getDbSession(), id) }; releaseId.isValid())
|
||||
core::Service<feedback::IFeedbackService>::get()->star(context.getUser()->getId(), releaseId);
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
{
|
||||
const StarParameters params{ getStarParameters(context.parameters) };
|
||||
const StarParameters params{ getStarParameters(context.getParameters()) };
|
||||
|
||||
for (const DirectoryId id : params.directoryIds)
|
||||
{
|
||||
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.dbSession, id) }; releaseId.isValid())
|
||||
core::Service<feedback::IFeedbackService>::get()->unstar(context.user->getId(), releaseId);
|
||||
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.getDbSession(), id) }; releaseId.isValid())
|
||||
core::Service<feedback::IFeedbackService>::get()->unstar(context.getUser()->getId(), releaseId);
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
{
|
||||
const RatingParameters params{ getRatingParameters(context.parameters) };
|
||||
const RatingParameters params{ getRatingParameters(context.getParameters()) };
|
||||
|
||||
if (const ArtistId * artistId{ std::get_if<ArtistId>(¶ms.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>(¶ms.id) })
|
||||
{
|
||||
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.dbSession, *directoryId) }; releaseId.isValid())
|
||||
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), releaseId, params.rating);
|
||||
if (const ReleaseId releaseId{ getReleaseIdFromDirectory(context.getDbSession(), *directoryId) }; releaseId.isValid())
|
||||
core::Service<feedback::IFeedbackService>::get()->setRating(context.getUser()->getId(), releaseId, params.rating);
|
||||
}
|
||||
else if (const ReleaseId * releaseId{ std::get_if<ReleaseId>(¶ms.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>(¶ms.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)
|
||||
{
|
||||
const std::vector<TrackId> ids{ getMandatoryMultiParametersAs<TrackId>(context.parameters, "id") };
|
||||
const std::vector<unsigned long> times{ getMultiParametersAs<unsigned long>(context.parameters, "time") };
|
||||
const bool submission{ getParameterAs<bool>(context.parameters, "submission").value_or(true) };
|
||||
const std::vector<TrackId> ids{ getMandatoryMultiParametersAs<TrackId>(context.getParameters(), "id") };
|
||||
const std::vector<unsigned long> times{ getMultiParametersAs<unsigned long>(context.getParameters(), "time") };
|
||||
const bool submission{ getParameterAs<bool>(context.getParameters(), "submission").value_or(true) };
|
||||
|
||||
// playing now => only one at a time
|
||||
if (!submission && ids.size() > 1)
|
||||
@@ -190,13 +190,13 @@ namespace lms::api::subsonic
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -204,11 +204,11 @@ namespace lms::api::subsonic
|
||||
{
|
||||
const TrackId trackId{ ids[i] };
|
||||
const unsigned long time{ times[i] };
|
||||
core::Service<scrobbling::IScrobblingService>::get()->addTimedListen({ { context.user->getId(), trackId }, Wt::WDateTime::fromTime_t(static_cast<std::time_t>(time / 1000)) });
|
||||
core::Service<scrobbling::IScrobblingService>::get()->addTimedListen({ { context.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
|
||||
@@ -51,7 +51,7 @@ namespace lms::api::subsonic::Scan
|
||||
|
||||
Response handleGetScanStatus(RequestContext& context)
|
||||
{
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
response.addNode("scanStatus", createStatusResponseNode());
|
||||
|
||||
return response;
|
||||
@@ -61,7 +61,7 @@ namespace lms::api::subsonic::Scan
|
||||
{
|
||||
core::Service<IScannerService>::get()->requestImmediateScan();
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
response.addNode("scanStatus", createStatusResponseNode());
|
||||
|
||||
return response;
|
||||
|
||||
@@ -176,20 +176,20 @@ namespace lms::api::subsonic
|
||||
StreamParameters getStreamParameters(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
const auto trackId{ getParameterAs<db::TrackId>(context.parameters, "id") };
|
||||
const auto podcastEpisodeId{ getParameterAs<db::PodcastEpisodeId>(context.parameters, "id") };
|
||||
const auto trackId{ getParameterAs<db::TrackId>(context.getParameters(), "id") };
|
||||
const auto podcastEpisodeId{ getParameterAs<db::PodcastEpisodeId>(context.getParameters(), "id") };
|
||||
if (!trackId && !podcastEpisodeId)
|
||||
throw RequiredParameterMissingError{ "id" };
|
||||
|
||||
const AudioFileId audioId{ trackId ? AudioFileId{ *trackId } : AudioFileId{ *podcastEpisodeId } };
|
||||
|
||||
// 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
|
||||
const std::string format{ getParameterAs<std::string>(context.parameters, "format").value_or("") };
|
||||
std::size_t timeOffset{ getParameterAs<std::size_t>(context.parameters, "timeOffset").value_or(0) };
|
||||
bool estimateContentLength{ getParameterAs<bool>(context.parameters, "estimateContentLength").value_or(false) };
|
||||
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.getParameters(), "format").value_or("") };
|
||||
std::size_t timeOffset{ getParameterAs<std::size_t>(context.getParameters(), "timeOffset").value_or(0) };
|
||||
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;
|
||||
|
||||
@@ -205,8 +205,8 @@ namespace lms::api::subsonic
|
||||
std::optional<transcoding::OutputFormat> requestedFormat{ subsonicStreamFormatToAvOutputFormat(format) };
|
||||
if (!requestedFormat)
|
||||
{
|
||||
if (context.user->getSubsonicEnableTranscodingByDefault())
|
||||
requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat());
|
||||
if (context.getUser()->getSubsonicEnableTranscodingByDefault())
|
||||
requestedFormat = userTranscodeFormatToAvFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat());
|
||||
}
|
||||
|
||||
if (!requestedFormat && (maxBitRate == 0 || audioFileInfo.bitrate <= maxBitRate))
|
||||
@@ -231,9 +231,9 @@ namespace lms::api::subsonic
|
||||
|
||||
// Need to transcode here
|
||||
if (!requestedFormat)
|
||||
requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat());
|
||||
requestedFormat = userTranscodeFormatToAvFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat());
|
||||
if (!bitrate)
|
||||
bitrate = context.user->getSubsonicDefaultTranscodingOutputBitrate();
|
||||
bitrate = context.getUser()->getSubsonicDefaultTranscodingOutputBitrate();
|
||||
if (maxBitRate)
|
||||
bitrate = std::min<std::size_t>(bitrate, maxBitRate);
|
||||
|
||||
@@ -248,13 +248,13 @@ namespace lms::api::subsonic
|
||||
|
||||
Response handleGetLyrics(RequestContext& context)
|
||||
{
|
||||
std::string artistName{ getParameterAs<std::string>(context.parameters, "artist").value_or("") };
|
||||
std::string titleName{ getParameterAs<std::string>(context.parameters, "title").value_or("") };
|
||||
std::string artistName{ getParameterAs<std::string>(context.getParameters(), "artist").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
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
db::Track::FindParameters params;
|
||||
params.setName(titleName);
|
||||
@@ -262,7 +262,7 @@ namespace lms::api::subsonic
|
||||
params.setRange(db::Range{ .offset = 0, .size = 2 });
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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.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));
|
||||
});
|
||||
}
|
||||
@@ -282,14 +282,14 @@ namespace lms::api::subsonic
|
||||
Response handleGetLyricsBySongId(RequestContext& context)
|
||||
{
|
||||
// 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") };
|
||||
lyricsList.createEmptyArrayChild("structuredLyrics");
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
const db::Track::pointer track{ db::Track::find(context.dbSession, id) };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
const db::Track::pointer track{ db::Track::find(context.getDbSession(), id) };
|
||||
if (track)
|
||||
{
|
||||
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
|
||||
|
||||
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));
|
||||
hasExternalLyrics = true;
|
||||
});
|
||||
@@ -305,7 +305,7 @@ namespace lms::api::subsonic
|
||||
if (!hasExternalLyrics)
|
||||
{
|
||||
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));
|
||||
});
|
||||
}
|
||||
@@ -322,13 +322,13 @@ namespace lms::api::subsonic
|
||||
if (!continuation)
|
||||
{
|
||||
// Mandatory params
|
||||
db::TrackId id{ getMandatoryParameterAs<db::TrackId>(context.parameters, "id") };
|
||||
db::TrackId id{ getMandatoryParameterAs<db::TrackId>(context.getParameters(), "id") };
|
||||
|
||||
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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
@@ -381,9 +381,9 @@ namespace lms::api::subsonic
|
||||
void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
|
||||
{
|
||||
// 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)
|
||||
*size = std::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 });
|
||||
|
||||
|
||||
@@ -53,9 +53,9 @@ namespace lms::api::subsonic
|
||||
|
||||
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") };
|
||||
|
||||
auto addTrackList{ [&](const db::TrackList::pointer& trackList) {
|
||||
@@ -65,10 +65,10 @@ namespace lms::api::subsonic
|
||||
// First add user's playlists
|
||||
{
|
||||
TrackList::FindParameters params;
|
||||
params.setUser(context.user->getId());
|
||||
params.setUser(context.getUser()->getId());
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -78,10 +78,10 @@ namespace lms::api::subsonic
|
||||
TrackList::FindParameters params;
|
||||
params.setVisibility(TrackList::Visibility::Public);
|
||||
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) {
|
||||
assert(trackList->getUserId() != context.user->getId());
|
||||
db::TrackList::find(context.getDbSession(), params, [&](const db::TrackList::pointer& trackList) {
|
||||
assert(trackList->getUserId() != context.getUser()->getId());
|
||||
addTrackList(trackList);
|
||||
});
|
||||
}
|
||||
@@ -92,23 +92,23 @@ namespace lms::api::subsonic
|
||||
Response handleGetPlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// 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)
|
||||
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{};
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
Response::Node playlistNode{ createPlaylistNode(context, trackList) };
|
||||
|
||||
auto entries{ trackList->getEntries() };
|
||||
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));
|
||||
|
||||
@@ -118,21 +118,21 @@ namespace lms::api::subsonic
|
||||
Response handleCreatePlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// Optional params
|
||||
const auto id{ getParameterAs<TrackListId>(context.parameters, "playlistId") };
|
||||
auto name{ getParameterAs<std::string>(context.parameters, "name") };
|
||||
const auto id{ getParameterAs<TrackListId>(context.getParameters(), "playlistId") };
|
||||
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)
|
||||
throw RequiredParameterMissingError{ "name or playlistId" };
|
||||
|
||||
auto transaction{ context.dbSession.createWriteTransaction() };
|
||||
auto transaction{ context.getDbSession().createWriteTransaction() };
|
||||
|
||||
TrackList::pointer trackList;
|
||||
if (id)
|
||||
{
|
||||
trackList = TrackList::find(context.dbSession, *id);
|
||||
checkTrackListModificationAccess(trackList, context.user->getId());
|
||||
trackList = TrackList::find(context.getDbSession(), *id);
|
||||
checkTrackListModificationAccess(trackList, context.getUser()->getId());
|
||||
|
||||
if (name)
|
||||
trackList.modify()->setName(*name);
|
||||
@@ -142,26 +142,26 @@ namespace lms::api::subsonic
|
||||
}
|
||||
else
|
||||
{
|
||||
trackList = context.dbSession.create<TrackList>(*name, TrackListType::PlayList);
|
||||
trackList.modify()->setUser(context.user);
|
||||
trackList = context.getDbSession().create<TrackList>(*name, TrackListType::PlayList);
|
||||
trackList.modify()->setUser(context.getUser());
|
||||
trackList.modify()->setVisibility(TrackList::Visibility::Private);
|
||||
}
|
||||
|
||||
for (const TrackId trackId : trackIds)
|
||||
{
|
||||
Track::pointer track{ Track::find(context.dbSession, trackId) };
|
||||
Track::pointer track{ Track::find(context.getDbSession(), trackId) };
|
||||
if (!track)
|
||||
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) };
|
||||
|
||||
auto entries{ trackList->getEntries() };
|
||||
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));
|
||||
|
||||
@@ -171,19 +171,19 @@ namespace lms::api::subsonic
|
||||
Response handleUpdatePlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
TrackListId id{ getMandatoryParameterAs<TrackListId>(context.parameters, "playlistId") };
|
||||
TrackListId id{ getMandatoryParameterAs<TrackListId>(context.getParameters(), "playlistId") };
|
||||
|
||||
// Optional parameters
|
||||
auto name{ getParameterAs<std::string>(context.parameters, "name") };
|
||||
auto isPublic{ getParameterAs<bool>(context.parameters, "public") };
|
||||
auto name{ getParameterAs<std::string>(context.getParameters(), "name") };
|
||||
auto isPublic{ getParameterAs<bool>(context.getParameters(), "public") };
|
||||
|
||||
std::vector<TrackId> trackIdsToAdd{ getMultiParametersAs<TrackId>(context.parameters, "songIdToAdd") };
|
||||
std::vector<std::size_t> trackPositionsToRemove{ getMultiParametersAs<std::size_t>(context.parameters, "songIndexToRemove") };
|
||||
std::vector<TrackId> trackIdsToAdd{ getMultiParametersAs<TrackId>(context.getParameters(), "songIdToAdd") };
|
||||
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) };
|
||||
checkTrackListModificationAccess(trackList, context.user->getId());
|
||||
TrackList::pointer trackList{ TrackList::find(context.getDbSession(), id) };
|
||||
checkTrackListModificationAccess(trackList, context.getUser()->getId());
|
||||
|
||||
if (name)
|
||||
trackList.modify()->setName(*name);
|
||||
@@ -206,27 +206,27 @@ namespace lms::api::subsonic
|
||||
// Add tracks
|
||||
for (const TrackId trackIdToAdd : trackIdsToAdd)
|
||||
{
|
||||
Track::pointer track{ Track::find(context.dbSession, trackIdToAdd) };
|
||||
Track::pointer track{ Track::find(context.getDbSession(), trackIdToAdd) };
|
||||
if (!track)
|
||||
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)
|
||||
{
|
||||
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) };
|
||||
checkTrackListModificationAccess(trackList, context.user->getId());
|
||||
TrackList::pointer trackList{ TrackList::find(context.getDbSession(), id) };
|
||||
checkTrackListModificationAccess(trackList, context.getUser()->getId());
|
||||
|
||||
trackList.remove();
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -34,14 +34,14 @@ namespace lms::api::subsonic
|
||||
{
|
||||
Response handleGetPodcasts(RequestContext& context)
|
||||
{
|
||||
const bool includeEpisodes{ getParameterAs<bool>(context.parameters, "includeEpisodes").value_or(true) };
|
||||
const std::optional<db::PodcastId> podcastId{ getParameterAs<db::PodcastId>(context.parameters, "id") };
|
||||
const bool includeEpisodes{ getParameterAs<bool>(context.getParameters(), "includeEpisodes").value_or(true) };
|
||||
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") };
|
||||
podcastsNode.createEmptyArrayChild("channel");
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
auto processPodcast{ [&](const db::Podcast::pointer& podcast) {
|
||||
podcastsNode.addArrayChild("channel", createPodcastNode(context, podcast, includeEpisodes));
|
||||
@@ -49,34 +49,34 @@ namespace lms::api::subsonic
|
||||
|
||||
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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
processPodcast(podcast);
|
||||
}
|
||||
else
|
||||
db::Podcast::find(context.dbSession, processPodcast);
|
||||
db::Podcast::find(context.getDbSession(), processPodcast);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
Response::Node& newestPodcastsNode{ response.createNode("newestPodcasts") };
|
||||
newestPodcastsNode.createEmptyArrayChild("episode");
|
||||
|
||||
{
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
db::PodcastEpisode::FindParameters findParameters;
|
||||
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));
|
||||
});
|
||||
}
|
||||
@@ -88,13 +88,13 @@ namespace lms::api::subsonic
|
||||
{
|
||||
core::Service<podcast::IPodcastService>::get()->refreshPodcasts();
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
|
||||
Response handleCreatePodcastChannel(RequestContext& context)
|
||||
{
|
||||
// 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://")))
|
||||
throw BadParameterGenericError{ "Invalid url" };
|
||||
@@ -102,52 +102,52 @@ namespace lms::api::subsonic
|
||||
// no effect if podcast already exists
|
||||
core::Service<podcast::IPodcastService>::get()->addPodcast(url);
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
|
||||
Response handleDeletePodcastChannel(RequestContext& context)
|
||||
{
|
||||
// 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))
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
|
||||
Response handleDeletePodcastEpisode(RequestContext& context)
|
||||
{
|
||||
// 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))
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
|
||||
Response handleDownloadPodcastEpisode(RequestContext& context)
|
||||
{
|
||||
// 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))
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
|
||||
Response handleGetPodcastEpisode(RequestContext& context)
|
||||
{
|
||||
// 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)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
|
||||
@@ -118,14 +118,14 @@ namespace lms::api::subsonic
|
||||
{
|
||||
// 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)
|
||||
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)
|
||||
return;
|
||||
|
||||
if (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;
|
||||
params.setKeywords(keywords);
|
||||
@@ -133,7 +133,7 @@ namespace lms::api::subsonic
|
||||
params.setWithNoTrack(true);
|
||||
params.setMediaLibrary(mediaLibrary);
|
||||
|
||||
Directory::find(context.dbSession, params, [&](const Directory::pointer& directory) {
|
||||
Directory::find(context.getDbSession(), params, [&](const Directory::pointer& directory) {
|
||||
Response::Node childNode;
|
||||
childNode.setAttribute("id", idToString(directory->getId()));
|
||||
childNode.setAttribute("name", directory->getName());
|
||||
@@ -147,14 +147,14 @@ namespace lms::api::subsonic
|
||||
{
|
||||
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)
|
||||
return;
|
||||
|
||||
if (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;
|
||||
auto findArtists{ [&] {
|
||||
@@ -164,7 +164,7 @@ namespace lms::api::subsonic
|
||||
params.setRange(Range{ artistOffset, artistCount });
|
||||
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));
|
||||
lastRetrievedId = artist->getId();
|
||||
});
|
||||
@@ -177,9 +177,9 @@ namespace lms::api::subsonic
|
||||
else
|
||||
{
|
||||
ScanTracker<ArtistId>::ScanInfo scanInfo{
|
||||
.clientAddress = context.clientIpAddr,
|
||||
.clientName = context.clientInfo.name,
|
||||
.user = context.user->getId(),
|
||||
.clientAddress = context.getClientIpAddr(),
|
||||
.clientName = std::string{ context.getClientName() },
|
||||
.user = context.getUser()->getId(),
|
||||
.library = mediaLibrary,
|
||||
.offset = artistOffset
|
||||
};
|
||||
@@ -187,7 +187,7 @@ namespace lms::api::subsonic
|
||||
if (ArtistId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
|
||||
{
|
||||
Artist::find(
|
||||
context.dbSession, cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist) {
|
||||
context.getDbSession(), cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist) {
|
||||
searchResultNode.addArrayChild("artist", createArtistNode(context, artist));
|
||||
},
|
||||
mediaLibrary);
|
||||
@@ -210,14 +210,14 @@ namespace lms::api::subsonic
|
||||
{
|
||||
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)
|
||||
return;
|
||||
|
||||
if (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;
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace lms::api::subsonic
|
||||
params.filters.setMediaLibrary(mediaLibrary);
|
||||
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));
|
||||
lastRetrievedId = release->getId();
|
||||
});
|
||||
@@ -241,9 +241,9 @@ namespace lms::api::subsonic
|
||||
else
|
||||
{
|
||||
ScanTracker<ReleaseId>::ScanInfo scanInfo{
|
||||
.clientAddress = context.clientIpAddr,
|
||||
.clientName = context.clientInfo.name,
|
||||
.user = context.user->getId(),
|
||||
.clientAddress = context.getClientIpAddr(),
|
||||
.clientName = std::string{ context.getClientName() },
|
||||
.user = context.getUser()->getId(),
|
||||
.library = mediaLibrary,
|
||||
.offset = albumOffset
|
||||
};
|
||||
@@ -251,7 +251,7 @@ namespace lms::api::subsonic
|
||||
if (ReleaseId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
|
||||
{
|
||||
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));
|
||||
},
|
||||
mediaLibrary);
|
||||
@@ -274,14 +274,14 @@ namespace lms::api::subsonic
|
||||
{
|
||||
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)
|
||||
return;
|
||||
|
||||
if (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;
|
||||
|
||||
@@ -292,7 +292,7 @@ namespace lms::api::subsonic
|
||||
params.filters.setMediaLibrary(mediaLibrary);
|
||||
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));
|
||||
lastRetrievedId = track->getId();
|
||||
});
|
||||
@@ -305,9 +305,9 @@ namespace lms::api::subsonic
|
||||
else
|
||||
{
|
||||
ScanTracker<TrackId>::ScanInfo scanInfo{
|
||||
.clientAddress = context.clientIpAddr,
|
||||
.clientName = context.clientInfo.name,
|
||||
.user = context.user->getId(),
|
||||
.clientAddress = context.getClientIpAddr(),
|
||||
.clientName = std::string{ context.getClientName() },
|
||||
.user = context.getUser()->getId(),
|
||||
.library = mediaLibrary,
|
||||
.offset = songOffset
|
||||
};
|
||||
@@ -315,7 +315,7 @@ namespace lms::api::subsonic
|
||||
if (TrackId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
|
||||
{
|
||||
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));
|
||||
},
|
||||
mediaLibrary);
|
||||
@@ -337,24 +337,24 @@ namespace lms::api::subsonic
|
||||
Response handleSearchRequestCommon(RequestContext& context, bool id3)
|
||||
{
|
||||
// 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 };
|
||||
|
||||
// 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 ""
|
||||
if (context.clientInfo.name == "Symfonium")
|
||||
if (context.getClientName() == "Symfonium")
|
||||
query = core::stringUtils::stringTrim(query, "\"");
|
||||
|
||||
std::vector<std::string_view> keywords;
|
||||
if (!query.empty())
|
||||
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") };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
if (id3)
|
||||
findRequestedArtists(context, keywords, mediaLibrary, searchResultNode);
|
||||
|
||||
@@ -4,12 +4,12 @@ namespace lms::api::subsonic
|
||||
{
|
||||
Response handlePingRequest(RequestContext& context)
|
||||
{
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
return Response::createOkResponse(context.getServerProtocolVersion());
|
||||
}
|
||||
|
||||
Response handleGetLicenseRequest(RequestContext& context)
|
||||
{
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
|
||||
Response::Node& licenseNode{ response.createNode("license") };
|
||||
licenseNode.setAttribute("licenseExpires", "2035-09-03T14:46:43");
|
||||
@@ -21,7 +21,7 @@ namespace lms::api::subsonic
|
||||
|
||||
Response handleGetOpenSubsonicExtensions(RequestContext& context)
|
||||
{
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
|
||||
{
|
||||
Response::Node& transcodeOffsetNode{ response.createArrayNode("openSubsonicExtensions") };
|
||||
|
||||
@@ -16,24 +16,24 @@ namespace lms::api::subsonic
|
||||
{
|
||||
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{};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
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);
|
||||
|
||||
const User::pointer user{ User::find(context.dbSession, username) };
|
||||
const User::pointer user{ User::find(context.getDbSession(), username) };
|
||||
if (!user)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
response.addNode("user", createUserNode(context, user));
|
||||
|
||||
return response;
|
||||
@@ -41,11 +41,11 @@ namespace lms::api::subsonic
|
||||
|
||||
Response handleGetUsersRequest(RequestContext& context)
|
||||
{
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
Response::Node& usersNode{ response.createNode("users") };
|
||||
|
||||
auto transaction{ context.dbSession.createReadTransaction() };
|
||||
User::find(context.dbSession, User::FindParameters{}, [&](const User::pointer& user) {
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
User::find(context.getDbSession(), User::FindParameters{}, [&](const User::pointer& user) {
|
||||
usersNode.addArrayChild("user", createUserNode(context, user));
|
||||
});
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace lms::api::subsonic
|
||||
Directory::FindParameters params;
|
||||
params.setRelease(release->getId());
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
const ClusterType::pointer genreClusterType{ ClusterType::find(context.dbSession, "GENRE") };
|
||||
const ClusterType::pointer genreClusterType{ ClusterType::find(context.getDbSession(), "GENRE") };
|
||||
if (genreClusterType)
|
||||
{
|
||||
const auto clusters{ release->getClusters(genreClusterType->getId(), 1) };
|
||||
@@ -127,14 +127,14 @@ namespace lms::api::subsonic
|
||||
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));
|
||||
|
||||
// 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);
|
||||
|
||||
if (!context.enableOpenSubsonic)
|
||||
if (!context.isOpenSubsonicEnabled())
|
||||
return albumNode;
|
||||
|
||||
// OpenSubsonic specific fields (must always be set)
|
||||
@@ -143,7 +143,7 @@ namespace lms::api::subsonic
|
||||
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{ "" });
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace lms::api::subsonic
|
||||
params.setRelease(release->getId());
|
||||
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());
|
||||
});
|
||||
} };
|
||||
@@ -174,7 +174,7 @@ namespace lms::api::subsonic
|
||||
params.setRelease(release->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()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace lms::api::subsonic
|
||||
|
||||
if (const auto releaseMBID{ release->getMBID() })
|
||||
{
|
||||
switch (context.responseFormat)
|
||||
switch (context.getResponseFormat())
|
||||
{
|
||||
case ResponseFormat::json:
|
||||
albumInfo.setAttribute("musicBrainzId", releaseMBID->getAsString());
|
||||
|
||||
@@ -101,17 +101,17 @@ namespace lms::api::subsonic
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
|
||||
// OpenSubsonic specific fields (must always be set)
|
||||
if (context.enableOpenSubsonic)
|
||||
if (context.isOpenSubsonicEnabled())
|
||||
{
|
||||
artistNode.setAttribute("mediaType", "artist");
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace lms::api::subsonic
|
||||
// roles
|
||||
Response::Node 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node clusterNode;
|
||||
|
||||
switch (context.responseFormat)
|
||||
switch (context.getResponseFormat())
|
||||
{
|
||||
case ResponseFormat::json:
|
||||
clusterNode.setAttribute("value", cluster->getName());
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace lms::api::subsonic
|
||||
addLine(std::move(line));
|
||||
}
|
||||
|
||||
switch (context.responseFormat)
|
||||
switch (context.getResponseFormat())
|
||||
{
|
||||
case ResponseFormat::json:
|
||||
lyricsNode.setAttribute("value", lyricsText);
|
||||
@@ -89,7 +89,7 @@ namespace lms::api::subsonic
|
||||
if (timestamp)
|
||||
lineNode.setAttribute("start", std::chrono::duration_cast<std::chrono::milliseconds>(*timestamp).count());
|
||||
|
||||
switch (context.responseFormat)
|
||||
switch (context.getResponseFormat())
|
||||
{
|
||||
case ResponseFormat::json:
|
||||
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 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() };
|
||||
playlistNode.setAttribute("coverArt", idToString(coverArtId));
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace lms::api::subsonic
|
||||
params.setPodcast(podcast->getId());
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace lms::api::subsonic
|
||||
trackResponse.setAttribute("year", *originalYear);
|
||||
else if (const auto year{ track->getYear() })
|
||||
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
|
||||
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 .*/);
|
||||
}
|
||||
|
||||
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("transcodedContentType", core::getMimeType(std::filesystem::path{ "." + fileSuffix }));
|
||||
}
|
||||
@@ -160,10 +160,10 @@ namespace lms::api::subsonic
|
||||
trackResponse.setAttribute("type", "music");
|
||||
trackResponse.setAttribute("created", core::stringUtils::toISO8601String(track->getAddedTime()));
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
// Report the first GENRE for this track
|
||||
@@ -173,13 +173,13 @@ namespace lms::api::subsonic
|
||||
params.setTrack(track->getId());
|
||||
params.setClusterTypeName("GENRE");
|
||||
|
||||
genres = Cluster::find(context.dbSession, params).results;
|
||||
genres = Cluster::find(context.getDbSession(), params).results;
|
||||
if (!genres.empty())
|
||||
trackResponse.setAttribute("genre", genres.front()->getName());
|
||||
}
|
||||
|
||||
// OpenSubsonic specific fields (must always be set)
|
||||
if (!context.enableOpenSubsonic)
|
||||
if (!context.isOpenSubsonicEnabled())
|
||||
return trackResponse;
|
||||
|
||||
trackResponse.setAttribute("comment", track->getComment());
|
||||
@@ -190,7 +190,7 @@ namespace lms::api::subsonic
|
||||
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) : "");
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace lms::api::subsonic
|
||||
trackResponse.createEmptyArrayChild("artists");
|
||||
trackResponse.createEmptyArrayChild("contributors");
|
||||
|
||||
TrackArtistLink::find(context.dbSession, track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) {
|
||||
TrackArtistLink::find(context.getDbSession(), track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) {
|
||||
switch (link->getType())
|
||||
{
|
||||
case TrackArtistLinkType::Artist:
|
||||
@@ -230,7 +230,7 @@ namespace lms::api::subsonic
|
||||
params.setTrack(track->getId());
|
||||
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());
|
||||
} };
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace lms::api::subsonic
|
||||
userNode.setAttribute("shareRole", false); // not supported
|
||||
|
||||
// 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());
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user