Subsonic API: added playCount
This commit is contained in:
@@ -140,6 +140,7 @@ namespace Database
|
||||
|
||||
Listen::pointer Listen::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track, ScrobblingBackend backend, const Wt::WDateTime& dateTime)
|
||||
{
|
||||
session.checkUniqueLocked();
|
||||
return session.getDboSession().add(std::unique_ptr<Listen> {new Listen{ user, track, backend, dateTime }});
|
||||
}
|
||||
|
||||
@@ -188,6 +189,7 @@ namespace Database
|
||||
|
||||
RangeResults<ArtistId> Listen::getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType) };
|
||||
|
||||
auto collection{ query
|
||||
@@ -199,6 +201,7 @@ namespace Database
|
||||
|
||||
RangeResults<ReleaseId> Listen::getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds)
|
||||
.orderBy("COUNT(r.id) DESC")
|
||||
.groupBy("r.id") };
|
||||
@@ -208,6 +211,7 @@ namespace Database
|
||||
|
||||
RangeResults<TrackId> Listen::getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds)
|
||||
.orderBy("COUNT(t.id) DESC")
|
||||
.groupBy("t.id") };
|
||||
@@ -217,6 +221,7 @@ namespace Database
|
||||
|
||||
RangeResults<ArtistId> Listen::getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType)
|
||||
.groupBy("a.id").having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
@@ -226,6 +231,7 @@ namespace Database
|
||||
|
||||
RangeResults<ReleaseId> Listen::getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds)
|
||||
.groupBy("r.id").having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
@@ -235,6 +241,7 @@ namespace Database
|
||||
|
||||
RangeResults<TrackId> Listen::getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds)
|
||||
.groupBy("t.id").having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
@@ -242,8 +249,38 @@ namespace Database
|
||||
return Utils::execQuery(query, range);
|
||||
}
|
||||
|
||||
std::size_t Listen::getCount(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
return session.getDboSession().query<int>("SELECT COUNT(*) from listen l")
|
||||
.where("l.track_id = ?").bind(trackId)
|
||||
.where("l.user_id = ?").bind(userId)
|
||||
.where("l.backend = ?").bind(backend)
|
||||
.resultValue();
|
||||
}
|
||||
|
||||
std::size_t Listen::getCount(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
return session.getDboSession().query<int>("SELECT IFNULL(MIN(count_result), 0)"
|
||||
" FROM ("
|
||||
" SELECT COUNT(l.track_id) AS count_result"
|
||||
" FROM track t"
|
||||
" LEFT JOIN listen l ON t.id = l.track_id AND l.backend = ? AND l.user_id = ?"
|
||||
" WHERE t.release_id = ?"
|
||||
" GROUP BY t.id)")
|
||||
.bind(backend)
|
||||
.bind(userId)
|
||||
.bind(releaseId)
|
||||
.resultValue();
|
||||
}
|
||||
|
||||
Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
// TODO not pending remove?
|
||||
return session.getDboSession().query<Wt::Dbo::ptr<Listen>>("SELECT l from listen l")
|
||||
.join("track t ON l.track_id = t.id")
|
||||
@@ -257,10 +294,10 @@ namespace Database
|
||||
|
||||
Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
// TODO not pending remove?
|
||||
return session.getDboSession().query<Wt::Dbo::ptr<Listen>>("SELECT l from listen l")
|
||||
.join("track t ON track_id = t.id")
|
||||
.where("t.id = ?").bind(trackId)
|
||||
.where("l.track_id = ?").bind(trackId)
|
||||
.where("l.user_id = ?").bind(userId)
|
||||
.where("l.backend = ?").bind(backend)
|
||||
.orderBy("l.date_time DESC")
|
||||
|
||||
@@ -160,6 +160,7 @@ namespace Database
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS track_bookmark_user_track_idx ON track_bookmark(user_id,track_id)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS listen_backend_idx ON listen(backend)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS listen_user_backend_idx ON listen(user_id,backend)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS listen_track_user_backend_idx ON listen(track_id,user_id,backend)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS listen_user_track_backend_date_time_idx ON listen(user_id,track_id,backend,date_time)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS starred_artist_user_backend_idx ON starred_artist(user_id,backend)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS starred_artist_artist_user_backend_idx ON starred_artist(artist_id,user_id,backend)");
|
||||
|
||||
@@ -71,10 +71,13 @@ namespace Database
|
||||
static RangeResults<ReleaseId> getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range = {});
|
||||
static RangeResults<TrackId> getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range = {});
|
||||
|
||||
static std::size_t getCount(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId);
|
||||
static std::size_t getCount(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId trackId);
|
||||
|
||||
static pointer getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId);
|
||||
static pointer getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId releaseId);
|
||||
|
||||
SyncState getSyncState() const { return _syncState; }
|
||||
SyncState getSyncState() const { return _syncState; }
|
||||
ObjectPtr<User> getUser() const { return _user; }
|
||||
ObjectPtr<Track> getTrack() const { return _track; }
|
||||
const Wt::WDateTime& getDateTime() const { return _dateTime; }
|
||||
|
||||
@@ -291,7 +291,7 @@ TEST_F(DatabaseFixture, Listen_getTopReleases)
|
||||
const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ TEST_F(DatabaseFixture, Listen_getTopReleases_multi)
|
||||
ScopedRelease release2{ session, "MyRelease2" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track1.get().modify()->setRelease(release1.get());
|
||||
track2.get().modify()->setRelease(release2.get());
|
||||
}
|
||||
@@ -381,7 +381,7 @@ TEST_F(DatabaseFixture, Listen_getTopReleases_cluster)
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
@@ -652,7 +652,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases)
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
@@ -692,7 +692,7 @@ TEST_F(DatabaseFixture, Listen_getMostRecentRelease)
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
@@ -746,7 +746,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_multi)
|
||||
ScopedRelease release2{ session, "MyRelease2" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track1.get().modify()->setRelease(release1.get());
|
||||
track2.get().modify()->setRelease(release2.get());
|
||||
}
|
||||
@@ -806,7 +806,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_cluster)
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
{
|
||||
@@ -873,6 +873,71 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Listen_getCount_track)
|
||||
{
|
||||
ScopedTrack track{ session, "MyTrack" };
|
||||
ScopedUser user{ session, "MyUser" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
const std::size_t count{ Listen::getCount(session, user->getId(), ScrobblingBackend::Internal, track.getId()) };
|
||||
EXPECT_EQ(count, 0);
|
||||
}
|
||||
|
||||
const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} };
|
||||
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
|
||||
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
const std::size_t count{ Listen::getCount(session, user->getId(), ScrobblingBackend::Internal, track.getId()) };
|
||||
EXPECT_EQ(count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Listen_getCount_release)
|
||||
{
|
||||
ScopedTrack track1{ session, "MyTrack" };
|
||||
ScopedTrack track2{ session, "MyTrack" };
|
||||
ScopedUser user{ session, "MyUser" };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
auto getReleaseListenCount{ [&]
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
return Listen::getCount(session, user->getId(), ScrobblingBackend::Internal, release.getId());
|
||||
} };
|
||||
|
||||
EXPECT_EQ(getReleaseListenCount(), 0);
|
||||
|
||||
const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} };
|
||||
ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
|
||||
|
||||
EXPECT_EQ(getReleaseListenCount(), 0);
|
||||
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track1.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
EXPECT_EQ(getReleaseListenCount(), 1);
|
||||
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track2.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
EXPECT_EQ(getReleaseListenCount(), 0);
|
||||
ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
|
||||
EXPECT_EQ(getReleaseListenCount(), 1);
|
||||
ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
|
||||
EXPECT_EQ(getReleaseListenCount(), 1);
|
||||
|
||||
ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
|
||||
EXPECT_EQ(getReleaseListenCount(), 2);
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Listen_getMostRecentTrack)
|
||||
{
|
||||
ScopedTrack track{ session, "MyTrack" };
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
|
||||
#include "responses/Album.hpp"
|
||||
|
||||
#include "services/database/Cluster.hpp"
|
||||
#include "services/database/Artist.hpp"
|
||||
#include "services/database/Cluster.hpp"
|
||||
#include "services/database/Listen.hpp"
|
||||
#include "services/database/Release.hpp"
|
||||
#include "services/database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
@@ -124,6 +125,8 @@ namespace API::Subsonic
|
||||
}
|
||||
}
|
||||
|
||||
albumNode.setAttribute("playCount", Listen::getCount(context.dbSession, user->getId(), user->getScrobblingBackend(), release->getId()));
|
||||
|
||||
// Report the first GENRE for this track
|
||||
const ClusterType::pointer genreClusterType{ ClusterType::find(context.dbSession, "GENRE") };
|
||||
if (genreClusterType)
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "services/database/Artist.hpp"
|
||||
#include "services/database/Cluster.hpp"
|
||||
#include "services/database/Listen.hpp"
|
||||
#include "services/database/Release.hpp"
|
||||
#include "services/database/Track.hpp"
|
||||
#include "services/database/TrackArtistLink.hpp"
|
||||
@@ -106,7 +107,7 @@ namespace API::Subsonic
|
||||
trackResponse.setAttribute("discNumber", *track->getDiscNumber());
|
||||
if (track->getYear())
|
||||
trackResponse.setAttribute("year", *track->getYear());
|
||||
|
||||
trackResponse.setAttribute("playCount", Listen::getCount(context.dbSession, user->getId(), user->getScrobblingBackend(), track->getId()));
|
||||
trackResponse.setAttribute("path", getTrackPath(track));
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
Reference in New Issue
Block a user