Added a playlist view. Can only play the playlists for now (no save/edit)

This commit is contained in:
emeric
2022-06-26 15:12:49 +02:00
parent 93d1f66413
commit 1d233e39af
42 changed files with 563 additions and 120 deletions
+10
View File
@@ -110,6 +110,13 @@ createQuery(Session& session, const Track::FindParameters& params)
else if (params.release.isValid())
query.where("t.release_id = ?").bind(params.release);
if (params.trackList.isValid())
{
query.join("tracklist t_l ON t_l_e.tracklist_id = t_l.id");
query.join("tracklist_entry t_l_e ON t.id = t_l_e.track_id");
query.where("t_l.id = ?").bind(params.trackList);
}
switch (params.sortMethod)
{
case TrackSortMethod::None:
@@ -133,6 +140,9 @@ createQuery(Session& session, const Track::FindParameters& params)
case TrackSortMethod::Release:
query.orderBy("t.disc_number,t.track_number");
break;
case TrackSortMethod::TrackList:
assert(params.trackList.isValid());
query.orderBy("t_l.id");
}
return query;
+34 -18
View File
@@ -35,7 +35,7 @@
namespace Database {
TrackList::TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<User> user)
TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
: _name {name},
_type {type},
_isPublic {isPublic},
@@ -44,7 +44,7 @@ TrackList::TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<
}
TrackList::pointer
TrackList::create(Session& session, std::string_view name, Type type, bool isPublic, ObjectPtr<User> user)
TrackList::create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
{
session.checkUniqueLocked();
assert(user);
@@ -65,7 +65,7 @@ TrackList::getCount(Session& session)
TrackList::pointer
TrackList::find(Session& session, std::string_view name, Type type, UserId userId)
TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId)
{
session.checkSharedLocked();
assert(userId.isValid());
@@ -77,28 +77,44 @@ TrackList::find(Session& session, std::string_view name, Type type, UserId userI
}
RangeResults<TrackListId>
TrackList::find(Session& session, UserId userId, Range range)
TrackList::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<TrackListId>("SELECT id FROM tracklist")
.where("user_id = ?").bind(userId)
.orderBy("name COLLATE NOCASE")};
auto query {session.getDboSession().query<TrackListId>("SELECT DISTINCT t_l.id FROM tracklist t_l")};
return execQuery(query, range);
}
if (params.user.isValid())
query.where("t_l.user_id = ?").bind(params.user);
RangeResults<TrackListId>
TrackList::find(Session& session, UserId userId, Type type, Range range)
{
session.checkSharedLocked();
if (params.type)
query.where("t_l.type = ?").bind(*params.type);
auto query {session.getDboSession().query<TrackListId>("SELECT id FROM tracklist")
.where("user_id = ?").bind(userId)
.where("type = ?").bind(type)
.orderBy("name COLLATE NOCASE")};
if (!params.clusters.empty())
{
query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id");
query.join("track t ON t.id = t_l_e.track_id");
return execQuery(query, range);
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (const ClusterId clusterId : params.clusters)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
query.where(oss.str());
}
query.orderBy("t_l.name COLLATE NOCASE");
return execQuery(query, params.range);
}
TrackList::pointer
@@ -39,6 +39,7 @@
#include "services/database/Object.hpp"
#include "services/database/ReleaseId.hpp"
#include "services/database/TrackId.hpp"
#include "services/database/TrackListId.hpp"
#include "services/database/Types.hpp"
#include "services/database/UserId.hpp"
@@ -69,6 +70,7 @@ class Track : public Object<Track, TrackId>
EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
bool nonRelease {}; // only tracks that do not belong to a release
ReleaseId release; // matching this release
TrackListId trackList; // matching this trackList
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
@@ -79,6 +81,7 @@ class Track : public Object<Track, TrackId>
FindParameters& setArtist(ArtistId _artist, EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; }
FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; }
FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; }
FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; }
};
struct PathResult
@@ -47,14 +47,8 @@ class User;
class TrackList : public Object<TrackList, TrackListId>
{
public:
enum class Type
{
Playlist, // user controlled playlists
Internal, // internal usage (current playqueue, history, ...)
};
TrackList() = default;
TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<User> user);
TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
// Stats utility
std::vector<ObjectPtr<Artist>> getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
@@ -62,19 +56,30 @@ class TrackList : public Object<TrackList, TrackListId>
std::vector<ObjectPtr<Track>> getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
// Search utility
struct FindParameters
{
std::vector<ClusterId> clusters; // if non empty, tracklists that have tracks that belong to these clusters
Range range;
std::optional<TrackListType> type;
UserId user; // only tracklists owned by this user
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setRange(Range _range) { range = _range; return *this; }
FindParameters& setType(TrackListType _type) { type = _type; return *this; }
FindParameters& setUser(UserId _user) { user = _user; return *this; }
};
static std::size_t getCount(Session& session);
static pointer find(Session& session, std::string_view name, Type type, UserId userId);
static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId);
static pointer find(Session& session, TrackListId tracklistId);
static RangeResults<TrackListId> find(Session& session, UserId userId, Range range);
static RangeResults<TrackListId> find(Session& session, UserId userId, Type type, Range range);
static RangeResults<TrackListId> find(Session& session, const FindParameters& params);
// Create utility
static pointer create(Session& session, std::string_view name, Type type, bool isPublic, ObjectPtr<User> user);
static pointer create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
// Accessors
std::string_view getName() const { return _name; }
bool isPublic() const { return _isPublic; }
Type getType() const { return _type; }
TrackListType getType() const { return _type; }
ObjectPtr<User> getUser() const { return _user; }
// Modifiers
@@ -122,7 +127,7 @@ class TrackList : public Object<TrackList, TrackListId>
private:
std::string _name;
Type _type {Type::Playlist};
TrackListType _type {TrackListType::Playlist};
bool _isPublic {false};
Wt::Dbo::ptr<User> _user;
@@ -113,6 +113,7 @@ namespace Database
Name,
DateDescAndRelease,
Release, // order by disc/track number
TrackList, // order by asc order in tracklist
};
enum class TrackArtistLinkType
@@ -177,5 +178,11 @@ namespace Database
ReleaseArtists = 1,
TrackArtists = 2,
};
enum class TrackListType
{
Playlist, // user controlled playlists
Internal, // internal usage (current playqueue, history, ...)
};
}
+4 -4
View File
@@ -187,7 +187,7 @@ TEST_F(DatabaseFixture, Cluster_multiTracksMultipleClustersTopRelease)
ScopedRelease releaseC {session, "ReleaseC"};
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "TrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "TrackList", TrackListType::Playlist, false, user.lockAndGet()};
{
auto transaction {session.createSharedTransaction()};
@@ -618,7 +618,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters)
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()};
ScopedClusterType clusterType {session, "MyClusterType"};
ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"};
std::list<ScopedTrack> tracks;
@@ -650,7 +650,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster)
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()};
ScopedClusterType clusterType {session, "MyClusterType"};
ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"};
ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"};
@@ -702,7 +702,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters)
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClustersRecentlyPlayed)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()};
ScopedClusterType clusterType {session, "MyClusterType"};
ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"};
ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"};
+74 -7
View File
@@ -31,7 +31,7 @@ TEST_F(DatabaseFixture, SingleTrackList)
EXPECT_EQ(TrackList::getCount(session), 0);
}
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
{
auto transaction {session.createSharedTransaction()};
@@ -39,10 +39,45 @@ TEST_F(DatabaseFixture, SingleTrackList)
}
}
TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()};
ScopedTrack track {session, "MyTrack"};
{
auto transaction {session.createSharedTransaction()};
auto tracks {Track::find(session, Track::FindParameters {}.setTrackList(trackList1.getId()))};
EXPECT_EQ(tracks.results.size(), 0);
tracks = Track::find(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
EXPECT_EQ(tracks.results.size(), 0);
}
{
auto transaction {session.createUniqueTransaction()};
TrackListEntry::create(session, track.get(), trackList1.get());
}
{
auto transaction {session.createSharedTransaction()};
auto tracks {Track::find(session, Track::FindParameters {}.setTrackList(trackList1.getId()))};
ASSERT_EQ(tracks.results.size(), 1);
EXPECT_EQ(tracks.results.front(), track.getId());
tracks = Track::find(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
EXPECT_EQ(tracks.results.size(), 0);
}
}
TEST_F(DatabaseFixture, SingleTrackListMultipleTrack)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
std::list<ScopedTrack> tracks;
for (std::size_t i {}; i < 10; ++i)
@@ -84,7 +119,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrack)
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackDateTime)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
ScopedTrack track1 {session, "MyTrack1"};
ScopedTrack track2 {session, "MyTrack2"};
ScopedTrack track3 {session, "MyTrack3"};
@@ -111,7 +146,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackDateTime)
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackRecentlyPlayed)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()};
ScopedTrack track1 {session, "MyTrack1"};
ScopedTrack track2 {session, "MyTrack1"};
ScopedArtist artist1 {session, "MyArtist1"};
@@ -216,7 +251,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackRecentlyPlayed)
TEST_F(DatabaseFixture, SingleTrackList_getArtists)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
ScopedTrack track1 {session, "MyTrack1"};
ScopedTrack track2 {session, "MyTrack2"};
ScopedRelease release {session, "MyRelease"};
@@ -297,7 +332,7 @@ TEST_F(DatabaseFixture, SingleTrackList_getArtists)
TEST_F(DatabaseFixture, SingleTrackList_getReleases)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
ScopedTrack track {session, "MyTrack"};
ScopedRelease release {session, "MyRelease"};
@@ -332,7 +367,7 @@ TEST_F(DatabaseFixture, SingleTrackList_getReleases)
TEST_F(DatabaseFixture, SingleTrackList_getTracks)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
ScopedTrack track1 {session, "MyTrack1"};
ScopedTrack track2 {session, "MyTrack1"};
@@ -363,3 +398,35 @@ TEST_F(DatabaseFixture, SingleTrackList_getTracks)
ASSERT_EQ(tracks[1]->getId(), track2->getId());
}
}
TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster)
{
ScopedUser user {session, "MyUser"};
ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()};
ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()};
ScopedClusterType clusterType {session, "MyClusterType"};
ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"};
ScopedTrack track {session, "MyTrack"};
{
auto transaction {session.createSharedTransaction()};
auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()}))};
EXPECT_EQ(trackLists.results.size(), 0);
}
{
auto transaction {session.createUniqueTransaction()};
TrackListEntry::create(session, track.get(), trackList1.get());
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction {session.createSharedTransaction()};
auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()}))};
ASSERT_EQ(trackLists.results.size(), 1);
EXPECT_EQ(trackLists.results.front(), trackList1.getId());
}
}
+9 -5
View File
@@ -553,7 +553,7 @@ handleCreatePlaylistRequest(RequestContext& context)
tracklist = TrackList::find(context.dbSession, *id);
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
|| tracklist->getType() != TrackListType::Playlist)
{
throw RequestedDataNotFoundError {};
}
@@ -563,7 +563,7 @@ handleCreatePlaylistRequest(RequestContext& context)
}
else
{
tracklist = TrackList::create(context.dbSession, *name, TrackList::Type::Playlist, false, user);
tracklist = TrackList::create(context.dbSession, *name, TrackListType::Playlist, false, user);
}
for (const TrackId trackId : trackIds)
@@ -644,7 +644,7 @@ handleDeletePlaylistRequest(RequestContext& context)
TrackList::pointer tracklist {TrackList::find(context.dbSession, id)};
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
|| tracklist->getType() != TrackListType::Playlist)
{
throw RequestedDataNotFoundError {};
}
@@ -1321,7 +1321,11 @@ handleGetPlaylistsRequest(RequestContext& context)
Response response {Response::createOkResponse(context.serverProtocolVersion)};
Response::Node& playlistsNode {response.createNode("playlists")};
auto tracklistIds {TrackList::find(context.dbSession, context.userId, TrackList::Type::Playlist, Range {})};
TrackList::FindParameters params;
params.setUser(context.userId);
params.setType(TrackListType::Playlist);
auto tracklistIds {TrackList::find(context.dbSession, params)};
for (const TrackListId trackListId : tracklistIds.results)
{
const TrackList::pointer trackList {TrackList::find(context.dbSession, trackListId)};
@@ -1665,7 +1669,7 @@ handleUpdatePlaylistRequest(RequestContext& context)
TrackList::pointer tracklist {TrackList::find(context.dbSession, id)};
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
|| tracklist->getType() != TrackListType::Playlist)
{
throw RequestedDataNotFoundError {};
}
+1 -1
View File
@@ -164,7 +164,7 @@ bufferToString(const std::vector<unsigned char>& data)
}
std::string
replaceInString(const std::string& str, const std::string& from, const std::string& to)
replaceInString(std::string_view str, const std::string& from, const std::string& to)
{
std::string res {str};
size_t pos = 0;
+1 -1
View File
@@ -87,7 +87,7 @@ readAs(std::string_view str);
[[nodiscard]]
std::string
replaceInString(const std::string& str, const std::string& from, const std::string& to);
replaceInString(std::string_view str, const std::string& from, const std::string& to);
[[nodiscard]]
std::string