Implemented Subsonic's user rating for tracks, albums and artists, fixes #511

This commit is contained in:
emeric
2024-08-30 19:40:15 +02:00
parent cd0e88d28d
commit 29d0610eb2
30 changed files with 991 additions and 5 deletions
+1 -1
View File
@@ -206,7 +206,7 @@ namespace lms::api::subsonic
// Media annotation
{ "/star", { handleStarRequest } },
{ "/unstar", { handleUnstarRequest } },
{ "/setRating", { handleNotImplemented } },
{ "/setRating", { handleSetRating } },
{ "/scrobble", { handleScrobble } },
// Sharing
@@ -19,6 +19,7 @@
#include "MediaAnnotation.hpp"
#include <variant>
#include <vector>
#include "core/Service.hpp"
@@ -77,6 +78,37 @@ namespace lms::api::subsonic
return res ? res->getId() : ReleaseId{};
}
struct RatingParameters
{
std::variant<ArtistId, ReleaseId, TrackId, DirectoryId> id;
std::optional<Rating> rating;
};
RatingParameters getRatingParameters(const Wt::Http::ParameterMap& parameters)
{
RatingParameters res;
if (const auto artistId{ getParameterAs<ArtistId>(parameters, "id") })
res.id = *artistId;
else if (const auto releaseId{ getParameterAs<ReleaseId>(parameters, "id") })
res.id = *releaseId;
else if (const auto trackId{ getParameterAs<TrackId>(parameters, "id") })
res.id = *trackId;
else if (const auto directoryId{ getParameterAs<DirectoryId>(parameters, "id") })
res.id = *directoryId;
else
throw RequiredParameterMissingError{ "id" };
const int rating = getMandatoryParameterAs<int>(parameters, "rating"); // The rating between 1 and 5 (inclusive), or 0 to remove the rating
if (rating < 0 || rating > 5)
throw BadParameterGenericError{ "rating must be 0 or in range 1-5" };
if (rating > 0)
res.rating = rating;
return res;
}
} // namespace
Response handleStarRequest(RequestContext& context)
@@ -103,7 +135,7 @@ namespace lms::api::subsonic
Response handleUnstarRequest(RequestContext& context)
{
StarParameters params{ getStarParameters(context.parameters) };
const StarParameters params{ getStarParameters(context.parameters) };
for (const DirectoryId id : params.directoryIds)
{
@@ -123,6 +155,25 @@ namespace lms::api::subsonic
return Response::createOkResponse(context.serverProtocolVersion);
}
Response handleSetRating(RequestContext& context)
{
const RatingParameters params{ getRatingParameters(context.parameters) };
if (const ArtistId * artistId{ std::get_if<ArtistId>(&params.id) })
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), *artistId, params.rating);
else if (const DirectoryId * directoryId{ std::get_if<DirectoryId>(&params.id) })
{
if (const ReleaseId releaseId{ getReleaseFromDirectory(context.dbSession, *directoryId) }; releaseId.isValid())
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), releaseId, params.rating);
}
else if (const ReleaseId * releaseId{ std::get_if<ReleaseId>(&params.id) })
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), *releaseId, params.rating);
else if (const TrackId * trackId{ std::get_if<TrackId>(&params.id) })
core::Service<feedback::IFeedbackService>::get()->setRating(context.user->getId(), *trackId, params.rating);
return Response::createOkResponse(context.serverProtocolVersion);
}
Response handleScrobble(RequestContext& context)
{
const std::vector<TrackId> ids{ getMandatoryMultiParametersAs<TrackId>(context.parameters, "id") };
@@ -26,5 +26,6 @@ namespace lms::api::subsonic
{
Response handleStarRequest(RequestContext& context);
Response handleUnstarRequest(RequestContext& context);
Response handleSetRating(RequestContext& context);
Response handleScrobble(RequestContext& context);
} // namespace lms::api::subsonic
@@ -116,6 +116,10 @@ namespace lms::api::subsonic
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.user->getId(), release->getId()) }; dateTime.isValid())
albumNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
// Always report user rating, even if legacy API only specified it for directories
if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.user->getId(), release->getId()) })
albumNode.setAttribute("userRating", *rating);
if (!context.enableOpenSubsonic)
return albumNode;
@@ -103,6 +103,9 @@ namespace lms::api::subsonic
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.user->getId(), artist->getId()) }; dateTime.isValid())
artistNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.user->getId(), artist->getId()) })
artistNode.setAttribute("userRating", *rating);
// OpenSubsonic specific fields (must always be set)
if (context.enableOpenSubsonic)
{
@@ -133,6 +133,8 @@ namespace lms::api::subsonic
trackResponse.setAttribute("type", "music");
trackResponse.setAttribute("created", core::stringUtils::toISO8601String(track->getLastWritten()));
trackResponse.setAttribute("contentType", av::getMimeType(track->getAbsoluteFilePath().extension()));
if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.user->getId(), track->getId()) })
trackResponse.setAttribute("userRating", *rating);
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.user->getId(), track->getId()) }; dateTime.isValid())
trackResponse.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));