Rework db range primitives + fixed bug when playing all in releases/tracks views + raised limit to 1000 for ranges in subsonic API

This commit is contained in:
emeric
2023-11-10 16:17:18 +01:00
parent bd61093898
commit b7a8bf85b8
61 changed files with 1704 additions and 2603 deletions
@@ -29,6 +29,8 @@
namespace API::Subsonic
{
// Max count expected from all API methods that expose a count
static inline constexpr std::size_t defaultMaxCountSize{ 1000 };
enum class ResponseFormat
{
@@ -47,8 +47,8 @@ namespace API::Subsonic
// Optional params
const std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(10) };
const std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) };
if (size > 500)
throw ParameterValueTooHighGenericError{ "size", 500 };
if (size > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
const Range range{ offset, size };
@@ -122,7 +122,7 @@ namespace API::Subsonic
// (no seed provided by subsonic, ot it would require to store some kind of context for each user/client when iterating over the random albums)
Release::FindParameters params;
params.setSortMethod(ReleaseSortMethod::Random);
params.setRange({ 0, size });
params.setRange(Range{ 0, size });
releases = Release::findIds(context.dbSession, params);
}
@@ -132,10 +132,15 @@ namespace API::Subsonic
}
else if (type == "starred")
{
releases = feedbackService.getStarredReleases(context.userId, {}, range);
Feedback::IFeedbackService::FindParameters params;
params.setUser(context.userId);
params.setRange(range);
releases = feedbackService.findStarredReleases(params);
}
else
{
throw NotImplementedGenericError{};
}
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& albumListNode{ response.createNode(id3 ? Response::Node::Key{ "albumList2" } : Response::Node::Key{ "albumList" }) };
@@ -162,19 +167,27 @@ namespace API::Subsonic
Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() };
for (const ArtistId artistId : feedbackService.getStarredArtists(context.userId, {} /* clusters */, std::nullopt /* linkType */, ArtistSortMethod::BySortName, Range{}).results)
Feedback::IFeedbackService::FindParameters findParameters;
findParameters.setUser(context.userId);
{
if (auto artist{ Artist::find(context.dbSession, artistId) })
starredNode.addArrayChild("artist", createArtistNode(context, artist, user, id3));
Feedback::IFeedbackService::ArtistFindParameters artistFindParams;
artistFindParams.setUser(context.userId);
artistFindParams.setSortMethod(ArtistSortMethod::BySortName);
for (const ArtistId artistId : feedbackService.findStarredArtists(artistFindParams).results)
{
if (auto artist{ Artist::find(context.dbSession, artistId) })
starredNode.addArrayChild("artist", createArtistNode(context, artist, user, id3));
}
}
for (const ReleaseId releaseId : feedbackService.getStarredReleases(context.userId, {} /* clusters */, Range{}).results)
for (const ReleaseId releaseId : feedbackService.findStarredReleases(findParameters).results)
{
if (auto release{ Release::find(context.dbSession, releaseId) })
starredNode.addArrayChild("album", createAlbumNode(context, release, user, id3));
}
for (const TrackId trackId : feedbackService.getStarredTracks(context.userId, {} /* clusters */, Range{}).results)
for (const TrackId trackId : feedbackService.findStarredTracks(findParameters).results)
{
if (auto track{ Track::find(context.dbSession, trackId) })
starredNode.addArrayChild("song", createSongNode(context, track, user));
@@ -198,8 +211,8 @@ namespace API::Subsonic
{
// Optional params
std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(50) };
if (size > 500)
throw ParameterValueTooHighGenericError{"size", 500};
if (size > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
auto transaction{ context.dbSession.createSharedTransaction() };
@@ -207,13 +220,17 @@ namespace API::Subsonic
if (!user)
throw UserNotAuthorizedError{};
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setSortMethod(TrackSortMethod::Random).setRange({0, size})) };
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& randomSongsNode{ response.createNode("randomSongs") };
for (const Track::pointer& track : tracks.results)
randomSongsNode.addArrayChild("song", createSongNode(context, track, user));
Track::FindParameters params;
params.setSortMethod(TrackSortMethod::Random);
params.setRange(Range{ 0, size });
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{
randomSongsNode.addArrayChild("song", createSongNode(context, track, user));
});
return response;
}
@@ -225,8 +242,8 @@ namespace API::Subsonic
// Optional params
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(10) };
if (count > 500)
throw ParameterValueTooHighGenericError{"count", 500};
if (count > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{"count", defaultMaxCountSize};
std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) };
@@ -249,11 +266,12 @@ namespace API::Subsonic
Track::FindParameters params;
params.setClusters({ cluster->getId() });
params.setRange({ offset, count });
params.setRange(Range{ offset, count });
const auto tracks{ Track::find(context.dbSession, params) };
for (const Track::pointer& track : tracks.results)
songsByGenreNode.addArrayChild("song", createSongNode(context, track, user));
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{
songsByGenreNode.addArrayChild("song", createSongNode(context, track, user));
});
return response;
}
@@ -40,7 +40,7 @@ namespace API::Subsonic
if (!user)
throw UserNotAuthorizedError{};
const auto bookmarkIds{ TrackBookmark::find(context.dbSession, user->getId(), Range {}) };
const auto bookmarkIds{ TrackBookmark::find(context.dbSession, user->getId()) };
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& bookmarksNode{ response.createNode("bookmarks") };
+18 -12
View File
@@ -124,7 +124,7 @@ namespace API::Subsonic
// first pass: dispatch the artists by first letter
LMS_LOG(API_SUBSONIC, DEBUG) << "GetArtists: fetching all artists...";
std::map<char, std::vector<ArtistId>> artistsSortedByFirstChar;
std::size_t currentArtistOffset{0};
std::size_t currentArtistOffset{ 0 };
constexpr std::size_t batchSize{ 100 };
bool hasMoreArtists{ true };
while (hasMoreArtists)
@@ -190,7 +190,7 @@ namespace API::Subsonic
{
Track::FindParameters params;
params.setArtist(id);
params.setRange({ 0, meanTrackCountPerArtist });
params.setRange(Range{ 0, meanTrackCountPerArtist });
params.setSortMethod(TrackSortMethod::Random);
const auto artistTracks{ Track::findIds(context.dbSession, params) };
@@ -221,7 +221,7 @@ namespace API::Subsonic
{
Track::FindParameters params;
params.setRelease(id);
params.setRange({ 0, meanTrackCountPerRelease });
params.setRange(Range{ 0, meanTrackCountPerRelease });
params.setSortMethod(TrackSortMethod::Random);
const auto releaseTracks{ Track::findIds(context.dbSession, params) };
@@ -242,6 +242,8 @@ namespace API::Subsonic
{
// Optional params
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(50) };
if (count > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize };
std::vector<TrackId> tracks;
@@ -316,9 +318,11 @@ namespace API::Subsonic
directoryNode.setAttribute("id", idToString(RootId{}));
directoryNode.setAttribute("name", "Music");
const auto rootArtistIds{ Artist::find(context.dbSession, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName)) };
for (const Artist::pointer& artist : rootArtistIds.results)
directoryNode.addArrayChild("child", createArtistNode(context, artist, user, false /* no id3 */));
// TODO: this does not scale when a lot of artists are present
Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::BySortName), [&](const Artist::pointer& artist)
{
directoryNode.addArrayChild("child", createArtistNode(context, artist, user, false /* no id3 */));
});
}
else if (artistId)
{
@@ -330,9 +334,10 @@ namespace API::Subsonic
directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(artist->getName()));
const auto artistReleases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(*artistId)) };
for (const Release::pointer& release : artistReleases.results)
directoryNode.addArrayChild("child", createAlbumNode(context, release, user, false /* no id3 */));
Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release)
{
directoryNode.addArrayChild("child", createAlbumNode(context, release, user, false /* no id3 */));
});
}
else if (releaseId)
{
@@ -344,9 +349,10 @@ namespace API::Subsonic
directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(release->getName()));
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release)) };
for (const Track::pointer& track : tracks.results)
directoryNode.addArrayChild("child", createSongNode(context, track, user));
Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track)
{
directoryNode.addArrayChild("child", createSongNode(context, track, user));
});
}
else
throw BadParameterGenericError{ "id" };
@@ -57,12 +57,12 @@ namespace API::Subsonic
std::size_t songCount{ getParameterAs<std::size_t>(context.parameters, "songCount").value_or(20) };
std::size_t songOffset{ getParameterAs<std::size_t>(context.parameters, "songOffset").value_or(0) };
if (artistCount > 500)
throw ParameterValueTooHighGenericError{ "artistCount", 500 };
else if (albumCount > 500)
throw ParameterValueTooHighGenericError{ "albumCount", 500 };
else if (songCount > 500)
throw ParameterValueTooHighGenericError{ "songCount", 500 };
if (artistCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "artistCount", defaultMaxCountSize };
else if (albumCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "albumCount", defaultMaxCountSize };
else if (songCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "songCount", defaultMaxCountSize };
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& searchResult2Node{ response.createNode(id3 ? "searchResult3" : "searchResult2") };
@@ -77,30 +77,36 @@ namespace API::Subsonic
{
Artist::FindParameters params;
params.setKeywords(keywords);
params.setRange({ artistOffset, artistCount });
params.setRange(Range{ artistOffset, artistCount });
for (const Artist::pointer& artist : Artist::find(context.dbSession, params).results)
searchResult2Node.addArrayChild("artist", createArtistNode(context, artist, user, id3));
Artist::find(context.dbSession, params, [&](const Artist::pointer& artist)
{
searchResult2Node.addArrayChild("artist", createArtistNode(context, artist, user, id3));
});
}
if (albumCount > 0)
{
Release::FindParameters params;
params.setKeywords(keywords);
params.setRange({ albumOffset, albumCount });
params.setRange(Range{ albumOffset, albumCount });
for (const Release::pointer& release : Release::find(context.dbSession, params).results)
searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3));
Release::find(context.dbSession, params, [&](const Release::pointer& release)
{
searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3));
});
}
if (songCount > 0)
{
Track::FindParameters params;
params.setKeywords(keywords);
params.setRange({ songOffset, songCount });
params.setRange(Range{ songOffset, songCount });
for (const Track::pointer& track : Track::find(context.dbSession, params).results)
searchResult2Node.addArrayChild("song", createSongNode(context, track, user));
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{
searchResult2Node.addArrayChild("song", createSongNode(context, track, user));
});
}
return response;