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