diff --git a/src/libs/services/database/impl/TrackArtistLink.cpp b/src/libs/services/database/impl/TrackArtistLink.cpp index bffd8d4d..ab64672a 100644 --- a/src/libs/services/database/impl/TrackArtistLink.cpp +++ b/src/libs/services/database/impl/TrackArtistLink.cpp @@ -28,74 +28,84 @@ namespace Database { + namespace + { + Wt::Dbo::Query createQuery(Session& session, const TrackArtistLink::FindParameters& params) + { + session.checkSharedLocked(); - static - Wt::Dbo::Query - createQuery(Session& session, const TrackArtistLink::FindParameters& params) - { - session.checkSharedLocked(); + auto query{ session.getDboSession().query("SELECT DISTINCT t_a_l.id FROM track_artist_link t_a_l") }; - auto query {session.getDboSession().query("SELECT DISTINCT t_a_l.id FROM track_artist_link t_a_l")}; + if (params.linkType) + query.where("t_a_l.type = ?").bind(*params.linkType); - if (params.linkType) - query.where("t_a_l.type = ?").bind(*params.linkType); + if (params.track.isValid() || params.release.isValid()) + query.join("track t ON t.id = t_a_l.track_id"); - if (params.track.isValid() || params.release.isValid()) - query.join("track t ON t.id = t_a_l.track_id"); + if (params.artist.isValid()) + query.join("artist a ON a.id = t_a_l.artist_id"); - if (params.track.isValid()) - query.where("t.id = ?").bind(params.track); + if (params.release.isValid()) + query.where("t.release_id = ?").bind(params.release); - if (params.release.isValid()) - 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, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType) - : _type {type} - , _subType {subType} - , _track {getDboPtr(track)} - , _artist {getDboPtr(artist)} - { - } + TrackArtistLink::TrackArtistLink(ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType) + : _type{ type } + , _subType{ subType } + , _track{ getDboPtr(track) } + , _artist{ getDboPtr(artist) } + { + } - TrackArtistLink::pointer - TrackArtistLink::create(Session& session, ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType) - { - session.checkUniqueLocked(); + TrackArtistLink::pointer TrackArtistLink::create(Session& session, ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType) + { + session.checkUniqueLocked(); - TrackArtistLink::pointer res {session.getDboSession().add(std::make_unique(track, artist, type, subType))}; - session.getDboSession().flush(); + TrackArtistLink::pointer res{ session.getDboSession().add(std::make_unique(track, artist, type, subType)) }; + session.getDboSession().flush(); - return res; - } + return res; + } - TrackArtistLink::pointer - TrackArtistLink::find(Session& session, TrackArtistLinkId id) - { - session.checkSharedLocked(); - return session.getDboSession().find().where("id = ?").bind(id).resultValue(); - } + TrackArtistLink::pointer TrackArtistLink::find(Session& session, TrackArtistLinkId id) + { + session.checkSharedLocked(); + return session.getDboSession().find().where("id = ?").bind(id).resultValue(); + } - RangeResults - TrackArtistLink::find(Session& session, const FindParameters& params) - { - session.checkSharedLocked(); + RangeResults TrackArtistLink::find(Session& session, const FindParameters& params) + { + session.checkSharedLocked(); - auto query {createQuery(session, params)}; - return Utils::execQuery(query, params.range); - } + auto query{ createQuery(session, params) }; + return Utils::execQuery(query, params.range); + } + EnumSet TrackArtistLink::findUsedTypes(Session& session) + { + session.checkSharedLocked(); - EnumSet - TrackArtistLink::findUsedTypes(Session& session) - { - session.checkSharedLocked(); + auto res{ session.getDboSession().query("SELECT DISTINCT type from track_artist_link").resultList() }; - auto res {session.getDboSession().query("SELECT DISTINCT type from track_artist_link").resultList()}; + return EnumSet(std::begin(res), std::end(res)); + } - return EnumSet(std::begin(res), std::end(res)); - } + EnumSet TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId) + { + session.checkSharedLocked(); + + auto res{ session.getDboSession() + .query("SELECT DISTINCT type from track_artist_link") + .where("artist_id = ?").bind(artistId) + .resultList() }; + + return EnumSet(std::begin(res), std::end(res)); + } } diff --git a/src/libs/services/database/include/services/database/TrackArtistLink.hpp b/src/libs/services/database/include/services/database/TrackArtistLink.hpp index c342d5b9..a1dec682 100644 --- a/src/libs/services/database/include/services/database/TrackArtistLink.hpp +++ b/src/libs/services/database/include/services/database/TrackArtistLink.hpp @@ -24,6 +24,7 @@ #include +#include "services/database/ArtistId.hpp" #include "services/database/IdType.hpp" #include "services/database/Object.hpp" #include "services/database/ReleaseId.hpp" @@ -35,55 +36,58 @@ LMS_DECLARE_IDTYPE(TrackArtistLinkId) namespace Database { - class Artist; - class Session; - class Track; + class Artist; + class Session; + class Track; - class TrackArtistLink final : public Object - { - public: - struct FindParameters - { - Range range; - std::optional linkType; // if set, only artists that have produced at least one track with this link type - TrackId track; // artists involved in this track - ReleaseId release; // artists involved in this release + class TrackArtistLink final : public Object + { + public: + struct FindParameters + { + Range range; + std::optional linkType; // if set, only artists that have produced at least one track with this link type + ArtistId artist; // if set, links involved with this artist + 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& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } - FindParameters& setTrack(TrackId _track) { track = _track; return *this; } - FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } - }; + FindParameters& setRange(Range _range) { range = _range; return *this; } + FindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } + FindParameters& setArtist(ArtistId _artist) { artist = _artist; return *this; } + FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } + FindParameters& setTrack(TrackId _track) { track = _track; return *this; } + }; - TrackArtistLink() = default; - TrackArtistLink(ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType); + TrackArtistLink() = default; + TrackArtistLink(ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType); - static RangeResults find(Session& session, const FindParameters& parameters); - static pointer find(Session& session, TrackArtistLinkId linkId); - static pointer create(Session& session, ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType = {}); - static EnumSet findUsedTypes(Session& session); + static RangeResults find(Session& session, const FindParameters& parameters); + static pointer find(Session& session, TrackArtistLinkId linkId); + static pointer create(Session& session, ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType = {}); + static EnumSet findUsedTypes(Session& session); + static EnumSet findUsedTypes(Session& session, ArtistId _artist); - ObjectPtr getTrack() const { return _track; } - ObjectPtr getArtist() const { return _artist; } - TrackArtistLinkType getType() const { return _type; } - std::string_view getSubType() const { return _subType; } + ObjectPtr getTrack() const { return _track; } + ObjectPtr getArtist() const { return _artist; } + TrackArtistLinkType getType() const { return _type; } + std::string_view getSubType() const { return _subType; } - template - void persist(Action& a) - { - Wt::Dbo::field(a, _type, "type"); - Wt::Dbo::field(a, _subType, "subtype"); + template + void persist(Action& a) + { + Wt::Dbo::field(a, _type, "type"); + Wt::Dbo::field(a, _subType, "subtype"); - Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade); - } + Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade); + } - private: - TrackArtistLinkType _type; - std::string _subType; + private: + TrackArtistLinkType _type; + std::string _subType; - Wt::Dbo::ptr _track; - Wt::Dbo::ptr _artist; - }; + Wt::Dbo::ptr _track; + Wt::Dbo::ptr _artist; + }; } diff --git a/src/libs/services/database/test/Artist.cpp b/src/libs/services/database/test/Artist.cpp index 6b7868b0..fe849cf9 100644 --- a/src/libs/services/database/test/Artist.cpp +++ b/src/libs/services/database/test/Artist.cpp @@ -179,6 +179,15 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles) tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Composer})); EXPECT_EQ(tracks.results.size(), 0); } + + { + auto transaction {session.createSharedTransaction()}; + EnumSet 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) diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 532a6ca2..b41d1aad 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -307,7 +307,7 @@ namespace API::Subsonic Response resp{ (itEntryPoint->second.func)(requestContext) }; resp.write(response.out(), format); - response.setMimeType(ResponseFormatToMimeType(format)); + response.setMimeType(std::string{ ResponseFormatToMimeType(format) }); LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!"; return; @@ -331,7 +331,7 @@ namespace API::Subsonic << ", code = " << static_cast(e.getCode()) << ", msg = '" << e.getMessage() << "'"; Response resp{ Response::createFailedResponse(protocolVersion, e) }; resp.write(response.out(), format); - response.setMimeType(ResponseFormatToMimeType(format)); + response.setMimeType(std::string{ ResponseFormatToMimeType(format) }); } } diff --git a/src/libs/subsonic/impl/SubsonicResponse.cpp b/src/libs/subsonic/impl/SubsonicResponse.cpp index f96ce0dc..7c86e0ff 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.cpp +++ b/src/libs/subsonic/impl/SubsonicResponse.cpp @@ -33,8 +33,7 @@ namespace API::Subsonic { - - std::string ResponseFormatToMimeType(ResponseFormat format) + std::string_view ResponseFormatToMimeType(ResponseFormat format) { switch (format) { @@ -47,7 +46,7 @@ namespace API::Subsonic 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" }; _value = std::string{ value }; @@ -55,7 +54,7 @@ namespace API::Subsonic 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" }; _value = value; @@ -82,6 +81,21 @@ namespace API::Subsonic _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{}); + } + + 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) { @@ -236,21 +250,28 @@ namespace API::Subsonic } else { - for (auto itChildNode : node._children) + for (const auto& [key, childNodes] : node._children) { - for (const Response::Node& childNode : itChildNode.second) - res[itChildNode.first] = nodeToJsonObject(childNode); + for (const Response::Node& childNode : childNodes) + res[key] = nodeToJsonObject(childNode); } - for (auto itChildArrayNode : node._childrenArrays) + for (const auto& [key, childArrayNodes] : node._childrenArrays) { - const std::vector& childArrayNodes{ itChildArrayNode.second }; - Json::Array array; for (const Response::Node& childNode : childArrayNodes) 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); } } diff --git a/src/libs/subsonic/impl/SubsonicResponse.hpp b/src/libs/subsonic/impl/SubsonicResponse.hpp index df9344a1..0ffe4f02 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.hpp +++ b/src/libs/subsonic/impl/SubsonicResponse.hpp @@ -36,7 +36,7 @@ namespace API::Subsonic json, }; - std::string ResponseFormatToMimeType(ResponseFormat format); + std::string_view ResponseFormatToMimeType(ResponseFormat format); class Error { @@ -202,7 +202,7 @@ namespace API::Subsonic _attributes[std::string{ key }] = static_cast(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(long long value); Node& createChild(const std::string& key); @@ -210,6 +210,8 @@ namespace API::Subsonic void addChild(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: void setVersionAttribute(ProtocolVersion version); @@ -220,6 +222,7 @@ namespace API::Subsonic std::optional _value; std::map> _children; std::map> _childrenArrays; + std::map> _childrenValues; }; static Response createOkResponse(ProtocolVersion protocolVersion); diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index 93147eac..94ba72ef 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -21,6 +21,7 @@ #include "services/database/Artist.hpp" #include "services/database/Release.hpp" +#include "services/database/TrackArtistLink.hpp" #include "services/database/User.hpp" #include "services/scrobbling/IScrobblingService.hpp" #include "utils/Service.hpp" @@ -50,6 +51,26 @@ namespace API::Subsonic 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) @@ -73,8 +94,15 @@ namespace API::Subsonic std::optional mbid {artist->getMBID()}; artistNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); } + 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; } } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Artist.hpp b/src/libs/subsonic/impl/responses/Artist.hpp index 06fd71ec..e27352ed 100644 --- a/src/libs/subsonic/impl/responses/Artist.hpp +++ b/src/libs/subsonic/impl/responses/Artist.hpp @@ -22,6 +22,7 @@ #include #include #include "services/database/Object.hpp" +#include "services/database/Types.hpp" #include "SubsonicResponse.hpp" namespace Database @@ -36,6 +37,7 @@ namespace API::Subsonic namespace Utils { std::string joinArtistNames(const std::vector>& artists); + std::string_view toString(Database::TrackArtistLinkType type); } Response::Node createArtistNode(const Database::ObjectPtr& artist, Database::Session& session, const Database::ObjectPtr& user, bool id3); } \ No newline at end of file