Subsonic API: added 'roles' in artist entries

This commit is contained in:
emeric
2023-10-04 08:34:51 +02:00
parent b1c6cf8d7d
commit 8f29410670
8 changed files with 184 additions and 107 deletions
@@ -28,14 +28,13 @@
namespace Database namespace Database
{ {
namespace
static {
Wt::Dbo::Query<TrackArtistLinkId> Wt::Dbo::Query<TrackArtistLinkId> createQuery(Session& session, const TrackArtistLink::FindParameters& params)
createQuery(Session& session, const TrackArtistLink::FindParameters& params)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query {session.getDboSession().query<TrackArtistLinkId>("SELECT DISTINCT t_a_l.id FROM track_artist_link t_a_l")}; auto query{ session.getDboSession().query<TrackArtistLinkId>("SELECT DISTINCT t_a_l.id FROM track_artist_link t_a_l") };
if (params.linkType) if (params.linkType)
query.where("t_a_l.type = ?").bind(*params.linkType); query.where("t_a_l.type = ?").bind(*params.linkType);
@@ -43,57 +42,68 @@ namespace Database
if (params.track.isValid() || params.release.isValid()) if (params.track.isValid() || params.release.isValid())
query.join("track t ON t.id = t_a_l.track_id"); query.join("track t ON t.id = t_a_l.track_id");
if (params.track.isValid()) if (params.artist.isValid())
query.where("t.id = ?").bind(params.track); query.join("artist a ON a.id = t_a_l.artist_id");
if (params.release.isValid()) if (params.release.isValid())
query.where("t.release_id = ?").bind(params.release); query.where("t.release_id = ?").bind(params.release);
if (params.track.isValid())
query.where("t.id = ?").bind(params.track);
return query; return query;
} }
}
TrackArtistLink::TrackArtistLink(ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType) TrackArtistLink::TrackArtistLink(ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType)
: _type {type} : _type{ type }
, _subType {subType} , _subType{ subType }
, _track {getDboPtr(track)} , _track{ getDboPtr(track) }
, _artist {getDboPtr(artist)} , _artist{ getDboPtr(artist) }
{ {
} }
TrackArtistLink::pointer TrackArtistLink::pointer TrackArtistLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType)
TrackArtistLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType)
{ {
session.checkUniqueLocked(); session.checkUniqueLocked();
TrackArtistLink::pointer res {session.getDboSession().add(std::make_unique<TrackArtistLink>(track, artist, type, subType))}; TrackArtistLink::pointer res{ session.getDboSession().add(std::make_unique<TrackArtistLink>(track, artist, type, subType)) };
session.getDboSession().flush(); session.getDboSession().flush();
return res; return res;
} }
TrackArtistLink::pointer TrackArtistLink::pointer TrackArtistLink::find(Session& session, TrackArtistLinkId id)
TrackArtistLink::find(Session& session, TrackArtistLinkId id)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
return session.getDboSession().find<TrackArtistLink>().where("id = ?").bind(id).resultValue(); return session.getDboSession().find<TrackArtistLink>().where("id = ?").bind(id).resultValue();
} }
RangeResults<TrackArtistLinkId> RangeResults<TrackArtistLinkId> TrackArtistLink::find(Session& session, const FindParameters& params)
TrackArtistLink::find(Session& session, const FindParameters& params)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query {createQuery(session, params)}; auto query{ createQuery(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery(query, params.range);
} }
EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session)
EnumSet<TrackArtistLinkType>
TrackArtistLink::findUsedTypes(Session& session)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto res {session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link").resultList()}; auto res{ session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link").resultList() };
return EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res));
}
EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId)
{
session.checkSharedLocked();
auto res{ session.getDboSession()
.query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link")
.where("artist_id = ?").bind(artistId)
.resultList() };
return EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res)); return EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res));
} }
@@ -24,6 +24,7 @@
#include <Wt/Dbo/Dbo.h> #include <Wt/Dbo/Dbo.h>
#include "services/database/ArtistId.hpp"
#include "services/database/IdType.hpp" #include "services/database/IdType.hpp"
#include "services/database/Object.hpp" #include "services/database/Object.hpp"
#include "services/database/ReleaseId.hpp" #include "services/database/ReleaseId.hpp"
@@ -46,13 +47,15 @@ namespace Database
{ {
Range range; Range range;
std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
TrackId track; // artists involved in this track ArtistId artist; // if set, links involved with this artist
ReleaseId release; // artists involved in this release ReleaseId release; // if set, artists involved in this release
TrackId track; // if set, artists involved in this track
FindParameters& setRange(Range _range) {range = _range; return *this; } FindParameters& setRange(Range _range) { range = _range; return *this; }
FindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; } FindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; }
FindParameters& setTrack(TrackId _track) { track = _track; return *this; } FindParameters& setArtist(ArtistId _artist) { artist = _artist; return *this; }
FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; }
FindParameters& setTrack(TrackId _track) { track = _track; return *this; }
}; };
TrackArtistLink() = default; TrackArtistLink() = default;
@@ -62,6 +65,7 @@ namespace Database
static pointer find(Session& session, TrackArtistLinkId linkId); static pointer find(Session& session, TrackArtistLinkId linkId);
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType = {}); static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType = {});
static EnumSet<TrackArtistLinkType> findUsedTypes(Session& session); static EnumSet<TrackArtistLinkType> findUsedTypes(Session& session);
static EnumSet<TrackArtistLinkType> findUsedTypes(Session& session, ArtistId _artist);
ObjectPtr<Track> getTrack() const { return _track; } ObjectPtr<Track> getTrack() const { return _track; }
ObjectPtr<Artist> getArtist() const { return _artist; } ObjectPtr<Artist> getArtist() const { return _artist; }
@@ -179,6 +179,15 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Composer})); tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Composer}));
EXPECT_EQ(tracks.results.size(), 0); EXPECT_EQ(tracks.results.size(), 0);
} }
{
auto transaction {session.createSharedTransaction()};
EnumSet<TrackArtistLinkType> types{ TrackArtistLink::findUsedTypes(session, artist.getId()) };
EXPECT_TRUE(types.contains(TrackArtistLinkType::ReleaseArtist));
EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist));
EXPECT_TRUE(types.contains(TrackArtistLinkType::Writer));
EXPECT_FALSE(types.contains(TrackArtistLinkType::Composer));
}
} }
TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists) TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists)
+2 -2
View File
@@ -307,7 +307,7 @@ namespace API::Subsonic
Response resp{ (itEntryPoint->second.func)(requestContext) }; Response resp{ (itEntryPoint->second.func)(requestContext) };
resp.write(response.out(), format); resp.write(response.out(), format);
response.setMimeType(ResponseFormatToMimeType(format)); response.setMimeType(std::string{ ResponseFormatToMimeType(format) });
LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!"; LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!";
return; return;
@@ -331,7 +331,7 @@ namespace API::Subsonic
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'"; << ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'";
Response resp{ Response::createFailedResponse(protocolVersion, e) }; Response resp{ Response::createFailedResponse(protocolVersion, e) };
resp.write(response.out(), format); resp.write(response.out(), format);
response.setMimeType(ResponseFormatToMimeType(format)); response.setMimeType(std::string{ ResponseFormatToMimeType(format) });
} }
} }
+32 -11
View File
@@ -33,8 +33,7 @@
namespace API::Subsonic namespace API::Subsonic
{ {
std::string_view ResponseFormatToMimeType(ResponseFormat format)
std::string ResponseFormatToMimeType(ResponseFormat format)
{ {
switch (format) switch (format)
{ {
@@ -47,7 +46,7 @@ namespace API::Subsonic
void Response::Node::setValue(std::string_view value) void Response::Node::setValue(std::string_view value)
{ {
if (!_children.empty() || !_childrenArrays.empty()) if (!_children.empty() || !_childrenArrays.empty() || !_childrenValues.empty())
throw LmsException{ "Node already has children" }; throw LmsException{ "Node already has children" };
_value = std::string{ value }; _value = std::string{ value };
@@ -55,7 +54,7 @@ namespace API::Subsonic
void Response::Node::setValue(long long value) void Response::Node::setValue(long long value)
{ {
if (!_children.empty() || !_childrenArrays.empty()) if (!_children.empty() || !_childrenArrays.empty() || !_childrenValues.empty())
throw LmsException{ "Node already has children" }; throw LmsException{ "Node already has children" };
_value = value; _value = value;
@@ -82,6 +81,21 @@ namespace API::Subsonic
_childrenArrays[key].emplace_back(std::move(node)); _childrenArrays[key].emplace_back(std::move(node));
} }
void Response::Node::createArrayValue(const std::string& key)
{
if (_value)
throw LmsException{ "Node already has a value" };
_childrenValues.emplace(key, std::vector<std::string>{});
}
void Response::Node::addArrayValue(const std::string& key, std::string_view value)
{
if (_value)
throw LmsException{ "Node already has a value" };
_childrenValues[key].push_back(std::string{ value });
}
Response::Node& Response::Node::createChild(const std::string& key) Response::Node& Response::Node::createChild(const std::string& key)
{ {
@@ -236,21 +250,28 @@ namespace API::Subsonic
} }
else else
{ {
for (auto itChildNode : node._children) for (const auto& [key, childNodes] : node._children)
{ {
for (const Response::Node& childNode : itChildNode.second) for (const Response::Node& childNode : childNodes)
res[itChildNode.first] = nodeToJsonObject(childNode); res[key] = nodeToJsonObject(childNode);
} }
for (auto itChildArrayNode : node._childrenArrays) for (const auto& [key, childArrayNodes] : node._childrenArrays)
{ {
const std::vector<Response::Node>& childArrayNodes{ itChildArrayNode.second };
Json::Array array; Json::Array array;
for (const Response::Node& childNode : childArrayNodes) for (const Response::Node& childNode : childArrayNodes)
array.emplace_back(nodeToJsonObject(childNode)); array.emplace_back(nodeToJsonObject(childNode));
res[itChildArrayNode.first] = std::move(array); res[key] = std::move(array);
}
for (const auto& [key, childValues] : node._childrenValues)
{
Json::Array array;
for (const std::string& childValue : childValues)
array.emplace_back(Json::Value{ childValue });
res[key] = std::move(array);
} }
} }
+5 -2
View File
@@ -36,7 +36,7 @@ namespace API::Subsonic
json, json,
}; };
std::string ResponseFormatToMimeType(ResponseFormat format); std::string_view ResponseFormatToMimeType(ResponseFormat format);
class Error class Error
{ {
@@ -202,7 +202,7 @@ namespace API::Subsonic
_attributes[std::string{ key }] = static_cast<long long>(value); _attributes[std::string{ key }] = static_cast<long long>(value);
} }
// A Node has either a value or some children // A Node has either a single value or an array of values or some children
void setValue(std::string_view value); void setValue(std::string_view value);
void setValue(long long value); void setValue(long long value);
Node& createChild(const std::string& key); Node& createChild(const std::string& key);
@@ -210,6 +210,8 @@ namespace API::Subsonic
void addChild(const std::string& key, Node node); void addChild(const std::string& key, Node node);
void addArrayChild(const std::string& key, Node node); void addArrayChild(const std::string& key, Node node);
void createArrayValue(const std::string& key);
void addArrayValue(const std::string& key, std::string_view value);
private: private:
void setVersionAttribute(ProtocolVersion version); void setVersionAttribute(ProtocolVersion version);
@@ -220,6 +222,7 @@ namespace API::Subsonic
std::optional<ValueType> _value; std::optional<ValueType> _value;
std::map<std::string, std::vector<Node>> _children; std::map<std::string, std::vector<Node>> _children;
std::map<std::string, std::vector<Node>> _childrenArrays; std::map<std::string, std::vector<Node>> _childrenArrays;
std::map<std::string, std::vector<std::string>> _childrenValues;
}; };
static Response createOkResponse(ProtocolVersion protocolVersion); static Response createOkResponse(ProtocolVersion protocolVersion);
@@ -21,6 +21,7 @@
#include "services/database/Artist.hpp" #include "services/database/Artist.hpp"
#include "services/database/Release.hpp" #include "services/database/Release.hpp"
#include "services/database/TrackArtistLink.hpp"
#include "services/database/User.hpp" #include "services/database/User.hpp"
#include "services/scrobbling/IScrobblingService.hpp" #include "services/scrobbling/IScrobblingService.hpp"
#include "utils/Service.hpp" #include "utils/Service.hpp"
@@ -50,6 +51,26 @@ namespace API::Subsonic
return StringUtils::joinStrings(names, ", "); return StringUtils::joinStrings(names, ", ");
} }
std::string_view toString(TrackArtistLinkType type)
{
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 "producrer";
case TrackArtistLinkType::ReleaseArtist: return "albumartist";
case TrackArtistLinkType::Remixer: return "remixer";
case TrackArtistLinkType::Writer: return "writer";
}
return "unknown";
}
} }
Response::Node createArtistNode(const Artist::pointer& artist, Session& session, const User::pointer& user, bool id3) Response::Node createArtistNode(const Artist::pointer& artist, Session& session, const User::pointer& user, bool id3)
@@ -73,8 +94,15 @@ namespace API::Subsonic
std::optional<UUID> mbid {artist->getMBID()}; std::optional<UUID> mbid {artist->getMBID()};
artistNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); artistNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : "");
} }
artistNode.setAttribute("sortName", artist->getSortName()); artistNode.setAttribute("sortName", artist->getSortName());
// roles
Response::Node roles;
artistNode.createArrayValue("roles");
for (const TrackArtistLinkType linkType : TrackArtistLink::findUsedTypes(session, artist->getId()))
artistNode.addArrayValue("roles", Utils::toString(linkType));
return artistNode; return artistNode;
} }
} }
@@ -22,6 +22,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "services/database/Object.hpp" #include "services/database/Object.hpp"
#include "services/database/Types.hpp"
#include "SubsonicResponse.hpp" #include "SubsonicResponse.hpp"
namespace Database namespace Database
@@ -36,6 +37,7 @@ namespace API::Subsonic
namespace Utils namespace Utils
{ {
std::string joinArtistNames(const std::vector<Database::ObjectPtr<Database::Artist>>& artists); std::string joinArtistNames(const std::vector<Database::ObjectPtr<Database::Artist>>& artists);
std::string_view toString(Database::TrackArtistLinkType type);
} }
Response::Node createArtistNode(const Database::ObjectPtr<Database::Artist>& artist, Database::Session& session, const Database::ObjectPtr<Database::User>& user, bool id3); Response::Node createArtistNode(const Database::ObjectPtr<Database::Artist>& artist, Database::Session& session, const Database::ObjectPtr<Database::User>& user, bool id3);
} }