Initial support for multi library, still WIP

This commit is contained in:
emeric
2024-01-25 00:01:11 +01:00
parent f5f577af52
commit 0ed3f1ba79
85 changed files with 3076 additions and 1445 deletions
+15
View File
@@ -31,6 +31,12 @@ namespace API::Subsonic
return "ar-" + id.toString();
}
std::string idToString(Database::MediaLibraryId id)
{
// No need to prefix as this is only used at well known places
return id.toString();
}
std::string idToString(Database::ReleaseId id)
{
return "al-" + id.toString();
@@ -70,6 +76,15 @@ namespace StringUtils
return std::nullopt;
}
template<>
std::optional<Database::MediaLibraryId> readAs(std::string_view str)
{
if (const auto value{ StringUtils::readAs<Database::MediaLibraryId::ValueType>(str) })
return Database::MediaLibraryId{ *value };
return std::nullopt;
}
template<>
std::optional<Database::ReleaseId> readAs(std::string_view str)
{
+5
View File
@@ -20,6 +20,7 @@
#pragma once
#include "database/ArtistId.hpp"
#include "database/MediaLibraryId.hpp"
#include "database/ReleaseId.hpp"
#include "database/TrackId.hpp"
#include "database/TrackListId.hpp"
@@ -30,6 +31,7 @@ namespace API::Subsonic
struct RootId {};
std::string idToString(Database::ArtistId id);
std::string idToString(Database::MediaLibraryId id);
std::string idToString(Database::ReleaseId id);
std::string idToString(Database::TrackId id);
std::string idToString(Database::TrackListId id);
@@ -45,6 +47,9 @@ namespace StringUtils
template<>
std::optional<Database::ArtistId> readAs(std::string_view str);
template<>
std::optional<Database::MediaLibraryId> readAs(std::string_view str);
template<>
std::optional<Database::ReleaseId> readAs(std::string_view str);
@@ -32,6 +32,7 @@
#include "responses/Song.hpp"
#include "utils/Service.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
namespace API::Subsonic
{
@@ -45,6 +46,7 @@ namespace API::Subsonic
const std::string type{ getMandatoryParameterAs<std::string>(context.parameters, "type") };
// Optional params
const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
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 > defaultMaxCountSize)
@@ -67,12 +69,18 @@ namespace API::Subsonic
Release::FindParameters params;
params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range);
params.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params);
}
else if (type == "alphabeticalByArtist")
{
releases = Release::findIdsOrderedByArtist(context.dbSession, range);
Release::FindParameters params;
params.setSortMethod(ReleaseSortMethod::ArtistNameThenName);
params.setRange(range);
params.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params);
}
else if (type == "byGenre")
{
@@ -87,6 +95,7 @@ namespace API::Subsonic
params.setClusters({ cluster->getId() });
params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range);
params.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params);
}
@@ -101,18 +110,25 @@ namespace API::Subsonic
params.setSortMethod(ReleaseSortMethod::Date);
params.setRange(range);
params.setDateRange(DateRange::fromYearRange(fromYear, toYear));
params.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params);
}
else if (type == "frequent")
{
releases = scrobblingService.getTopReleases(context.userId, {}, range);
Scrobbling::IScrobblingService::FindParameters params;
params.setUser(context.userId);
params.setRange(range);
params.setMediaLibrary(mediaLibraryId);
releases = scrobblingService.getTopReleases(params);
}
else if (type == "newest")
{
Release::FindParameters params;
params.setSortMethod(ReleaseSortMethod::LastWritten);
params.setRange(range);
params.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params);
}
@@ -123,18 +139,26 @@ namespace API::Subsonic
Release::FindParameters params;
params.setSortMethod(ReleaseSortMethod::Random);
params.setRange(Range{ 0, size });
params.setMediaLibrary(mediaLibraryId);
releases = Release::findIds(context.dbSession, params);
}
else if (type == "recent")
{
releases = scrobblingService.getRecentReleases(context.userId, {}, range);
Scrobbling::IScrobblingService::FindParameters params;
params.setUser(context.userId);
params.setRange(range);
params.setMediaLibrary(mediaLibraryId);
releases = scrobblingService.getRecentReleases(params);
}
else if (type == "starred")
{
Feedback::IFeedbackService::FindParameters params;
params.setUser(context.userId);
params.setRange(range);
params.setMediaLibrary(mediaLibraryId);
releases = feedbackService.findStarredReleases(params);
}
else
@@ -156,6 +180,9 @@ namespace API::Subsonic
Response handleGetStarredRequestCommon(RequestContext& context, bool id3)
{
// Optional parameters
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
auto transaction{ context.dbSession.createReadTransaction() };
User::pointer user{ User::find(context.dbSession, context.userId) };
@@ -167,9 +194,6 @@ namespace API::Subsonic
Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() };
Feedback::IFeedbackService::FindParameters findParameters;
findParameters.setUser(context.userId);
{
Feedback::IFeedbackService::ArtistFindParameters artistFindParams;
artistFindParams.setUser(context.userId);
@@ -181,6 +205,10 @@ namespace API::Subsonic
}
}
Feedback::IFeedbackService::FindParameters findParameters;
findParameters.setUser(context.userId);
findParameters.setMediaLibrary(mediaLibrary);
for (const ReleaseId releaseId : feedbackService.findStarredReleases(findParameters).results)
{
if (auto release{ Release::find(context.dbSession, releaseId) })
@@ -210,6 +238,7 @@ namespace API::Subsonic
Response handleGetRandomSongsRequest(RequestContext& context)
{
// Optional params
const MediaLibraryId mediaLibraryId{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(50) };
if (size > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
@@ -226,6 +255,7 @@ namespace API::Subsonic
Track::FindParameters params;
params.setSortMethod(TrackSortMethod::Random);
params.setRange(Range{ 0, size });
params.setMediaLibrary(mediaLibraryId);
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{
@@ -241,6 +271,7 @@ namespace API::Subsonic
std::string genre{ getMandatoryParameterAs<std::string>(context.parameters, "genre") };
// Optional params
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(10) };
if (count > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{"count", defaultMaxCountSize};
@@ -267,6 +298,7 @@ namespace API::Subsonic
Track::FindParameters params;
params.setClusters({ cluster->getId() });
params.setRange(Range{ offset, count });
params.setMediaLibrary(mediaLibrary);
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{
@@ -21,6 +21,7 @@
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/MediaLibrary.hpp"
#include "database/Session.hpp"
#include "database/Release.hpp"
#include "database/Track.hpp"
@@ -92,6 +93,9 @@ namespace API::Subsonic
Response handleGetArtistsRequestCommon(RequestContext& context, bool id3)
{
// Optional params
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& artistsNode{ response.createNode(id3 ? "artists" : "indexes") };
@@ -119,6 +123,7 @@ namespace API::Subsonic
break;
}
}
parameters.setMediaLibrary(mediaLibrary);
// This endpoint does not scale: make sort lived transactions in order not to block the whole application
@@ -283,9 +288,14 @@ namespace API::Subsonic
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& musicFoldersNode{ response.createNode("musicFolders") };
Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") };
musicFolderNode.setAttribute("id", "0");
musicFolderNode.setAttribute("name", "Music");
auto transaction{ context.dbSession.createReadTransaction() };
MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library)
{
Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") };
musicFolderNode.setAttribute("id", idToString(library->getId()));
musicFolderNode.setAttribute("name", library->getName());
});
return response;
}
@@ -502,7 +512,12 @@ namespace API::Subsonic
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& topSongs{ response.createNode("topSongs") };
const auto trackIds{ Service<Scrobbling::IScrobblingService>::get()->getTopTracks(context.userId, artists.front()->getId(), {}, Database::Range{ 0, count }) };
Scrobbling::IScrobblingService::FindParameters params;
params.setUser(context.userId);
params.setRange(Database::Range{ 0, count });
params.setArtist(artists.front()->getId());
const auto trackIds{ Service<Scrobbling::IScrobblingService>::get()->getTopTracks(params) };
for (const TrackId trackId : trackIds.results)
{
if (Track::pointer track{ Track::find(context.dbSession, trackId) })
@@ -28,9 +28,8 @@
#include "responses/Artist.hpp"
#include "responses/Song.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
#include "ParameterParsing.hpp"
namespace API::Subsonic
{
using namespace Database;
@@ -43,6 +42,9 @@ namespace API::Subsonic
std::string queryString{ getMandatoryParameterAs<std::string>(context.parameters, "query") };
std::string_view query{ queryString };
// Optional params
const MediaLibraryId mediaLibrary{ getParameterAs<MediaLibraryId>(context.parameters, "musicFolderId").value_or(MediaLibraryId{}) };
// Symfonium adds extra ""
if (context.clientInfo.name == "Symfonium")
query = StringUtils::stringTrim(query, "\"");
@@ -78,6 +80,7 @@ namespace API::Subsonic
Artist::FindParameters params;
params.setKeywords(keywords);
params.setRange(Range{ artistOffset, artistCount });
params.setMediaLibrary(mediaLibrary);
Artist::find(context.dbSession, params, [&](const Artist::pointer& artist)
{
@@ -90,6 +93,7 @@ namespace API::Subsonic
Release::FindParameters params;
params.setKeywords(keywords);
params.setRange(Range{ albumOffset, albumCount });
params.setMediaLibrary(mediaLibrary);
Release::find(context.dbSession, params, [&](const Release::pointer& release)
{
@@ -102,6 +106,7 @@ namespace API::Subsonic
Track::FindParameters params;
params.setKeywords(keywords);
params.setRange(Range{ songOffset, songCount });
params.setMediaLibrary(mediaLibrary);
Track::find(context.dbSession, params, [&](const Track::pointer& track)
{