From 040973209e699cff73f1255eca6fa721ffa95107 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 17 Dec 2022 19:08:35 +0100 Subject: [PATCH] Subsonic API: handle tracks and releases in getSimilarSongs endpoint. fixes #245 --- src/libs/subsonic/impl/SubsonicResource.cpp | 114 ++++++++++++++------ 1 file changed, 84 insertions(+), 30 deletions(-) diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 82d1efa9..27dd9211 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -1169,49 +1169,103 @@ handleGetArtistsRequest(RequestContext& context) } static -Response -handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3) +std::vector +findSimilarSongs(RequestContext& context, ArtistId artistId, std::size_t count) { - // Mandatory params - const ArtistId artistId {getMandatoryParameterAs(context.parameters, "id")}; + // API says: "Returns a random collection of songs from the given artist and similar artists" + const std::size_t similarArtistCount {count / 5}; + std::vector artistIds {Service::get()->getSimilarArtists(artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, similarArtistCount)}; + artistIds.push_back(artistId); - // Optional params - std::size_t count {getParameterAs(context.parameters, "count").value_or(50)}; - - const auto similarArtistIds {Service::get()->getSimilarArtists(artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, 5)}; + const std::size_t meanTrackCountPerArtist {(count / artistIds.size()) + 1}; auto transaction {context.dbSession.createSharedTransaction()}; - const Artist::pointer artist {Artist::find(context.dbSession, artistId)}; - if (!artist) - throw RequestedDataNotFoundError {}; + std::vector tracks; + tracks.reserve(count); - const User::pointer user {User::find(context.dbSession, context.userId)}; - if (!user) - throw UserNotAuthorizedError {}; - - // "Returns a random collection of songs from the given artist and similar artists" - const auto trackResults {Track::find(context.dbSession, Track::FindParameters {} - .setArtist(artist->getId()) - .setRange({0, count / 2}) - .setSortMethod(TrackSortMethod::Random))}; - - std::vector tracks {trackResults.results}; - - for (const ArtistId similarArtistId : similarArtistIds) + for (const ArtistId id : artistIds) { - const auto similarArtistTracks {Track::find(context.dbSession, Track::FindParameters {} - .setArtist(similarArtistId) - .setRange({0, (count / 2) / 5}) - .setSortMethod(TrackSortMethod::Random))}; + Track::FindParameters params; + params.setArtist(id); + params.setRange({0, meanTrackCountPerArtist}); + params.setSortMethod(TrackSortMethod::Random); + const auto artistTracks {Track::find(context.dbSession, params)}; tracks.insert(std::end(tracks), - std::begin(similarArtistTracks.results), - std::end(similarArtistTracks.results)); + std::begin(artistTracks.results), + std::end(artistTracks.results)); } + return tracks; +} + +static +std::vector +findSimilarSongs(RequestContext& context, ReleaseId releaseId, std::size_t count) +{ + // API says: "Returns a random collection of songs from the given artist and similar artists" + // so let's extend this for release + const std::size_t similarReleaseCount {count / 5}; + std::vector releaseIds {Service::get()->getSimilarReleases(releaseId, similarReleaseCount)}; + releaseIds.push_back(releaseId); + + const std::size_t meanTrackCountPerRelease {(count / releaseIds.size()) + 1}; + + auto transaction {context.dbSession.createSharedTransaction()}; + + std::vector tracks; + tracks.reserve(count); + + for (const ReleaseId id : releaseIds) + { + Track::FindParameters params; + params.setRelease(id); + params.setRange({0, meanTrackCountPerRelease}); + params.setSortMethod(TrackSortMethod::Random); + + const auto releaseTracks {Track::find(context.dbSession, params)}; + tracks.insert(std::end(tracks), + std::begin(releaseTracks.results), + std::end(releaseTracks.results)); + } + + return tracks; +} + +static +std::vector +findSimilarSongs(RequestContext&, TrackId trackId, std::size_t count) +{ + return Service::get()->findSimilarTracks({trackId}, count); +} + +static +Response +handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3) +{ + // Optional params + std::size_t count {getParameterAs(context.parameters, "count").value_or(50)}; + + std::vector tracks; + + if (const auto artistId {getParameterAs(context.parameters, "id")}) + tracks = findSimilarSongs(context, *artistId, count); + else if (const auto releaseId {getParameterAs(context.parameters, "id")}) + tracks = findSimilarSongs(context, *releaseId, count); + else if (const auto trackId {getParameterAs(context.parameters, "id")}) + tracks = findSimilarSongs(context, *trackId, count); + else + throw BadParameterGenericError {"id"}; + Random::shuffleContainer(tracks); + auto transaction {context.dbSession.createSharedTransaction()}; + + User::pointer user {User::find(context.dbSession, context.userId)}; + if (!user) + throw UserNotAuthorizedError {}; + Response response {Response::createOkResponse(context.serverProtocolVersion)}; Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")}; for (const TrackId trackId : tracks)