Subsonic API: do not block the whole application while handling a getArtist. But this is really best effort as it is way too long

This commit is contained in:
emeric
2023-11-02 16:10:12 +01:00
parent fa11a1df9b
commit 8b35e61c7d
@@ -26,6 +26,7 @@
#include "services/database/Track.hpp" #include "services/database/Track.hpp"
#include "services/database/User.hpp" #include "services/database/User.hpp"
#include "services/recommendation/IRecommendationService.hpp" #include "services/recommendation/IRecommendationService.hpp"
#include "utils/Logger.hpp"
#include "utils/Random.hpp" #include "utils/Random.hpp"
#include "utils/Service.hpp" #include "utils/Service.hpp"
#include "responses/Album.hpp" #include "responses/Album.hpp"
@@ -94,15 +95,16 @@ namespace API::Subsonic
Response::Node& artistsNode{ response.createNode(id3 ? "artists" : "indexes") }; Response::Node& artistsNode{ response.createNode(id3 ? "artists" : "indexes") };
artistsNode.setAttribute("ignoredArticles", ""); artistsNode.setAttribute("ignoredArticles", "");
artistsNode.setAttribute("lastModified", reportedDummyDateULong); artistsNode.setAttribute("lastModified", reportedDummyDateULong); // TODO report last file write?
Artist::FindParameters parameters;
{
auto transaction{ context.dbSession.createSharedTransaction() }; auto transaction{ context.dbSession.createSharedTransaction() };
User::pointer user{ User::find(context.dbSession, context.userId) }; User::pointer user{ User::find(context.dbSession, context.userId) };
if (!user) if (!user)
throw UserNotAuthorizedError{}; throw UserNotAuthorizedError{};
Artist::FindParameters parameters;
parameters.setSortMethod(ArtistSortMethod::BySortName); parameters.setSortMethod(ArtistSortMethod::BySortName);
switch (user->getSubsonicArtistListMode()) switch (user->getSubsonicArtistListMode())
{ {
@@ -115,13 +117,26 @@ namespace API::Subsonic
parameters.setLinkType(TrackArtistLinkType::Artist); parameters.setLinkType(TrackArtistLinkType::Artist);
break; break;
} }
}
std::map<char, std::vector<Artist::pointer>> artistsSortedByFirstChar; // This endpoint does not scale: make sort lived transactions in order not to block the whole application
// 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};
constexpr std::size_t batchSize{ 100 };
bool hasMoreArtists{ true };
while (hasMoreArtists)
{
auto transaction{ context.dbSession.createSharedTransaction() };
parameters.setRange(Range{ currentArtistOffset, batchSize });
const RangeResults<ArtistId> artists{ Artist::find(context.dbSession, parameters) }; const RangeResults<ArtistId> artists{ Artist::find(context.dbSession, parameters) };
for (const ArtistId artistId : artists.results) for (const ArtistId artistId : artists.results)
{ {
const Artist::pointer artist{ Artist::find(context.dbSession, artistId) }; const Artist::pointer artist{ Artist::find(context.dbSession, artistId) };
const std::string& sortName{ artist->getSortName() }; std::string_view sortName{ artist->getSortName() };
char sortChar; char sortChar;
if (sortName.empty() || !std::isalpha(sortName[0])) if (sortName.empty() || !std::isalpha(sortName[0]))
@@ -129,18 +144,31 @@ namespace API::Subsonic
else else
sortChar = std::toupper(sortName[0]); sortChar = std::toupper(sortName[0]);
artistsSortedByFirstChar[sortChar].push_back(artist); artistsSortedByFirstChar[sortChar].push_back(artistId);
} }
hasMoreArtists = artists.moreResults;
currentArtistOffset += artists.results.size();
}
for (const auto& [sortChar, artists] : artistsSortedByFirstChar) // second pass: add each artist
LMS_LOG(API_SUBSONIC, DEBUG) << "GetArtists: constructing response...";
for (const auto& [sortChar, artistIds] : artistsSortedByFirstChar)
{ {
Response::Node& indexNode{ artistsNode.createArrayChild("index") }; Response::Node& indexNode{ artistsNode.createArrayChild("index") };
indexNode.setAttribute("name", std::string{ sortChar }); indexNode.setAttribute("name", std::string{ sortChar });
for (const Artist::pointer& artist : artists) for (const ArtistId artistId : artistIds)
{
auto transaction{ context.dbSession.createSharedTransaction() };
User::pointer user{ User::find(context.dbSession, context.userId) };
if (!user)
throw UserNotAuthorizedError{};
if (const Artist::pointer artist{ Artist::find(context.dbSession, artistId) })
indexNode.addArrayChild("artist", createArtistNode(artist, context.dbSession, user, id3)); indexNode.addArrayChild("artist", createArtistNode(artist, context.dbSession, user, id3));
} }
}
return response; return response;
} }