Can now browse by directorie in the Subsonic API, ref #474

This commit is contained in:
emeric
2024-07-13 14:22:19 +02:00
parent 9292b05732
commit 980709dbcf
20 changed files with 613 additions and 224 deletions
+31 -24
View File
@@ -24,6 +24,7 @@
#include "core/String.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Directory.hpp"
#include "database/Release.hpp"
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
@@ -39,7 +40,7 @@ namespace lms::api::subsonic
{
using namespace db;
Response::Node createAlbumNode(RequestContext& context, const Release::pointer& release, const User::pointer& user, bool id3)
Response::Node createAlbumNode(RequestContext& context, const Release::pointer& release, bool id3, const Directory::pointer& directory)
{
LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateAlbum");
@@ -47,21 +48,38 @@ namespace lms::api::subsonic
if (id3)
{
albumNode.setAttribute("id", idToString(release->getId()));
albumNode.setAttribute("name", release->getName());
albumNode.setAttribute("songCount", release->getTrackCount());
albumNode.setAttribute(
"duration", std::chrono::duration_cast<std::chrono::seconds>(
release->getDuration())
.count());
albumNode.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(release->getDuration()).count());
}
else
{
albumNode.setAttribute("title", release->getName());
Directory::pointer directoryToReport{ directory };
if (!directoryToReport)
{
Directory::FindParameters params;
params.setRelease(release->getId());
params.setRange(Range{ 0, 1 }); // only support 1 directory <-> 1 release
Directory::find(context.dbSession, params, [&](const Directory::pointer& foundDirectory) {
directoryToReport = foundDirectory;
});
}
if (directoryToReport)
{
albumNode.setAttribute("title", directoryToReport->getName());
albumNode.setAttribute("id", idToString(directoryToReport->getId()));
if (const Directory::pointer & parentDirectory{ directoryToReport->getParentDirectory() })
albumNode.setAttribute("parent", idToString(parentDirectory->getId()));
}
albumNode.setAttribute("album", release->getName());
albumNode.setAttribute("isDir", true);
}
albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getLastWritten()));
albumNode.setAttribute("id", idToString(release->getId()));
albumNode.setAttribute("coverArt", idToString(release->getId()));
if (const auto year{ release->getYear() })
albumNode.setAttribute("year", *year);
@@ -70,11 +88,7 @@ namespace lms::api::subsonic
if (artists.empty())
artists = release->getArtists();
if (artists.empty() && !id3)
{
albumNode.setAttribute("parent", idToString(RootId{}));
}
else if (!artists.empty())
if (!artists.empty())
{
if (!release->getArtistDisplayName().empty())
albumNode.setAttribute("artist", release->getArtistDisplayName());
@@ -83,16 +97,11 @@ namespace lms::api::subsonic
if (artists.size() == 1)
{
albumNode.setAttribute(id3 ? Response::Node::Key{ "artistId" } : Response::Node::Key{ "parent" }, idToString(artists.front()->getId()));
}
else
{
if (!id3)
albumNode.setAttribute("parent", idToString(RootId{}));
albumNode.setAttribute("artistId", idToString(artists.front()->getId()));
}
}
albumNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(user->getId(), release->getId()));
albumNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.user->getId(), release->getId()));
// Report the first GENRE for this track
const ClusterType::pointer genreClusterType{ ClusterType::find(context.dbSession, "GENRE") };
@@ -103,7 +112,7 @@ namespace lms::api::subsonic
albumNode.setAttribute("genre", clusters.front().front()->getName());
}
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(user->getId(), release->getId()) }; dateTime.isValid())
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));
if (!context.enableOpenSubsonic)
@@ -111,12 +120,10 @@ namespace lms::api::subsonic
// OpenSubsonic specific fields (must always be set)
albumNode.setAttribute("sortName", release->getSortName());
if (!id3)
albumNode.setAttribute("mediaType", "album");
albumNode.setAttribute("mediaType", "album");
{
const Wt::WDateTime dateTime{ core::Service<scrobbling::IScrobblingService>::get()->getLastListenDateTime(user->getId(), release->getId()) };
const Wt::WDateTime dateTime{ core::Service<scrobbling::IScrobblingService>::get()->getLastListenDateTime(context.user->getId(), release->getId()) };
albumNode.setAttribute("played", dateTime.isValid() ? core::stringUtils::toISO8601String(dateTime) : std::string{ "" });
}
+2 -1
View File
@@ -25,6 +25,7 @@
namespace lms::db
{
class Directory;
class Release;
class User;
class Session;
@@ -32,5 +33,5 @@ namespace lms::db
namespace lms::api::subsonic
{
Response::Node createAlbumNode(RequestContext& context, const db::ObjectPtr<db::Release>& release, const db::ObjectPtr<db::User>& user, bool id3);
Response::Node createAlbumNode(RequestContext& context, const db::ObjectPtr<db::Release>& release, bool id3, const db::ObjectPtr<db::Directory>& directory = {});
}
+5 -9
View File
@@ -85,7 +85,7 @@ namespace lms::api::subsonic
}
} // namespace utils
Response::Node createArtistNode(RequestContext& context, const Artist::pointer& artist, const User::pointer& user, bool id3)
Response::Node createArtistNode(RequestContext& context, const Artist::pointer& artist)
{
LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateArtist");
@@ -96,20 +96,16 @@ namespace lms::api::subsonic
if (const db::Image::pointer artistImage{ artist->getImage() })
artistNode.setAttribute("coverArt", idToString(artist->getId()));
if (id3)
{
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
artistNode.setAttribute("albumCount", count);
}
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
artistNode.setAttribute("albumCount", count);
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(user->getId(), artist->getId()) }; dateTime.isValid())
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));
// OpenSubsonic specific fields (must always be set)
if (context.enableOpenSubsonic)
{
if (!id3)
artistNode.setAttribute("mediaType", "artist");
artistNode.setAttribute("mediaType", "artist");
{
std::optional<core::UUID> mbid{ artist->getMBID() };
+2 -1
View File
@@ -41,6 +41,7 @@ namespace lms::api::subsonic
std::string joinArtistNames(const std::vector<db::ObjectPtr<db::Artist>>& artists);
std::string_view toString(db::TrackArtistLinkType type);
} // namespace utils
Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr<db::Artist>& artist, const db::ObjectPtr<db::User>& user, bool id3);
Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr<db::Artist>& artist);
Response::Node createArtistNode(const db::ObjectPtr<db::Artist>& artist); // only minimal info
} // namespace lms::api::subsonic
+15 -8
View File
@@ -27,6 +27,7 @@
#include "core/String.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Directory.hpp"
#include "database/Release.hpp"
#include "database/Track.hpp"
#include "database/TrackArtistLink.hpp"
@@ -67,14 +68,20 @@ namespace lms::api::subsonic
}
} // namespace
Response::Node createSongNode(RequestContext& context, const Track::pointer& track, const User::pointer& user)
Response::Node createSongNode(RequestContext& context, const Track::pointer& track, bool id3)
{
LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateSong");
Response::Node trackResponse;
if (!id3)
{
if (const auto directory{ track->getDirectory() })
trackResponse.setAttribute("parent", idToString(directory->getId()));
trackResponse.setAttribute("isDir", false);
}
trackResponse.setAttribute("id", idToString(track->getId()));
trackResponse.setAttribute("isDir", false);
trackResponse.setAttribute("title", track->getName());
if (track->getTrackNumber())
trackResponse.setAttribute("track", *track->getTrackNumber());
@@ -82,7 +89,7 @@ namespace lms::api::subsonic
trackResponse.setAttribute("discNumber", *track->getDiscNumber());
if (track->getYear())
trackResponse.setAttribute("year", *track->getYear());
trackResponse.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(user->getId(), track->getId()));
trackResponse.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.user->getId(), track->getId()));
trackResponse.setAttribute("path", track->getRelativeFilePath().string());
trackResponse.setAttribute("size", track->getFileSize());
@@ -93,12 +100,13 @@ namespace lms::api::subsonic
}
{
const std::string fileSuffix{ formatToSuffix(user->getSubsonicDefaultTranscodingOutputFormat()) };
const std::string fileSuffix{ formatToSuffix(context.user->getSubsonicDefaultTranscodingOutputFormat()) };
trackResponse.setAttribute("transcodedSuffix", fileSuffix);
trackResponse.setAttribute("transcodedContentType", av::getMimeType(std::filesystem::path{ "." + fileSuffix }));
}
trackResponse.setAttribute("coverArt", idToString(track->getId()));
if (track->hasCover())
trackResponse.setAttribute("coverArt", idToString(track->getId()));
const std::vector<Artist::pointer>& artists{ track->getArtists({ TrackArtistLinkType::Artist }) };
if (!artists.empty())
@@ -117,7 +125,6 @@ namespace lms::api::subsonic
{
trackResponse.setAttribute("album", release->getName());
trackResponse.setAttribute("albumId", idToString(release->getId()));
trackResponse.setAttribute("parent", idToString(release->getId()));
}
trackResponse.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count());
@@ -126,7 +133,7 @@ namespace lms::api::subsonic
trackResponse.setAttribute("created", core::stringUtils::toISO8601String(track->getLastWritten()));
trackResponse.setAttribute("contentType", av::getMimeType(track->getAbsoluteFilePath().extension()));
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(user->getId(), track->getId()) }; dateTime.isValid())
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));
// Report the first GENRE for this track
@@ -152,7 +159,7 @@ namespace lms::api::subsonic
trackResponse.setAttribute("mediaType", "song");
{
const Wt::WDateTime dateTime{ core::Service<scrobbling::IScrobblingService>::get()->getLastListenDateTime(user->getId(), track->getId()) };
const Wt::WDateTime dateTime{ core::Service<scrobbling::IScrobblingService>::get()->getLastListenDateTime(context.user->getId(), track->getId()) };
trackResponse.setAttribute("played", dateTime.isValid() ? core::stringUtils::toISO8601String(dateTime) : "");
}
+1 -1
View File
@@ -32,5 +32,5 @@ namespace lms::db
namespace lms::api::subsonic
{
Response::Node createSongNode(RequestContext& context, const db::ObjectPtr<db::Track>& track, const db::ObjectPtr<db::User>& user);
Response::Node createSongNode(RequestContext& context, const db::ObjectPtr<db::Track>& track, bool id3);
}