Auto reformatted the base, ref #470

This commit is contained in:
emeric
2024-05-24 23:31:52 +02:00
parent 83b868673c
commit 39941d90a3
460 changed files with 8583 additions and 8514 deletions
+2 -1
View File
@@ -20,6 +20,7 @@
#pragma once
#include <string>
#include "ProtocolVersion.hpp"
namespace lms::api::subsonic
@@ -32,4 +33,4 @@ namespace lms::api::subsonic
std::string password;
ProtocolVersion version;
};
}
} // namespace lms::api::subsonic
+1 -1
View File
@@ -39,4 +39,4 @@ namespace lms::api::subsonic
return password;
}
}
} // namespace lms::api::subsonic
+4 -4
View File
@@ -22,11 +22,12 @@
#include <Wt/Http/Request.h>
#include <optional>
#include <vector>
#include <string>
#include <vector>
#include "database/Types.hpp"
#include "core/String.hpp"
#include "database/Types.hpp"
#include "SubsonicResponse.hpp"
namespace lms::api::subsonic
@@ -84,5 +85,4 @@ namespace lms::api::subsonic
bool hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param);
std::string decodePasswordIfNeeded(const std::string& password);
}
} // namespace lms::api::subsonic
+1 -2
View File
@@ -51,5 +51,4 @@ namespace lms::core::stringUtils
return version;
}
}
} // namespace lms::core::stringUtils
+1 -2
View File
@@ -32,11 +32,10 @@ namespace lms::api::subsonic
static inline constexpr ProtocolVersion defaultServerProtocolVersion{ 1, 16, 0 };
static inline constexpr std::string_view serverVersion{ "6" };
}
} // namespace lms::api::subsonic
namespace lms::core::stringUtils
{
template<>
std::optional<api::subsonic::ProtocolVersion> readAs(std::string_view str);
}
+4 -4
View File
@@ -24,6 +24,7 @@
#include <Wt/Http/Request.h>
#include "database/Object.hpp"
#include "ClientInfo.hpp"
#include "ProtocolVersion.hpp"
@@ -31,7 +32,7 @@ namespace lms::db
{
class Session;
class User;
}
} // namespace lms::db
namespace lms::api::subsonic
{
@@ -43,7 +44,6 @@ namespace lms::api::subsonic
ClientInfo clientInfo;
ProtocolVersion serverProtocolVersion;
bool enableOpenSubsonic{ true };
bool enableDefaultCover{ };
bool enableDefaultCover{};
};
}
} // namespace lms::api::subsonic
+1 -1
View File
@@ -141,4 +141,4 @@ namespace lms::core::stringUtils
return std::nullopt;
}
}
} // namespace lms::core::stringUtils
+5 -4
View File
@@ -19,16 +19,18 @@
#pragma once
#include "core/String.hpp"
#include "database/ArtistId.hpp"
#include "database/MediaLibraryId.hpp"
#include "database/ReleaseId.hpp"
#include "database/TrackId.hpp"
#include "database/TrackListId.hpp"
#include "core/String.hpp"
namespace lms::api::subsonic
{
struct RootId {};
struct RootId
{
};
std::string idToString(db::ArtistId id);
std::string idToString(db::MediaLibraryId id);
@@ -58,5 +60,4 @@ namespace lms::core::stringUtils
template<>
std::optional<db::TrackListId> readAs(std::string_view str);
}
} // namespace lms::core::stringUtils
+117 -121
View File
@@ -22,23 +22,29 @@
#include <atomic>
#include <unordered_map>
#include "services/auth/IPasswordService.hpp"
#include "services/auth/IEnvService.hpp"
#include "database/Db.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
#include "core/EnumSet.hpp"
#include "core/LiteralString.hpp"
#include "core/IConfig.hpp"
#include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp"
#include "core/LiteralString.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "core/Utils.hpp"
#include "database/Db.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
#include "services/auth/IEnvService.hpp"
#include "services/auth/IPasswordService.hpp"
#include "ParameterParsing.hpp"
#include "ProtocolVersion.hpp"
#include "RequestContext.hpp"
#include "SubsonicId.hpp"
#include "SubsonicResponse.hpp"
#include "Utils.hpp"
#include "entrypoints/AlbumSongLists.hpp"
#include "entrypoints/Browsing.hpp"
#include "entrypoints/Bookmarks.hpp"
#include "entrypoints/Browsing.hpp"
#include "entrypoints/MediaAnnotation.hpp"
#include "entrypoints/MediaLibraryScanning.hpp"
#include "entrypoints/MediaRetrieval.hpp"
@@ -46,12 +52,6 @@
#include "entrypoints/Searching.hpp"
#include "entrypoints/System.hpp"
#include "entrypoints/UserManagement.hpp"
#include "ParameterParsing.hpp"
#include "ProtocolVersion.hpp"
#include "RequestContext.hpp"
#include "SubsonicId.hpp"
#include "SubsonicResponse.hpp"
#include "Utils.hpp"
namespace lms::api::subsonic
{
@@ -67,10 +67,10 @@ namespace lms::api::subsonic
std::unordered_map<std::string, ProtocolVersion> res;
core::Service<core::IConfig>::get()->visitStrings("api-subsonic-old-server-protocol-clients",
[&](std::string_view client)
{
[&](std::string_view client) {
res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 });
}, { "DSub" });
},
{ "DSub" });
return res;
}
@@ -80,10 +80,10 @@ namespace lms::api::subsonic
std::unordered_set<std::string> res;
core::Service<core::IConfig>::get()->visitStrings("api-open-subsonic-disabled-clients",
[&](std::string_view client)
{
[&](std::string_view client) {
res.emplace(std::string{ client });
}, { "DSub" });
},
{ "DSub" });
return res;
}
@@ -93,23 +93,22 @@ namespace lms::api::subsonic
std::unordered_set<std::string> res;
core::Service<core::IConfig>::get()->visitStrings("api-subsonic-default-cover-clients",
[&](std::string_view client)
{
[&](std::string_view client) {
res.emplace(std::string{ client });
}, { "DSub", "substreamer" });
},
{ "DSub", "substreamer" });
return res;
}
std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap)
{
auto censorValue = [](const std::string& type, const std::string& value) -> std::string
{
if (type == "p" || type == "password")
return "*REDACTED*";
else
return value;
};
auto censorValue = [](const std::string& type, const std::string& value) -> std::string {
if (type == "p" || type == "password")
return "*REDACTED*";
else
return value;
};
std::string res;
@@ -148,124 +147,122 @@ namespace lms::api::subsonic
using CheckImplementedFunc = std::function<void()>;
struct RequestEntryPointInfo
{
RequestHandlerFunc func;
core::EnumSet<db::UserType> allowedUserTypes{ db::UserType::DEMO, db::UserType::REGULAR, db::UserType::ADMIN };
CheckImplementedFunc checkFunc{};
RequestHandlerFunc func;
core::EnumSet<db::UserType> allowedUserTypes{ db::UserType::DEMO, db::UserType::REGULAR, db::UserType::ADMIN };
CheckImplementedFunc checkFunc{};
};
const std::unordered_map<core::LiteralString, RequestEntryPointInfo, core::LiteralStringHash, core::LiteralStringEqual> requestEntryPoints
{
const std::unordered_map<core::LiteralString, RequestEntryPointInfo, core::LiteralStringHash, core::LiteralStringEqual> requestEntryPoints{
// System
{"/ping", {handlePingRequest}},
{"/getLicense", {handleGetLicenseRequest}},
{"/getOpenSubsonicExtensions", {handleGetOpenSubsonicExtensions}},
{ "/ping", { handlePingRequest } },
{ "/getLicense", { handleGetLicenseRequest } },
{ "/getOpenSubsonicExtensions", { handleGetOpenSubsonicExtensions } },
// Browsing
{"/getMusicFolders", {handleGetMusicFoldersRequest}},
{"/getIndexes", {handleGetIndexesRequest}},
{"/getMusicDirectory", {handleGetMusicDirectoryRequest}},
{"/getGenres", {handleGetGenresRequest}},
{"/getArtists", {handleGetArtistsRequest}},
{"/getArtist", {handleGetArtistRequest}},
{"/getAlbum", {handleGetAlbumRequest}},
{"/getSong", {handleGetSongRequest}},
{"/getVideos", {handleNotImplemented}},
{"/getArtistInfo", {handleGetArtistInfoRequest}},
{"/getArtistInfo2", {handleGetArtistInfo2Request}},
{"/getAlbumInfo", {handleNotImplemented}},
{"/getAlbumInfo2", {handleNotImplemented}},
{"/getSimilarSongs", {handleGetSimilarSongsRequest}},
{"/getSimilarSongs2", {handleGetSimilarSongs2Request}},
{"/getTopSongs", {handleGetTopSongs}},
{ "/getMusicFolders", { handleGetMusicFoldersRequest } },
{ "/getIndexes", { handleGetIndexesRequest } },
{ "/getMusicDirectory", { handleGetMusicDirectoryRequest } },
{ "/getGenres", { handleGetGenresRequest } },
{ "/getArtists", { handleGetArtistsRequest } },
{ "/getArtist", { handleGetArtistRequest } },
{ "/getAlbum", { handleGetAlbumRequest } },
{ "/getSong", { handleGetSongRequest } },
{ "/getVideos", { handleNotImplemented } },
{ "/getArtistInfo", { handleGetArtistInfoRequest } },
{ "/getArtistInfo2", { handleGetArtistInfo2Request } },
{ "/getAlbumInfo", { handleNotImplemented } },
{ "/getAlbumInfo2", { handleNotImplemented } },
{ "/getSimilarSongs", { handleGetSimilarSongsRequest } },
{ "/getSimilarSongs2", { handleGetSimilarSongs2Request } },
{ "/getTopSongs", { handleGetTopSongs } },
// Album/song lists
{"/getAlbumList", {handleGetAlbumListRequest}},
{"/getAlbumList2", {handleGetAlbumList2Request}},
{"/getRandomSongs", {handleGetRandomSongsRequest}},
{"/getSongsByGenre", {handleGetSongsByGenreRequest}},
{"/getNowPlaying", {handleNotImplemented}},
{"/getStarred", {handleGetStarredRequest}},
{"/getStarred2", {handleGetStarred2Request}},
{ "/getAlbumList", { handleGetAlbumListRequest } },
{ "/getAlbumList2", { handleGetAlbumList2Request } },
{ "/getRandomSongs", { handleGetRandomSongsRequest } },
{ "/getSongsByGenre", { handleGetSongsByGenreRequest } },
{ "/getNowPlaying", { handleNotImplemented } },
{ "/getStarred", { handleGetStarredRequest } },
{ "/getStarred2", { handleGetStarred2Request } },
// Searching
{"/search", {handleNotImplemented}},
{"/search2", {handleSearch2Request}},
{"/search3", {handleSearch3Request}},
{ "/search", { handleNotImplemented } },
{ "/search2", { handleSearch2Request } },
{ "/search3", { handleSearch3Request } },
// Playlists
{"/getPlaylists", {handleGetPlaylistsRequest}},
{"/getPlaylist", {handleGetPlaylistRequest}},
{"/createPlaylist", {handleCreatePlaylistRequest}},
{"/updatePlaylist", {handleUpdatePlaylistRequest}},
{"/deletePlaylist", {handleDeletePlaylistRequest}},
{ "/getPlaylists", { handleGetPlaylistsRequest } },
{ "/getPlaylist", { handleGetPlaylistRequest } },
{ "/createPlaylist", { handleCreatePlaylistRequest } },
{ "/updatePlaylist", { handleUpdatePlaylistRequest } },
{ "/deletePlaylist", { handleDeletePlaylistRequest } },
// Media retrieval
{"/hls", {handleNotImplemented}},
{"/getCaptions", {handleNotImplemented}},
{"/getLyrics", {handleNotImplemented}},
{"/getAvatar", {handleNotImplemented}},
{ "/hls", { handleNotImplemented } },
{ "/getCaptions", { handleNotImplemented } },
{ "/getLyrics", { handleNotImplemented } },
{ "/getAvatar", { handleNotImplemented } },
// Media annotation
{"/star", {handleStarRequest}},
{"/unstar", {handleUnstarRequest}},
{"/setRating", {handleNotImplemented}},
{"/scrobble", {handleScrobble}},
{ "/star", { handleStarRequest } },
{ "/unstar", { handleUnstarRequest } },
{ "/setRating", { handleNotImplemented } },
{ "/scrobble", { handleScrobble } },
// Sharing
{"/getShares", {handleNotImplemented}},
{"/createShares", {handleNotImplemented}},
{"/updateShare", {handleNotImplemented}},
{"/deleteShare", {handleNotImplemented}},
{ "/getShares", { handleNotImplemented } },
{ "/createShares", { handleNotImplemented } },
{ "/updateShare", { handleNotImplemented } },
{ "/deleteShare", { handleNotImplemented } },
// Podcast
{"/getPodcasts", {handleNotImplemented}},
{"/getNewestPodcasts", {handleNotImplemented}},
{"/refreshPodcasts", {handleNotImplemented}},
{"/createPodcastChannel", {handleNotImplemented}},
{"/deletePodcastChannel", {handleNotImplemented}},
{"/deletePodcastEpisode", {handleNotImplemented}},
{"/downloadPodcastEpisode", {handleNotImplemented}},
{ "/getPodcasts", { handleNotImplemented } },
{ "/getNewestPodcasts", { handleNotImplemented } },
{ "/refreshPodcasts", { handleNotImplemented } },
{ "/createPodcastChannel", { handleNotImplemented } },
{ "/deletePodcastChannel", { handleNotImplemented } },
{ "/deletePodcastEpisode", { handleNotImplemented } },
{ "/downloadPodcastEpisode", { handleNotImplemented } },
// Jukebox
{"/jukeboxControl", {handleNotImplemented}},
{ "/jukeboxControl", { handleNotImplemented } },
// Internet radio
{"/getInternetRadioStations", {handleNotImplemented}},
{"/createInternetRadioStation", {handleNotImplemented}},
{"/updateInternetRadioStation", {handleNotImplemented}},
{"/deleteInternetRadioStation", {handleNotImplemented}},
{ "/getInternetRadioStations", { handleNotImplemented } },
{ "/createInternetRadioStation", { handleNotImplemented } },
{ "/updateInternetRadioStation", { handleNotImplemented } },
{ "/deleteInternetRadioStation", { handleNotImplemented } },
// Chat
{"/getChatMessages", {handleNotImplemented}},
{"/addChatMessages", {handleNotImplemented}},
{ "/getChatMessages", { handleNotImplemented } },
{ "/addChatMessages", { handleNotImplemented } },
// User management
{"/getUser", {handleGetUserRequest}},
{"/getUsers", {handleGetUsersRequest, {db::UserType::ADMIN}}},
{"/createUser", {handleCreateUserRequest, {db::UserType::ADMIN}, &utils::checkSetPasswordImplemented}},
{"/updateUser", {handleUpdateUserRequest, {db::UserType::ADMIN}}},
{"/deleteUser", {handleDeleteUserRequest, {db::UserType::ADMIN}}},
{"/changePassword", {handleChangePassword, {db::UserType::REGULAR, db::UserType::ADMIN}, &utils::checkSetPasswordImplemented}},
{ "/getUser", { handleGetUserRequest } },
{ "/getUsers", { handleGetUsersRequest, { db::UserType::ADMIN } } },
{ "/createUser", { handleCreateUserRequest, { db::UserType::ADMIN }, &utils::checkSetPasswordImplemented } },
{ "/updateUser", { handleUpdateUserRequest, { db::UserType::ADMIN } } },
{ "/deleteUser", { handleDeleteUserRequest, { db::UserType::ADMIN } } },
{ "/changePassword", { handleChangePassword, { db::UserType::REGULAR, db::UserType::ADMIN }, &utils::checkSetPasswordImplemented } },
// Bookmarks
{"/getBookmarks", {handleGetBookmarks}},
{"/createBookmark", {handleCreateBookmark}},
{"/deleteBookmark", {handleDeleteBookmark}},
{"/getPlayQueue", {handleNotImplemented}},
{"/savePlayQueue", {handleNotImplemented}},
{ "/getBookmarks", { handleGetBookmarks } },
{ "/createBookmark", { handleCreateBookmark } },
{ "/deleteBookmark", { handleDeleteBookmark } },
{ "/getPlayQueue", { handleNotImplemented } },
{ "/savePlayQueue", { handleNotImplemented } },
// Media library scanning
{"/getScanStatus", {Scan::handleGetScanStatus, {db::UserType::ADMIN}}},
{"/startScan", {Scan::handleStartScan, {db::UserType::ADMIN}}},
{ "/getScanStatus", { Scan::handleGetScanStatus, { db::UserType::ADMIN } } },
{ "/startScan", { Scan::handleStartScan, { db::UserType::ADMIN } } },
};
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
const std::unordered_map<core::LiteralString, MediaRetrievalHandlerFunc, core::LiteralStringHash, core::LiteralStringEqual> mediaRetrievalHandlers
{
const std::unordered_map<core::LiteralString, MediaRetrievalHandlerFunc, core::LiteralStringHash, core::LiteralStringEqual> mediaRetrievalHandlers{
// Media retrieval
{"/download", handleDownload},
{"/stream", handleStream},
{"/getCoverArt", handleGetCoverArt},
{ "/download", handleDownload },
{ "/stream", handleStream },
{ "/getCoverArt", handleGetCoverArt },
};
struct TLSMonotonicMemoryResourceCleaner
@@ -280,7 +277,7 @@ namespace lms::api::subsonic
TLSMonotonicMemoryResourceCleaner(const TLSMonotonicMemoryResourceCleaner&) = delete;
TLSMonotonicMemoryResourceCleaner& operator=(const TLSMonotonicMemoryResourceCleaner&) = delete;
};
}
} // namespace
SubsonicResource::SubsonicResource(db::Db& db)
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
@@ -294,8 +291,8 @@ namespace lms::api::subsonic
{
static std::atomic<std::size_t> curRequestId{};
const std::size_t requestId{ curRequestId++ };
TLSMonotonicMemoryResourceCleaner memoryResourceCleaner;
const std::size_t requestId{ curRequestId++ };
TLSMonotonicMemoryResourceCleaner memoryResourceCleaner;
LMS_LOG(API_SUBSONIC, DEBUG, "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap()));
@@ -327,7 +324,7 @@ namespace lms::api::subsonic
const Response resp{ [&] {
LMS_SCOPED_TRACE_DETAILED("Subsonic", "HandleRequest");
return itEntryPoint->second.func(requestContext);
}()};
}() };
{
LMS_SCOPED_TRACE_DETAILED("Subsonic", "WriteResponse");
@@ -356,8 +353,8 @@ namespace lms::api::subsonic
catch (const Error& e)
{
LMS_LOG(API_SUBSONIC, ERROR, "Error while processing request '" << requestPath << "'"
<< ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]"
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'");
<< ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]"
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'");
Response resp{ Response::createFailedResponse(protocolVersion, e) };
resp.write(response.out(), format);
response.setMimeType(std::string{ ResponseFormatToMimeType(format) });
@@ -443,7 +440,7 @@ namespace lms::api::subsonic
return user->getId();
}
if (auto * authEnvService{ core::Service<auth::IEnvService>::get() })
if (auto* authEnvService{ core::Service<auth::IEnvService>::get() })
{
const auto checkResult{ authEnvService->processRequest(request) };
if (checkResult.state != auth::IEnvService::CheckResult::State::Granted)
@@ -451,7 +448,7 @@ namespace lms::api::subsonic
return *checkResult.userId;
}
else if (auto * authPasswordService{ core::Service<auth::IPasswordService>::get() })
else if (auto* authPasswordService{ core::Service<auth::IPasswordService>::get() })
{
const auto checkResult{ authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), clientInfo.user, clientInfo.password) };
@@ -471,4 +468,3 @@ namespace lms::api::subsonic
}
} // namespace lms::api::subsonic
+17 -18
View File
@@ -19,14 +19,15 @@
#pragma once
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <unordered_set>
#include <Wt/WResource.h>
#include <Wt/Http/Response.h>
#include <Wt/WResource.h>
#include "database/Types.hpp"
#include "database/UserId.hpp"
#include "ClientInfo.hpp"
#include "RequestContext.hpp"
@@ -37,26 +38,24 @@ namespace lms::db
namespace lms::api::subsonic
{
class SubsonicResource final : public Wt::WResource
{
public:
SubsonicResource(db::Db& db);
public:
SubsonicResource(db::Db& db);
private:
void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override;
ProtocolVersion getServerProtocolVersion(const std::string& clientName) const;
private:
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
ProtocolVersion getServerProtocolVersion(const std::string& clientName) const;
static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server);
ClientInfo getClientInfo(const Wt::Http::Request& request);
RequestContext buildRequestContext(const Wt::Http::Request& request);
db::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo);
static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server);
ClientInfo getClientInfo(const Wt::Http::Request& request);
RequestContext buildRequestContext(const Wt::Http::Request& request);
db::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo);
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
const std::unordered_set<std::string> _openSubsonicDisabledClients;
const std::unordered_set<std::string> _defaultCoverClients;
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
const std::unordered_set<std::string> _openSubsonicDisabledClients;
const std::unordered_set<std::string> _defaultCoverClients;
db::Db& _db;
db::Db& _db;
};
} // namespace
} // namespace lms::api::subsonic
+56 -54
View File
@@ -20,12 +20,14 @@
#include "SubsonicResponse.hpp"
#include <cassert>
#include <cmath>
#include <climits>
#include <cmath>
#include <boost/property_tree/xml_parser.hpp>
#include "core/Exception.hpp"
#include "core/String.hpp"
#include "ProtocolVersion.hpp"
namespace lms::api::subsonic
@@ -34,8 +36,10 @@ namespace lms::api::subsonic
{
switch (format)
{
case ResponseFormat::xml: return "text/xml";
case ResponseFormat::json: return "application/json";
case ResponseFormat::xml:
return "text/xml";
case ResponseFormat::json:
return "application/json";
}
return "";
@@ -92,7 +96,7 @@ namespace lms::api::subsonic
assert(!_children.contains(key));
auto& values{ _childrenValues[key] };
values.emplace_back(string{ value });
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) {return value.index() == values.front().index();}));
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) { return value.index() == values.front().index(); }));
}
void Response::Node::addArrayValue(Key key, long long value)
@@ -100,7 +104,7 @@ namespace lms::api::subsonic
assert(!_value);
auto& values{ _childrenValues[key] };
values.emplace_back(value);
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) {return value.index() == values.front().index();}));
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) { return value.index() == values.front().index(); }));
}
Response::Node& Response::Node::createChild(Key key)
@@ -186,60 +190,58 @@ namespace lms::api::subsonic
void Response::writeXML(std::ostream& os) const
{
std::function<boost::property_tree::ptree(const Node&)> nodeToPropertyTree = [&](const Node& node)
std::function<boost::property_tree::ptree(const Node&)> nodeToPropertyTree = [&](const Node& node) {
boost::property_tree::ptree res;
for (const auto& [key, value] : node._attributes)
{
if (std::holds_alternative<Node::string>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<Node::string>(value));
else if (std::holds_alternative<bool>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<bool>(value));
else if (std::holds_alternative<float>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<float>(value));
else if (std::holds_alternative<long long>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<long long>(value));
}
auto valueToPropertyTree = [](const Node::ValueType& value) {
boost::property_tree::ptree res;
for (const auto& [key, value] : node._attributes)
{
if (std::holds_alternative<Node::string>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<Node::string>(value));
else if (std::holds_alternative<bool>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<bool>(value));
else if (std::holds_alternative<float>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<float>(value));
else if (std::holds_alternative<long long>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<long long>(value));
}
auto valueToPropertyTree = [](const Node::ValueType& value)
{
boost::property_tree::ptree res;
std::visit([&](const auto& rawValue)
{
res.put_value(rawValue);
}, value);
return res;
};
if (node._value)
{
res = valueToPropertyTree(*node._value);
}
else
{
for (const auto& [key, childNode] : node._children)
{
res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode));
}
for (const auto& [key, childArrayNodes] : node._childrenArrays)
{
for (const Node& childNode : childArrayNodes)
res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode));
}
for (const auto& [key, childArrayValues] : node._childrenValues)
{
for (const Response::Node::ValueType& value : childArrayValues)
res.add_child(std::string{ key.str() }, valueToPropertyTree(value));
}
}
std::visit([&](const auto& rawValue) {
res.put_value(rawValue);
},
value);
return res;
};
if (node._value)
{
res = valueToPropertyTree(*node._value);
}
else
{
for (const auto& [key, childNode] : node._children)
{
res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode));
}
for (const auto& [key, childArrayNodes] : node._childrenArrays)
{
for (const Node& childNode : childArrayNodes)
res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode));
}
for (const auto& [key, childArrayValues] : node._childrenValues)
{
for (const Response::Node::ValueType& value : childArrayValues)
res.add_child(std::string{ key.str() }, valueToPropertyTree(value));
}
}
return res;
};
const boost::property_tree::ptree root{ nodeToPropertyTree(_root) };
boost::property_tree::write_xml(os, root);
}
@@ -376,4 +378,4 @@ namespace lms::api::subsonic
serializer.serializeNode(os, _root);
}
} // namespace
} // namespace lms::api::subsonic
+39 -19
View File
@@ -26,6 +26,7 @@
#include <vector>
#include "core/LiteralString.hpp"
#include "RequestContext.hpp"
#include "SubsonicResponseAllocator.hpp"
@@ -57,7 +58,8 @@ namespace lms::api::subsonic
RequestedDataNotFound = 70,
};
Error(Code code) : _code{ code } {}
Error(Code code)
: _code{ code } {}
virtual std::string getMessage() const = 0;
@@ -70,7 +72,8 @@ namespace lms::api::subsonic
class GenericError : public Error
{
public:
GenericError() : Error{ Code::Generic } {}
GenericError()
: Error{ Code::Generic } {}
};
class RequiredParameterMissingError : public Error
@@ -79,7 +82,8 @@ namespace lms::api::subsonic
RequiredParameterMissingError(std::string_view param)
: Error{ Code::RequiredParameterMissing }
, _param{ param }
{}
{
}
private:
std::string getMessage() const override { return "Required parameter '" + _param + "' is missing."; }
@@ -89,7 +93,9 @@ namespace lms::api::subsonic
class ClientMustUpgradeError : public Error
{
public:
ClientMustUpgradeError() : Error{ Code::ClientMustUpgrade } {}
ClientMustUpgradeError()
: Error{ Code::ClientMustUpgrade } {}
private:
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; }
};
@@ -97,7 +103,9 @@ namespace lms::api::subsonic
class ServerMustUpgradeError : public Error
{
public:
ServerMustUpgradeError() : Error{ Code::ServerMustUpgrade } {}
ServerMustUpgradeError()
: Error{ Code::ServerMustUpgrade } {}
private:
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; }
};
@@ -105,7 +113,9 @@ namespace lms::api::subsonic
class WrongUsernameOrPasswordError : public Error
{
public:
WrongUsernameOrPasswordError() : Error{ Code::WrongUsernameOrPassword } {}
WrongUsernameOrPasswordError()
: Error{ Code::WrongUsernameOrPassword } {}
private:
std::string getMessage() const override { return "Wrong username or password."; }
};
@@ -113,7 +123,9 @@ namespace lms::api::subsonic
class TokenAuthenticationNotSupportedForLDAPUsersError : public Error
{
public:
TokenAuthenticationNotSupportedForLDAPUsersError() : Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {}
TokenAuthenticationNotSupportedForLDAPUsersError()
: Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {}
private:
std::string getMessage() const override { return "Token authentication not supported for LDAP users."; }
};
@@ -121,7 +133,9 @@ namespace lms::api::subsonic
class UserNotAuthorizedError : public Error
{
public:
UserNotAuthorizedError() : Error{ Code::UserNotAuthorized } {}
UserNotAuthorizedError()
: Error{ Code::UserNotAuthorized } {}
private:
std::string getMessage() const override { return "User is not authorized for the given operation."; }
};
@@ -129,7 +143,9 @@ namespace lms::api::subsonic
class RequestedDataNotFoundError : public Error
{
public:
RequestedDataNotFoundError() : Error{ Code::RequestedDataNotFound } {}
RequestedDataNotFoundError()
: Error{ Code::RequestedDataNotFound } {}
private:
std::string getMessage() const override { return "The requested data was not found."; }
};
@@ -137,7 +153,9 @@ namespace lms::api::subsonic
class InternalErrorGenericError : public GenericError
{
public:
InternalErrorGenericError(const std::string& message) : _message{ message } {}
InternalErrorGenericError(const std::string& message)
: _message{ message } {}
private:
std::string getMessage() const override { return "Internal error: " + _message; }
const std::string _message;
@@ -181,7 +199,8 @@ namespace lms::api::subsonic
class BadParameterGenericError : public GenericError
{
public:
BadParameterGenericError(const std::string& parameterName) : _parameterName{ parameterName } {}
BadParameterGenericError(const std::string& parameterName)
: _parameterName{ parameterName } {}
private:
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; }
@@ -192,7 +211,9 @@ namespace lms::api::subsonic
class ParameterValueTooHighGenericError : public GenericError
{
public:
ParameterValueTooHighGenericError(std::string_view parameterName, std::size_t max) : _parameterName{ parameterName }, _max{ max } {}
ParameterValueTooHighGenericError(std::string_view parameterName, std::size_t max)
: _parameterName{ parameterName }
, _max{ max } {}
private:
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value (max is " + std::to_string(_max) + ")"; }
@@ -211,7 +232,7 @@ namespace lms::api::subsonic
void setAttribute(Key key, std::string_view value);
template <typename T, std::enable_if_t<std::is_arithmetic<T>::value>* = nullptr>
template<typename T, std::enable_if_t<std::is_arithmetic<T>::value>* = nullptr>
void setAttribute(Key key, T value)
{
if constexpr (std::is_same<bool, T>::value)
@@ -242,10 +263,10 @@ namespace lms::api::subsonic
friend class Response;
template <typename Key, typename Value>
using map = std::map< Key, Value, std::less<Key>, ResponseAllocator<std::pair<const Key, Value>>>;
template<typename Key, typename Value>
using map = std::map<Key, Value, std::less<Key>, ResponseAllocator<std::pair<const Key, Value>>>;
template <typename T>
template<typename T>
using vector = std::vector<T, ResponseAllocator<T>>;
using string = std::basic_string<char, std::char_traits<char>, ResponseAllocator<char>>;
@@ -280,7 +301,7 @@ namespace lms::api::subsonic
class JsonSerializer
{
public:
public:
void serializeNode(std::ostream& os, const Node& node);
void serializeValue(std::ostream& os, const Node::ValueType& value);
void serializeEscapedString(std::ostream&, std::string_view str);
@@ -293,5 +314,4 @@ namespace lms::api::subsonic
Node _root;
};
} // namespace
} // namespace lms::api::subsonic
@@ -23,7 +23,7 @@
namespace lms::api::subsonic
{
// Stateless allocator that uses a shared MemoryResource
template <typename MemoryResource, typename T>
template<typename MemoryResource, typename T>
class Allocator
{
public:
@@ -37,10 +37,12 @@ namespace lms::api::subsonic
constexpr Allocator() noexcept = default;
template <typename U>
constexpr Allocator(const Allocator<MemoryResource, U>&) noexcept {}
template<typename U>
constexpr Allocator(const Allocator<MemoryResource, U>&) noexcept
{
}
template <typename V>
template<typename V>
struct rebind
{
using other = Allocator<MemoryResource, V>;
@@ -59,17 +61,17 @@ namespace lms::api::subsonic
};
template<class MemoryResource, class T, class U>
bool operator==(const Allocator <MemoryResource, T>&, const Allocator <MemoryResource, U>&)
bool operator==(const Allocator<MemoryResource, T>&, const Allocator<MemoryResource, U>&)
{
return true;
}
template<class MemoryResource, class T, class U>
bool operator!=(const Allocator <MemoryResource, T>&, const Allocator <MemoryResource, U>&)
bool operator!=(const Allocator<MemoryResource, T>&, const Allocator<MemoryResource, U>&)
{
return false;
}
template <typename T>
template<typename T>
using ResponseAllocator = Allocator<TLSMonotonicMemoryResource, T>;
}
} // namespace lms::api::subsonic
@@ -18,10 +18,10 @@
*/
#pragma once
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <array>
#include <list>
namespace lms::api::subsonic
@@ -55,7 +55,7 @@ namespace lms::api::subsonic
throw std::bad_alloc{};
assert(currentAddrAligned >= &_currentBlock->front());
std::byte* res{ currentAddrAligned };
_currentAddr = currentAddrAligned + byteCount;
@@ -100,4 +100,4 @@ namespace lms::api::subsonic
BlockType* _currentBlock{};
std::byte* _currentAddr{};
};
}
} // namespace lms::api::subsonic
+2 -1
View File
@@ -22,6 +22,7 @@
#include "core/Service.hpp"
#include "core/String.hpp"
#include "services/auth/IPasswordService.hpp"
#include "SubsonicResponse.hpp"
namespace lms::api::subsonic::utils
@@ -38,4 +39,4 @@ namespace lms::api::subsonic::utils
return core::stringUtils::replaceInString(name, "/", "_");
}
}
} // namespace lms::api::subsonic::utils
+1 -1
View File
@@ -26,4 +26,4 @@ namespace lms::api::subsonic::utils
{
void checkSetPasswordImplemented();
std::string makeNameFilesystemCompatible(std::string_view name);
}
} // namespace lms::api::subsonic::utils
@@ -19,6 +19,7 @@
#include "AlbumSongLists.hpp"
#include "core/Service.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Release.hpp"
@@ -27,12 +28,12 @@
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
#include "responses/Album.hpp"
#include "responses/Artist.hpp"
#include "responses/Song.hpp"
#include "core/Service.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
namespace lms::api::subsonic
{
@@ -245,10 +246,9 @@ namespace lms::api::subsonic
params.setRange(Range{ 0, size });
params.setMediaLibrary(mediaLibraryId);
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{
randomSongsNode.addArrayChild("song", createSongNode(context, track, context.user));
});
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
randomSongsNode.addArrayChild("song", createSongNode(context, track, context.user));
});
return response;
}
@@ -262,7 +262,7 @@ namespace lms::api::subsonic
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(10) };
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) };
@@ -284,10 +284,9 @@ namespace lms::api::subsonic
params.setRange(Range{ offset, count });
params.setMediaLibrary(mediaLibrary);
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{
songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.user));
});
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.user));
});
return response;
}
@@ -302,4 +301,4 @@ namespace lms::api::subsonic
return handleGetStarredRequestCommon(context, true /* id3 */);
}
}
} // namespace lms::api::subsonic
@@ -30,4 +30,4 @@ namespace lms::api::subsonic
Response handleGetSongsByGenreRequest(RequestContext& context);
Response handleGetStarredRequest(RequestContext& context);
Response handleGetStarred2Request(RequestContext& context);
}
} // namespace lms::api::subsonic
@@ -20,13 +20,14 @@
#include "Bookmarks.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
#include "database/Track.hpp"
#include "database/TrackBookmark.hpp"
#include "responses/Bookmark.hpp"
#include "responses/Song.hpp"
#include "database/User.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
#include "responses/Bookmark.hpp"
#include "responses/Song.hpp"
namespace lms::api::subsonic
{
@@ -92,4 +93,4 @@ namespace lms::api::subsonic
return Response::createOkResponse(context.serverProtocolVersion);
}
}
} // namespace lms::api::subsonic
@@ -27,4 +27,4 @@ namespace lms::api::subsonic
Response handleGetBookmarks(RequestContext& context);
Response handleCreateBookmark(RequestContext& context);
Response handleDeleteBookmark(RequestContext& context);
}
} // namespace lms::api::subsonic
+28 -31
View File
@@ -19,31 +19,32 @@
#include "Browsing.hpp"
#include "core/ILogger.hpp"
#include "core/Random.hpp"
#include "core/Service.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/MediaLibrary.hpp"
#include "database/Session.hpp"
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp"
#include "services/recommendation/IRecommendationService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "core/ILogger.hpp"
#include "core/Random.hpp"
#include "core/Service.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
#include "Utils.hpp"
#include "responses/Album.hpp"
#include "responses/Artist.hpp"
#include "responses/Genre.hpp"
#include "responses/Song.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
#include "Utils.hpp"
namespace lms::api::subsonic
{
using namespace db;
static const unsigned long long reportedDummyDateULong{ 946684800000ULL }; // 2000-01-01T00:00:00 UTC
static const unsigned long long reportedDummyDateULong{ 946684800000ULL }; // 2000-01-01T00:00:00 UTC
namespace
{
@@ -70,7 +71,7 @@ namespace lms::api::subsonic
artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->getAsString());
}
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() };
@@ -170,7 +171,7 @@ namespace lms::api::subsonic
{
// API says: "Returns a random collection of songs from the given artist and similar artists"
const std::size_t similarArtistCount{ count / 5 };
std::vector<ArtistId> artistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, similarArtistCount) };
std::vector<ArtistId> artistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, similarArtistCount) };
artistIds.push_back(artistId);
const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 };
@@ -265,7 +266,7 @@ namespace lms::api::subsonic
return response;
}
}
} // namespace
Response handleGetMusicFoldersRequest(RequestContext& context)
{
@@ -273,13 +274,12 @@ namespace lms::api::subsonic
Response::Node& musicFoldersNode{ response.createNode("musicFolders") };
auto transaction{ context.dbSession.createReadTransaction() };
MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library)
{
Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") };
MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library) {
Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") };
musicFolderNode.setAttribute("id", idToString(library->getId()));
musicFolderNode.setAttribute("name", library->getName());
});
musicFolderNode.setAttribute("id", idToString(library->getId()));
musicFolderNode.setAttribute("name", library->getName());
});
return response;
}
@@ -310,10 +310,9 @@ namespace lms::api::subsonic
directoryNode.setAttribute("name", "Music");
// TODO: this does not scale when a lot of artists are present
Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::SortName), [&](const Artist::pointer& artist)
{
directoryNode.addArrayChild("child", createArtistNode(context, artist, context.user, false /* no id3 */));
});
Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::SortName), [&](const Artist::pointer& artist) {
directoryNode.addArrayChild("child", createArtistNode(context, artist, context.user, false /* no id3 */));
});
}
else if (artistId)
{
@@ -325,10 +324,9 @@ namespace lms::api::subsonic
directoryNode.setAttribute("name", utils::makeNameFilesystemCompatible(artist->getName()));
Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release)
{
directoryNode.addArrayChild("child", createAlbumNode(context, release, context.user, false /* no id3 */));
});
Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release) {
directoryNode.addArrayChild("child", createAlbumNode(context, release, context.user, false /* no id3 */));
});
}
else if (releaseId)
{
@@ -340,10 +338,9 @@ namespace lms::api::subsonic
directoryNode.setAttribute("name", utils::makeNameFilesystemCompatible(release->getName()));
Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track)
{
directoryNode.addArrayChild("child", createSongNode(context, track, context.user));
});
Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track) {
directoryNode.addArrayChild("child", createSongNode(context, track, context.user));
});
}
else
throw BadParameterGenericError{ "id" };
@@ -390,7 +387,7 @@ namespace lms::api::subsonic
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node artistNode{ createArtistNode(context, artist, context.user, true /* id3 */) };
const auto releases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(artist->getId())) };
const auto releases{ Release::find(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
for (const Release::pointer& release : releases.results)
artistNode.addArrayChild("album", createAlbumNode(context, release, context.user, true /* id3 */));
@@ -490,4 +487,4 @@ namespace lms::api::subsonic
return response;
}
}
} // namespace lms::api::subsonic
@@ -37,4 +37,4 @@ namespace lms::api::subsonic
Response handleGetSimilarSongsRequest(RequestContext& context);
Response handleGetSimilarSongs2Request(RequestContext& context);
Response handleGetTopSongs(RequestContext& context);
}
} // namespace lms::api::subsonic
@@ -21,13 +21,14 @@
#include <vector>
#include "core/Service.hpp"
#include "database/ArtistId.hpp"
#include "database/ReleaseId.hpp"
#include "database/TrackId.hpp"
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "core/Service.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
@@ -55,7 +56,7 @@ namespace lms::api::subsonic
return res;
}
}
} // namespace
Response handleStarRequest(RequestContext& context)
{
@@ -119,11 +120,11 @@ namespace lms::api::subsonic
{
const TrackId trackId{ ids[i] };
const unsigned long time{ times[i] };
core::Service<scrobbling::IScrobblingService>::get()->addTimedListen({ {context.user->getId(), trackId}, Wt::WDateTime::fromTime_t(static_cast<std::time_t>(time / 1000)) });
core::Service<scrobbling::IScrobblingService>::get()->addTimedListen({ { context.user->getId(), trackId }, Wt::WDateTime::fromTime_t(static_cast<std::time_t>(time / 1000)) });
}
}
}
return Response::createOkResponse(context.serverProtocolVersion);
}
}
} // namespace lms::api::subsonic
@@ -27,4 +27,4 @@ namespace lms::api::subsonic
Response handleStarRequest(RequestContext& context);
Response handleUnstarRequest(RequestContext& context);
Response handleScrobble(RequestContext& context);
}
} // namespace lms::api::subsonic
@@ -19,8 +19,8 @@
#include "MediaLibraryScanning.hpp"
#include "services/scanner/IScannerService.hpp"
#include "core/Service.hpp"
#include "services/scanner/IScannerService.hpp"
namespace lms::api::subsonic::Scan
{
@@ -47,7 +47,7 @@ namespace lms::api::subsonic::Scan
return statusResponse;
}
}
} // namespace
Response handleGetScanStatus(RequestContext& context)
{
@@ -66,5 +66,4 @@ namespace lms::api::subsonic::Scan
return response;
}
}
} // namespace lms::api::subsonic::Scan
@@ -26,5 +26,4 @@ namespace lms::api::subsonic::Scan
{
Response handleGetScanStatus(RequestContext& context);
Response handleStartScan(RequestContext& context);
}
} // namespace lms::api::subsonic::Scan
@@ -24,15 +24,16 @@
#include "av/TranscodingParameters.hpp"
#include "av/TranscodingResourceHandlerCreator.hpp"
#include "av/Types.hpp"
#include "services/cover/ICoverService.hpp"
#include "core/FileResourceHandlerCreator.hpp"
#include "core/ILogger.hpp"
#include "core/IResourceHandler.hpp"
#include "core/String.hpp"
#include "core/Utils.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp"
#include "core/IResourceHandler.hpp"
#include "core/ILogger.hpp"
#include "core/FileResourceHandlerCreator.hpp"
#include "core/Utils.hpp"
#include "core/String.hpp"
#include "services/cover/ICoverService.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
@@ -40,15 +41,15 @@ namespace lms::api::subsonic
{
using namespace db;
namespace
namespace
{
std::optional<av::transcoding::OutputFormat> subsonicStreamFormatToAvOutputFormat(std::string_view format)
{
for (const auto& [str, avFormat] : std::initializer_list<std::pair<std::string_view, av::transcoding::OutputFormat>>{
{"mp3", av::transcoding::OutputFormat::MP3},
{"opus", av::transcoding::OutputFormat::OGG_OPUS},
{"vorbis", av::transcoding::OutputFormat::OGG_VORBIS},
})
{ "mp3", av::transcoding::OutputFormat::MP3 },
{ "opus", av::transcoding::OutputFormat::OGG_OPUS },
{ "vorbis", av::transcoding::OutputFormat::OGG_VORBIS },
})
{
if (core::stringUtils::stringCaseInsensitiveEqual(str, format))
return avFormat;
@@ -60,11 +61,16 @@ namespace lms::api::subsonic
{
switch (format)
{
case db::TranscodingOutputFormat::MP3: return av::transcoding::OutputFormat::MP3;
case db::TranscodingOutputFormat::OGG_OPUS: return av::transcoding::OutputFormat::OGG_OPUS;
case db::TranscodingOutputFormat::MATROSKA_OPUS: return av::transcoding::OutputFormat::MATROSKA_OPUS;
case db::TranscodingOutputFormat::OGG_VORBIS: return av::transcoding::OutputFormat::OGG_VORBIS;
case db::TranscodingOutputFormat::WEBM_VORBIS: return av::transcoding::OutputFormat::WEBM_VORBIS;
case db::TranscodingOutputFormat::MP3:
return av::transcoding::OutputFormat::MP3;
case db::TranscodingOutputFormat::OGG_OPUS:
return av::transcoding::OutputFormat::OGG_OPUS;
case db::TranscodingOutputFormat::MATROSKA_OPUS:
return av::transcoding::OutputFormat::MATROSKA_OPUS;
case db::TranscodingOutputFormat::OGG_VORBIS:
return av::transcoding::OutputFormat::OGG_VORBIS;
case db::TranscodingOutputFormat::WEBM_VORBIS:
return av::transcoding::OutputFormat::WEBM_VORBIS;
}
return av::transcoding::OutputFormat::OGG_OPUS;
}
@@ -147,7 +153,7 @@ namespace lms::api::subsonic
requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat());
}
if (!requestedFormat && (maxBitRate == 0 || track->getBitrate() <= maxBitRate ))
if (!requestedFormat && (maxBitRate == 0 || track->getBitrate() <= maxBitRate))
{
LMS_LOG(API_SUBSONIC, DEBUG, "File's bitrate is compatible with parameters => no transcoding");
return parameters; // no transcoding needed
@@ -166,7 +172,7 @@ namespace lms::api::subsonic
}
bitrate = maxBitRate;
}
if (!requestedFormat)
requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat());
if (!bitrate)
@@ -181,7 +187,7 @@ namespace lms::api::subsonic
return parameters;
}
}
} // namespace
void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response)
{
@@ -29,5 +29,4 @@ namespace lms::api::subsonic
void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response);
void handleStream(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response);
void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response);
}
} // namespace lms::api::subsonic
@@ -23,10 +23,11 @@
#include "database/Track.hpp"
#include "database/TrackList.hpp"
#include "database/User.hpp"
#include "responses/Playlist.hpp"
#include "responses/Song.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
#include "responses/Playlist.hpp"
#include "responses/Song.hpp"
namespace lms::api::subsonic
{
@@ -200,4 +201,4 @@ namespace lms::api::subsonic
return Response::createOkResponse(context.serverProtocolVersion);
}
}
} // namespace lms::api::subsonic
@@ -29,4 +29,4 @@ namespace lms::api::subsonic
Response handleCreatePlaylistRequest(RequestContext& context);
Response handleUpdatePlaylistRequest(RequestContext& context);
Response handleDeletePlaylistRequest(RequestContext& context);
}
} // namespace lms::api::subsonic
@@ -20,8 +20,8 @@
#include "Searching.hpp"
#include <chrono>
#include <mutex>
#include <map>
#include <mutex>
#include "core/Random.hpp"
#include "database/Artist.hpp"
@@ -29,11 +29,12 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
#include "responses/Album.hpp"
#include "responses/Artist.hpp"
#include "responses/Song.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
namespace lms::api::subsonic
{
@@ -44,7 +45,7 @@ namespace lms::api::subsonic
// Search endpoints can be used to scan/sync the database
// This class is used to keep track of the current scans, in order to retrieve the last objectId
// to speed up the query of the following range (avoid the 'offset' cost)
template <typename ObjectId>
template<typename ObjectId>
class ScanTracker
{
public:
@@ -71,7 +72,7 @@ namespace lms::api::subsonic
};
static constexpr std::size_t maxScanCount{ 50 };
static constexpr ClockType::duration maxEntryDuration{ std::chrono::seconds{30} };
static constexpr ClockType::duration maxEntryDuration{ std::chrono::seconds{ 30 } };
std::mutex _mutex;
std::map<ScanInfo, Entry> _ongoingScans;
@@ -126,20 +127,18 @@ namespace lms::api::subsonic
const std::size_t artistOffset{ getParameterAs<std::size_t>(context.parameters, "artistOffset").value_or(0) };
ArtistId lastRetrievedId;
auto findArtists{ [&]
{
Artist::FindParameters params;
params.setKeywords(keywords);
params.setRange(Range{ artistOffset, artistCount });
params.setMediaLibrary(mediaLibrary);
params.setSortMethod(ArtistSortMethod::Id); // must be consistent with both methods
auto findArtists{ [&] {
Artist::FindParameters params;
params.setKeywords(keywords);
params.setRange(Range{ artistOffset, artistCount });
params.setMediaLibrary(mediaLibrary);
params.setSortMethod(ArtistSortMethod::Id); // must be consistent with both methods
Artist::find(context.dbSession, params, [&](const Artist::pointer& artist)
{
searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3));
lastRetrievedId = artist->getId();
});
} };
Artist::find(context.dbSession, params, [&](const Artist::pointer& artist) {
searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3));
lastRetrievedId = artist->getId();
});
} };
if (!keywords.empty())
{
@@ -147,8 +146,7 @@ namespace lms::api::subsonic
}
else
{
ScanTracker<ArtistId>::ScanInfo scanInfo
{
ScanTracker<ArtistId>::ScanInfo scanInfo{
.clientAddress = context.clientInfo.ipAddress,
.clientName = context.clientInfo.name,
.userName = context.clientInfo.user,
@@ -158,10 +156,11 @@ namespace lms::api::subsonic
if (ArtistId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
{
Artist::find(context.dbSession, cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist)
{
Artist::find(
context.dbSession, cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist) {
searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3));
}, mediaLibrary);
},
mediaLibrary);
lastRetrievedId = cachedLastRetrievedId;
}
else
@@ -192,19 +191,17 @@ namespace lms::api::subsonic
ReleaseId lastRetrievedId;
auto findReleases{ [&]
{
auto findReleases{ [&] {
Release::FindParameters params;
params.setKeywords(keywords);
params.setRange(Range{ albumOffset, albumCount });
params.setMediaLibrary(mediaLibrary);
params.setSortMethod(ReleaseSortMethod::Id); // must be consistent with both methods
Release::find(context.dbSession, params, [&](const Release::pointer& release)
{
searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3));
lastRetrievedId = release->getId();
});
Release::find(context.dbSession, params, [&](const Release::pointer& release) {
searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3));
lastRetrievedId = release->getId();
});
} };
if (!keywords.empty())
@@ -213,8 +210,7 @@ namespace lms::api::subsonic
}
else
{
ScanTracker<ReleaseId>::ScanInfo scanInfo
{
ScanTracker<ReleaseId>::ScanInfo scanInfo{
.clientAddress = context.clientInfo.ipAddress,
.clientName = context.clientInfo.name,
.userName = context.clientInfo.user,
@@ -224,10 +220,11 @@ namespace lms::api::subsonic
if (ReleaseId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
{
Release::find(context.dbSession, cachedLastRetrievedId, albumCount, [&](const Release::pointer& release)
{
Release::find(
context.dbSession, cachedLastRetrievedId, albumCount, [&](const Release::pointer& release) {
searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3));
}, mediaLibrary);
},
mediaLibrary);
lastRetrievedId = cachedLastRetrievedId;
}
else
@@ -258,19 +255,17 @@ namespace lms::api::subsonic
TrackId lastRetrievedId;
auto findTracks{ [&]
{
auto findTracks{ [&] {
Track::FindParameters params;
params.setKeywords(keywords);
params.setRange(Range{ songOffset, songCount });
params.setMediaLibrary(mediaLibrary);
params.setSortMethod(TrackSortMethod::Id); // must be consistent with both methods
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{
searchResultNode.addArrayChild("song", createSongNode(context, track, user));
lastRetrievedId = track->getId();
});
Track::find(context.dbSession, params, [&](const Track::pointer& track) {
searchResultNode.addArrayChild("song", createSongNode(context, track, user));
lastRetrievedId = track->getId();
});
} };
if (!keywords.empty())
@@ -279,8 +274,7 @@ namespace lms::api::subsonic
}
else
{
ScanTracker<TrackId>::ScanInfo scanInfo
{
ScanTracker<TrackId>::ScanInfo scanInfo{
.clientAddress = context.clientInfo.ipAddress,
.clientName = context.clientInfo.name,
.userName = context.clientInfo.user,
@@ -290,10 +284,11 @@ namespace lms::api::subsonic
if (TrackId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid())
{
Track::find(context.dbSession, cachedLastRetrievedId, songCount, [&](const Track::pointer& track)
{
Track::find(
context.dbSession, cachedLastRetrievedId, songCount, [&](const Track::pointer& track) {
searchResultNode.addArrayChild("song", createSongNode(context, track, user));
}, mediaLibrary);
},
mediaLibrary);
lastRetrievedId = cachedLastRetrievedId;
}
else
@@ -308,7 +303,7 @@ namespace lms::api::subsonic
}
}
}
}
} // namespace
namespace
{
@@ -340,7 +335,7 @@ namespace lms::api::subsonic
return response;
}
}
} // namespace
Response handleSearch2Request(RequestContext& context)
{
@@ -351,4 +346,4 @@ namespace lms::api::subsonic
{
return handleSearchRequestCommon(context, true /* id3 */);
}
}
} // namespace lms::api::subsonic
@@ -26,4 +26,4 @@ namespace lms::api::subsonic
{
Response handleSearch2Request(RequestContext& context);
Response handleSearch3Request(RequestContext& context);
}
} // namespace lms::api::subsonic
@@ -37,4 +37,4 @@ namespace lms::api::subsonic
return response;
};
}
} // namespace lms::api::subsonic
@@ -27,4 +27,4 @@ namespace lms::api::subsonic
Response handlePingRequest(RequestContext& context);
Response handleGetLicenseRequest(RequestContext& context);
Response handleGetOpenSubsonicExtensions(RequestContext& context);
}
} // namespace lms::api::subsonic
@@ -1,24 +1,26 @@
#include "UserManagement.hpp"
#include "core/Service.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
#include "services/auth/IPasswordService.hpp"
#include "core/Service.hpp"
#include "responses/User.hpp"
#include "ParameterParsing.hpp"
#include "Utils.hpp"
#include "responses/User.hpp"
namespace lms::api::subsonic
{
using namespace db;
namespace {
namespace
{
void checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username)
{
if (context.user->getLoginName() != username && !context.user->isAdmin())
throw UserNotAuthorizedError{};
}
}
} // namespace
Response handleGetUserRequest(RequestContext& context)
{
@@ -44,10 +46,9 @@ namespace lms::api::subsonic
Response::Node& usersNode{ response.createNode("users") };
auto transaction{ context.dbSession.createReadTransaction() };
User::find(context.dbSession, User::FindParameters{}, [&](const User::pointer& user)
{
usersNode.addArrayChild("user", createUserNode(user));
});
User::find(context.dbSession, User::FindParameters{}, [&](const User::pointer& user) {
usersNode.addArrayChild("user", createUserNode(user));
});
return response;
}
@@ -70,13 +71,12 @@ namespace lms::api::subsonic
userId = user->getId();
}
auto removeCreatedUser{ [&]
{
auto transaction {context.dbSession.createWriteTransaction()};
User::pointer user{ User::find(context.dbSession, userId) };
if (user)
user.remove();
} };
auto removeCreatedUser{ [&] {
auto transaction{ context.dbSession.createWriteTransaction() };
User::pointer user{ User::find(context.dbSession, userId) };
if (user)
user.remove();
} };
try
{
@@ -198,4 +198,4 @@ namespace lms::api::subsonic
return Response::createOkResponse(context.serverProtocolVersion);
}
}
} // namespace lms::api::subsonic
@@ -30,4 +30,4 @@ namespace lms::api::subsonic
Response handleUpdateUserRequest(RequestContext& context);
Response handleDeleteUserRequest(RequestContext& context);
Response handleChangePassword(RequestContext& context);
}
} // namespace lms::api::subsonic
+18 -20
View File
@@ -19,21 +19,21 @@
#include "responses/Album.hpp"
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Release.hpp"
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "SubsonicId.hpp"
#include "responses/Artist.hpp"
#include "responses/DiscTitle.hpp"
#include "responses/ItemDate.hpp"
#include "responses/ItemGenre.hpp"
#include "SubsonicId.hpp"
namespace lms::api::subsonic
{
@@ -45,13 +45,14 @@ namespace lms::api::subsonic
Response::Node albumNode;
if (id3) {
if (id3)
{
albumNode.setAttribute("name", release->getName());
albumNode.setAttribute("songCount", release->getTrackCount());
albumNode.setAttribute(
"duration", std::chrono::duration_cast<std::chrono::seconds>(
release->getDuration())
.count());
release->getDuration())
.count());
}
else
{
@@ -97,7 +98,7 @@ namespace lms::api::subsonic
const ClusterType::pointer genreClusterType{ ClusterType::find(context.dbSession, "GENRE") };
if (genreClusterType)
{
auto clusters{ release->getClusterGroups({genreClusterType->getId()}, 1) };
auto clusters{ release->getClusterGroups({ genreClusterType->getId() }, 1) };
if (!clusters.empty() && !clusters.front().empty())
albumNode.setAttribute("genre", clusters.front().front()->getName());
}
@@ -124,19 +125,17 @@ namespace lms::api::subsonic
albumNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : "");
}
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName)
{
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) {
albumNode.createEmptyArrayValue(field);
Cluster::FindParameters params;
params.setRelease(release->getId());
params.setClusterTypeName(clusterTypeName);
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster)
{
albumNode.addArrayValue(field, cluster->getName());
});
}};
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) {
albumNode.addArrayValue(field, cluster->getName());
});
} };
addClusters("moods", "MOOD");
@@ -148,10 +147,9 @@ namespace lms::api::subsonic
params.setRelease(release->getId());
params.setClusterType(genreClusterType->getId());
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster)
{
albumNode.addArrayChild("genres", createItemGenreNode(cluster->getName()));
});
Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) {
albumNode.addArrayChild("genres", createItemGenreNode(cluster->getName()));
});
}
albumNode.createEmptyArrayChild("artists");
@@ -186,4 +184,4 @@ namespace lms::api::subsonic
return albumNode;
}
}
} // namespace lms::api::subsonic
+2 -1
View File
@@ -20,6 +20,7 @@
#pragma once
#include "database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db
@@ -27,7 +28,7 @@ namespace lms::db
class Release;
class User;
class Session;
}
} // namespace lms::db
namespace lms::api::subsonic
{
+29 -19
View File
@@ -19,14 +19,14 @@
#include "responses/Artist.hpp"
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "database/Artist.hpp"
#include "database/Release.hpp"
#include "database/TrackArtistLink.hpp"
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "SubsonicId.hpp"
@@ -45,8 +45,7 @@ namespace lms::api::subsonic
names.resize(artists.size());
std::transform(std::cbegin(artists), std::cend(artists), std::begin(names),
[](const Artist::pointer& artist)
{
[](const Artist::pointer& artist) {
return artist->getName();
});
@@ -57,22 +56,33 @@ namespace lms::api::subsonic
{
switch (type)
{
case TrackArtistLinkType::Arranger: return "arranger";
case TrackArtistLinkType::Artist: return "artist";
case TrackArtistLinkType::Composer: return "composer";
case TrackArtistLinkType::Conductor: return "conductor";
case TrackArtistLinkType::Lyricist: return "lyricist";
case TrackArtistLinkType::Mixer: return "mixer";
case TrackArtistLinkType::Performer: return "performer";
case TrackArtistLinkType::Producer: return "producer";
case TrackArtistLinkType::ReleaseArtist: return "albumartist";
case TrackArtistLinkType::Remixer: return "remixer";
case TrackArtistLinkType::Writer: return "writer";
case TrackArtistLinkType::Arranger:
return "arranger";
case TrackArtistLinkType::Artist:
return "artist";
case TrackArtistLinkType::Composer:
return "composer";
case TrackArtistLinkType::Conductor:
return "conductor";
case TrackArtistLinkType::Lyricist:
return "lyricist";
case TrackArtistLinkType::Mixer:
return "mixer";
case TrackArtistLinkType::Performer:
return "performer";
case TrackArtistLinkType::Producer:
return "producer";
case TrackArtistLinkType::ReleaseArtist:
return "albumartist";
case TrackArtistLinkType::Remixer:
return "remixer";
case TrackArtistLinkType::Writer:
return "writer";
}
return "unknown";
}
}
} // namespace utils
Response::Node createArtistNode(RequestContext& context, const Artist::pointer& artist, const User::pointer& user, bool id3)
{
@@ -86,7 +96,7 @@ namespace lms::api::subsonic
if (id3)
{
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters {}.setArtist(artist->getId())) };
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
artistNode.setAttribute("albumCount", count);
}
@@ -125,4 +135,4 @@ namespace lms::api::subsonic
return artistNode;
}
}
} // namespace lms::api::subsonic
+5 -3
View File
@@ -21,8 +21,10 @@
#include <string>
#include <vector>
#include "database/Object.hpp"
#include "database/Types.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db
@@ -30,7 +32,7 @@ namespace lms::db
class Artist;
class User;
class Session;
}
} // namespace lms::db
namespace lms::api::subsonic
{
@@ -38,7 +40,7 @@ namespace lms::api::subsonic
{
std::string joinArtistNames(const std::vector<db::ObjectPtr<db::Artist>>& artists);
std::string_view toString(db::TrackArtistLinkType type);
}
} // namespace utils
Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr<db::Artist>& artist, const db::ObjectPtr<db::User>& user, bool id3);
Response::Node createArtistNode(const db::ObjectPtr<db::Artist>& artist); // only minimal info
}
} // namespace lms::api::subsonic
@@ -39,4 +39,4 @@ namespace lms::api::subsonic
return trackBookmarkNode;
}
}
} // namespace lms::api::subsonic
@@ -20,6 +20,7 @@
#pragma once
#include "database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db
@@ -21,6 +21,7 @@
#include "database/Object.hpp"
#include "database/TrackArtistLink.hpp"
#include "SubsonicResponse.hpp"
#include "responses/Artist.hpp"
@@ -37,4 +38,4 @@ namespace lms::api::subsonic
return contributorNode;
}
}
} // namespace lms::api::subsonic
@@ -20,13 +20,14 @@
#pragma once
#include "database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db
{
class Artist;
class TrackArtistLink;
}
} // namespace lms::db
namespace lms::api::subsonic
{
@@ -30,4 +30,4 @@ namespace lms::api::subsonic
return discTitleNode;
}
}
} // namespace lms::api::subsonic
@@ -20,6 +20,7 @@
#pragma once
#include "database/Types.hpp"
#include "SubsonicResponse.hpp"
namespace lms::api::subsonic
+1 -1
View File
@@ -33,4 +33,4 @@ namespace lms::api::subsonic
return clusterNode;
}
}
} // namespace lms::api::subsonic
@@ -20,6 +20,7 @@
#pragma once
#include "database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db
@@ -40,4 +40,4 @@ namespace lms::api::subsonic
return itemDateNode;
}
}
} // namespace lms::api::subsonic
@@ -20,6 +20,7 @@
#pragma once
#include <Wt/WDate.h>
#include "SubsonicResponse.hpp"
namespace lms::api::subsonic
@@ -31,4 +31,4 @@ namespace lms::api::subsonic
return genreNode;
}
}
} // namespace lms::api::subsonic
@@ -20,6 +20,7 @@
#pragma once
#include <string_view>
#include "SubsonicResponse.hpp"
namespace lms::db
@@ -22,6 +22,7 @@
#include "database/Track.hpp"
#include "database/TrackList.hpp"
#include "database/User.hpp"
#include "SubsonicId.hpp"
namespace lms::api::subsonic
@@ -42,9 +43,9 @@ namespace lms::api::subsonic
playlistNode.setAttribute("created", reportedDummyDate);
playlistNode.setAttribute("owner", tracklist->getUser()->getLoginName());
if (const auto entry {tracklist->getEntry(0)})
if (const auto entry{ tracklist->getEntry(0) })
playlistNode.setAttribute("coverArt", idToString(entry->getTrack()->getId()));
return playlistNode;
}
}
} // namespace lms::api::subsonic
@@ -20,13 +20,14 @@
#pragma once
#include "database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db
{
class TrackList;
class Session;
}
} // namespace lms::db
namespace lms::api::subsonic
{
@@ -35,4 +35,4 @@ namespace lms::api::subsonic
return replayGainNode;
}
}
} // namespace lms::api::subsonic
@@ -20,6 +20,7 @@
#pragma once
#include "database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db
+29 -25
View File
@@ -22,6 +22,9 @@
#include <string_view>
#include "av/IAudioFile.hpp"
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Release.hpp"
@@ -30,15 +33,13 @@
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "SubsonicId.hpp"
#include "Utils.hpp"
#include "responses/Artist.hpp"
#include "responses/Contributor.hpp"
#include "responses/ItemGenre.hpp"
#include "responses/ReplayGain.hpp"
#include "SubsonicId.hpp"
#include "Utils.hpp"
namespace lms::api::subsonic
{
@@ -50,16 +51,21 @@ namespace lms::api::subsonic
{
switch (format)
{
case TranscodingOutputFormat::MP3: return "mp3";
case TranscodingOutputFormat::OGG_OPUS: return "opus";
case TranscodingOutputFormat::MATROSKA_OPUS: return "mka";
case TranscodingOutputFormat::OGG_VORBIS: return "ogg";
case TranscodingOutputFormat::WEBM_VORBIS: return "webm";
case TranscodingOutputFormat::MP3:
return "mp3";
case TranscodingOutputFormat::OGG_OPUS:
return "opus";
case TranscodingOutputFormat::MATROSKA_OPUS:
return "mka";
case TranscodingOutputFormat::OGG_VORBIS:
return "ogg";
case TranscodingOutputFormat::WEBM_VORBIS:
return "webm";
}
return "";
}
}
} // namespace
Response::Node createSongNode(RequestContext& context, const Track::pointer& track, const User::pointer& user)
{
@@ -94,7 +100,7 @@ namespace lms::api::subsonic
trackResponse.setAttribute("coverArt", idToString(track->getId()));
const std::vector<Artist::pointer>& artists{ track->getArtists({TrackArtistLinkType::Artist}) };
const std::vector<Artist::pointer>& artists{ track->getArtists({ TrackArtistLinkType::Artist }) };
if (!artists.empty())
{
if (!track->getArtistDisplayName().empty())
@@ -160,18 +166,17 @@ namespace lms::api::subsonic
trackResponse.createEmptyArrayChild("artists");
trackResponse.createEmptyArrayChild("contributors");
TrackArtistLink::find(context.dbSession, track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist)
{
TrackArtistLink::find(context.dbSession, track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) {
switch (link->getType())
{
case TrackArtistLinkType::Artist:
trackResponse.addArrayChild("artists", createArtistNode(artist));
break;
case TrackArtistLinkType::ReleaseArtist:
trackResponse.addArrayChild("albumartists", createArtistNode(artist));
break;
default:
trackResponse.addArrayChild("contributors", createContributorNode(link, artist));
case TrackArtistLinkType::Artist:
trackResponse.addArrayChild("artists", createArtistNode(artist));
break;
case TrackArtistLinkType::ReleaseArtist:
trackResponse.addArrayChild("albumartists", createArtistNode(artist));
break;
default:
trackResponse.addArrayChild("contributors", createContributorNode(link, artist));
}
});
}
@@ -180,8 +185,7 @@ namespace lms::api::subsonic
if (release)
trackResponse.setAttribute("displayAlbumArtist", release->getArtistDisplayName());
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName)
{
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) {
trackResponse.createEmptyArrayValue(field);
Cluster::FindParameters params;
@@ -203,4 +207,4 @@ namespace lms::api::subsonic
return trackResponse;
}
}
} // namespace lms::api::subsonic
+2 -1
View File
@@ -20,6 +20,7 @@
#pragma once
#include "database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db
@@ -27,7 +28,7 @@ namespace lms::db
class Track;
class User;
class Session;
}
} // namespace lms::db
namespace lms::api::subsonic
{
+1 -1
View File
@@ -49,4 +49,4 @@ namespace lms::api::subsonic
return userNode;
}
}
} // namespace lms::api::subsonic
@@ -20,6 +20,7 @@
#pragma once
#include "database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace lms::db