diff --git a/SUBSONIC.md b/SUBSONIC.md index ba3ba36f..de749e9e 100644 --- a/SUBSONIC.md +++ b/SUBSONIC.md @@ -11,6 +11,7 @@ OpenSubsonic is an initiative to patch and extend the legacy Subsonic API. You'l ## Extra fields The following extra fields are implemented: * `Album` response: + * `played` * `musicBrainzId` * `genres` * `artists` @@ -21,6 +22,7 @@ The following extra fields are implemented: * `isCompilation` * `discTitles`: discs with no subtitle are omitted * `Child` response: + * `played` * `musicBrainzId`: note this is actually the recording MBID when this response refers to a song * `genres` * `artists` diff --git a/src/libs/services/database/impl/Listen.cpp b/src/libs/services/database/impl/Listen.cpp index 8e37711c..d77ef827 100644 --- a/src/libs/services/database/impl/Listen.cpp +++ b/src/libs/services/database/impl/Listen.cpp @@ -27,261 +27,245 @@ namespace { - using namespace Database; + using namespace Database; - Wt::Dbo::Query - createArtistsQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, std::optional linkType) - { - auto query {session.query("SELECT a.id from artist a") - .join("track t ON t.id = t_a_l.track_id") - .join("track_artist_link t_a_l ON t_a_l.artist_id = a.id") - .join("listen l ON l.track_id = t.id") - .where("l.user_id = ?").bind(userId) - .where("l.scrobbler = ?").bind(scrobbler)}; + Wt::Dbo::Query createArtistsQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, std::optional linkType) + { + auto query{ session.query("SELECT a.id from artist a") + .join("track t ON t.id = t_a_l.track_id") + .join("track_artist_link t_a_l ON t_a_l.artist_id = a.id") + .join("listen l ON l.track_id = t.id") + .where("l.user_id = ?").bind(userId) + .where("l.scrobbler = ?").bind(scrobbler) }; - if (linkType) - query.where("t_a_l.type = ?").bind(*linkType); + if (linkType) + query.where("t_a_l.type = ?").bind(*linkType); - if (!clusterIds.empty()) - { - std::ostringstream oss; - oss << "a.id IN (SELECT DISTINCT a.id FROM artist a" - " INNER JOIN track t ON t.id = t_a_l.track_id" - " INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" - " INNER JOIN cluster c ON c.id = t_c.cluster_id" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + if (!clusterIds.empty()) + { + std::ostringstream oss; + oss << "a.id IN (SELECT DISTINCT a.id FROM artist a" + " INNER JOIN track t ON t.id = t_a_l.track_id" + " INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" + " INNER JOIN cluster c ON c.id = t_c.cluster_id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; - WhereClause clusterClause; - for (auto id : clusterIds) - { - clusterClause.Or(WhereClause("c.id = ?")); - query.bind(id); - } + WhereClause clusterClause; + for (auto id : clusterIds) + { + clusterClause.Or(WhereClause("c.id = ?")); + query.bind(id); + } - oss << " " << clusterClause.get(); - oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; + oss << " " << clusterClause.get(); + oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; - query.where(oss.str()); - } + query.where(oss.str()); + } - return query; - } + return query; + } - Wt::Dbo::Query - createReleasesQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds) - { - auto query {session.query("SELECT r.id from release r") - .join("track t ON t.release_id = r.id") - .join("listen l ON l.track_id = t.id") - .where("l.user_id = ?").bind(userId) - .where("l.scrobbler = ?").bind(scrobbler)}; + Wt::Dbo::Query createReleasesQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds) + { + auto query{ session.query("SELECT r.id from release r") + .join("track t ON t.release_id = r.id") + .join("listen l ON l.track_id = t.id") + .where("l.user_id = ?").bind(userId) + .where("l.scrobbler = ?").bind(scrobbler) }; - if (!clusterIds.empty()) - { - std::ostringstream oss; - oss << "r.id IN (SELECT DISTINCT r.id FROM release r" - " INNER JOIN track t ON t.release_id = r.id" - " INNER JOIN cluster c ON c.id = t_c.cluster_id" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + if (!clusterIds.empty()) + { + std::ostringstream oss; + oss << "r.id IN (SELECT DISTINCT r.id FROM release r" + " INNER JOIN track t ON t.release_id = r.id" + " INNER JOIN cluster c ON c.id = t_c.cluster_id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; - WhereClause clusterClause; - for (ClusterId id : clusterIds) - { - clusterClause.Or(WhereClause("c.id = ?")); - query.bind(id); - } + WhereClause clusterClause; + for (ClusterId id : clusterIds) + { + clusterClause.Or(WhereClause("c.id = ?")); + query.bind(id); + } - oss << " " << clusterClause.get(); - oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; + oss << " " << clusterClause.get(); + oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; - query.where(oss.str()); - } + query.where(oss.str()); + } - return query; - } + return query; + } - Wt::Dbo::Query - createTracksQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds) - { - auto query {session.query("SELECT t.id from track t") - .join("listen l ON l.track_id = t.id") - .where("l.user_id = ?").bind(userId) - .where("l.scrobbler = ?").bind(scrobbler)}; + Wt::Dbo::Query createTracksQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds) + { + auto query{ session.query("SELECT t.id from track t") + .join("listen l ON l.track_id = t.id") + .where("l.user_id = ?").bind(userId) + .where("l.scrobbler = ?").bind(scrobbler) }; - if (!clusterIds.empty()) - { - 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"; + if (!clusterIds.empty()) + { + 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 (auto id : clusterIds) - { - clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString()); - query.bind(id); - } + WhereClause clusterClause; + for (auto id : clusterIds) + { + clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString()); + query.bind(id); + } - oss << " " << clusterClause.get(); - oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")"; + oss << " " << clusterClause.get(); + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")"; - query.where(oss.str()); - } + query.where(oss.str()); + } - return query; - } + return query; + } } namespace Database { - Listen::Listen(ObjectPtr user, ObjectPtr track, Scrobbler scrobbler, const Wt::WDateTime& dateTime) - : _dateTime {Wt::WDateTime::fromTime_t(dateTime.toTime_t())} - , _scrobbler {scrobbler} - , _user {getDboPtr(user)} - , _track {getDboPtr(track)} - {} + Listen::Listen(ObjectPtr user, ObjectPtr track, Scrobbler scrobbler, const Wt::WDateTime& dateTime) + : _dateTime{ Wt::WDateTime::fromTime_t(dateTime.toTime_t()) } + , _scrobbler{ scrobbler } + , _user{ getDboPtr(user) } + , _track{ getDboPtr(track) } + {} - Listen::pointer - Listen::create(Session& session, ObjectPtr user, ObjectPtr track, Scrobbler scrobbler, const Wt::WDateTime& dateTime) - { - return session.getDboSession().add(std::unique_ptr {new Listen {user, track, scrobbler, dateTime}}); - } + Listen::pointer Listen::create(Session& session, ObjectPtr user, ObjectPtr track, Scrobbler scrobbler, const Wt::WDateTime& dateTime) + { + return session.getDboSession().add(std::unique_ptr {new Listen{ user, track, scrobbler, dateTime }}); + } - std::size_t - Listen::getCount(Session& session) - { - session.checkSharedLocked(); - return session.getDboSession().query("SELECT COUNT(*) FROM listen"); - } + std::size_t Listen::getCount(Session& session) + { + session.checkSharedLocked(); + return session.getDboSession().query("SELECT COUNT(*) FROM listen"); + } - Listen::pointer - Listen::find(Session& session, ListenId id) - { - session.checkSharedLocked(); - return session.getDboSession().find().where("id = ?").bind(id).resultValue(); - } + Listen::pointer Listen::find(Session& session, ListenId id) + { + session.checkSharedLocked(); + return session.getDboSession().find().where("id = ?").bind(id).resultValue(); + } - RangeResults - Listen::find(Session& session, const FindParameters& parameters) - { - session.checkSharedLocked(); + RangeResults Listen::find(Session& session, const FindParameters& parameters) + { + session.checkSharedLocked(); - auto query {session.getDboSession().query("SELECT id FROM listen") - .orderBy("date_time")}; + auto query{ session.getDboSession().query("SELECT id FROM listen") + .orderBy("date_time") }; - if (parameters.user.isValid()) - query.where("user_id = ?").bind(parameters.user); + if (parameters.user.isValid()) + query.where("user_id = ?").bind(parameters.user); - if (parameters.scrobbler) - query.where("scrobbler = ?").bind(*parameters.scrobbler); + if (parameters.scrobbler) + query.where("scrobbler = ?").bind(*parameters.scrobbler); - if (parameters.scrobblingState) - query.where("scrobbling_state = ?").bind(*parameters.scrobblingState); + if (parameters.scrobblingState) + query.where("scrobbling_state = ?").bind(*parameters.scrobblingState); - return Utils::execQuery(query, parameters.range); - } + return Utils::execQuery(query, parameters.range); + } - Listen::pointer - Listen::find(Session& session, UserId userId, TrackId trackId, Scrobbler scrobbler, const Wt::WDateTime& dateTime) - { - session.checkSharedLocked(); + Listen::pointer Listen::find(Session& session, UserId userId, TrackId trackId, Scrobbler scrobbler, const Wt::WDateTime& dateTime) + { + session.checkSharedLocked(); - return session.getDboSession().find() - .where("user_id = ?").bind(userId) - .where("track_id = ?").bind(trackId) - .where("scrobbler = ?").bind(scrobbler) - .where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t())) - .resultValue(); - } + return session.getDboSession().find() + .where("user_id = ?").bind(userId) + .where("track_id = ?").bind(trackId) + .where("scrobbler = ?").bind(scrobbler) + .where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t())) + .resultValue(); + } - RangeResults - Listen::getTopArtists(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - std::optional linkType, - Range range) - { - auto query {createArtistsQuery(session.getDboSession(), userId, scrobbler, clusterIds, linkType)}; + RangeResults Listen::getTopArtists(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, std::optional linkType, Range range) + { + auto query{ createArtistsQuery(session.getDboSession(), userId, scrobbler, clusterIds, linkType) }; - auto collection {query - .orderBy("COUNT(a.id) DESC") - .groupBy("a.id")}; + auto collection{ query + .orderBy("COUNT(a.id) DESC") + .groupBy("a.id") }; - return Utils::execQuery(query, range); - } + return Utils::execQuery(query, range); + } - RangeResults - Listen::getTopReleases(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - Range range) - { - auto query {createReleasesQuery(session.getDboSession(), userId, scrobbler, clusterIds) - .orderBy("COUNT(r.id) DESC") - .groupBy("r.id")}; + RangeResults Listen::getTopReleases(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, Range range) + { + auto query{ createReleasesQuery(session.getDboSession(), userId, scrobbler, clusterIds) + .orderBy("COUNT(r.id) DESC") + .groupBy("r.id") }; - return Utils::execQuery(query, range); - } + return Utils::execQuery(query, range); + } - RangeResults - Listen::getTopTracks(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - Range range) - { - auto query {createTracksQuery(session.getDboSession(), userId, scrobbler, clusterIds) - .orderBy("COUNT(t.id) DESC") - .groupBy("t.id")}; + RangeResults Listen::getTopTracks(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, Range range) + { + auto query{ createTracksQuery(session.getDboSession(), userId, scrobbler, clusterIds) + .orderBy("COUNT(t.id) DESC") + .groupBy("t.id") }; - return Utils::execQuery(query, range); - } + return Utils::execQuery(query, range); + } - RangeResults - Listen::getRecentArtists(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - std::optional linkType, - Range range) - { - auto query {createArtistsQuery(session.getDboSession(), userId, scrobbler, clusterIds, linkType) - .groupBy("a.id").having("l.date_time = MAX(l.date_time)") - .orderBy("l.date_time DESC")}; + RangeResults Listen::getRecentArtists(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, std::optional linkType, Range range) + { + auto query{ createArtistsQuery(session.getDboSession(), userId, scrobbler, clusterIds, linkType) + .groupBy("a.id").having("l.date_time = MAX(l.date_time)") + .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, range); - } + return Utils::execQuery(query, range); + } - RangeResults - Listen::getRecentReleases(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - Range range) - { - auto query {createReleasesQuery(session.getDboSession(), userId, scrobbler, clusterIds) - .groupBy("r.id").having("l.date_time = MAX(l.date_time)") - .orderBy("l.date_time DESC")}; + RangeResults Listen::getRecentReleases(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, Range range) + { + auto query{ createReleasesQuery(session.getDboSession(), userId, scrobbler, clusterIds) + .groupBy("r.id").having("l.date_time = MAX(l.date_time)") + .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, range); - } + return Utils::execQuery(query, range); + } - RangeResults - Listen::getRecentTracks(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - Range range) - { - auto query {createTracksQuery(session.getDboSession(), userId, scrobbler, clusterIds) - .groupBy("t.id").having("l.date_time = MAX(l.date_time)") - .orderBy("l.date_time DESC")}; + RangeResults Listen::getRecentTracks(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, Range range) + { + auto query{ createTracksQuery(session.getDboSession(), userId, scrobbler, clusterIds) + .groupBy("t.id").having("l.date_time = MAX(l.date_time)") + .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, range); - } + return Utils::execQuery(query, range); + } + Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, Scrobbler scrobbler, ReleaseId releaseId) + { + // TODO not pending remove? + return session.getDboSession().query>("SELECT l from listen l") + .join("track t ON l.track_id = t.id") + .where("t.release_id = ?").bind(releaseId) + .where("l.user_id = ?").bind(userId) + .where("l.scrobbler = ?").bind(scrobbler) + .orderBy("l.date_time DESC") + .limit(1) + .resultValue(); + } + Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, Scrobbler scrobbler, TrackId trackId) + { + // TODO not pending remove? + return session.getDboSession().query>("SELECT l from listen l") + .join("track t ON track_id = t.id") + .where("t.id = ?").bind(trackId) + .where("l.user_id = ?").bind(userId) + .where("l.scrobbler = ?").bind(scrobbler) + .orderBy("l.date_time DESC") + .limit(1) + .resultValue(); + } } // namespace Database diff --git a/src/libs/services/database/include/services/database/Listen.hpp b/src/libs/services/database/include/services/database/Listen.hpp index 5dcaf1fc..231d763e 100644 --- a/src/libs/services/database/include/services/database/Listen.hpp +++ b/src/libs/services/database/include/services/database/Listen.hpp @@ -34,99 +34,76 @@ namespace Database { -class Session; -class Track; -class User; + class Session; + class Track; + class User; -class Listen final : public Object -{ - public: - Listen() = default; + class Listen final : public Object + { + public: + Listen() = default; - struct FindParameters - { - UserId user; - std::optional scrobbler; - std::optional scrobblingState; - Range range; + struct FindParameters + { + UserId user; + std::optional scrobbler; + std::optional scrobblingState; + Range range; - FindParameters& setUser(UserId _user) { user = _user; return *this; } - FindParameters& setScrobbler(Scrobbler _scrobbler) { scrobbler = _scrobbler; return *this; } - FindParameters& setScrobblingState(ScrobblingState _scrobblingState) { scrobblingState = _scrobblingState; return *this; } - FindParameters& setRange(Range _range) {range = _range; return *this; } - }; + FindParameters& setUser(UserId _user) { user = _user; return *this; } + FindParameters& setScrobbler(Scrobbler _scrobbler) { scrobbler = _scrobbler; return *this; } + FindParameters& setScrobblingState(ScrobblingState _scrobblingState) { scrobblingState = _scrobblingState; return *this; } + FindParameters& setRange(Range _range) { range = _range; return *this; } + }; - // Accessors - static std::size_t getCount(Session& session); - static pointer find(Session& session, ListenId id); - static pointer find(Session& session, UserId userId, TrackId trackId, Scrobbler scrobbler, const Wt::WDateTime& dateTime); - static RangeResults find(Session& session, const FindParameters& parameters); + // Accessors + static std::size_t getCount(Session& session); + static pointer find(Session& session, ListenId id); + static pointer find(Session& session, UserId userId, TrackId trackId, Scrobbler scrobbler, const Wt::WDateTime& dateTime); + static RangeResults find(Session& session, const FindParameters& parameters); - // Stats - static RangeResults getTopArtists(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - std::optional linkType, - Range range = {}); - static RangeResults getTopReleases(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - Range range = {}); - static RangeResults getTopTracks(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - Range range = {}); + // Stats + static RangeResults getTopArtists(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, std::optional linkType, Range range = {}); + static RangeResults getTopReleases(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, Range range = {}); + static RangeResults getTopTracks(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, Range range = {}); - static RangeResults getRecentArtists(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - std::optional linkType, - Range range = {}); - static RangeResults getRecentReleases(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - Range range = {}); - static RangeResults getRecentTracks(Session& session, - UserId userId, - Scrobbler scrobbler, - const std::vector& clusterIds, - Range range = {}); + static RangeResults getRecentArtists(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, std::optional linkType, Range range = {}); + static RangeResults getRecentReleases(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, Range range = {}); + static RangeResults getRecentTracks(Session& session, UserId userId, Scrobbler scrobbler, const std::vector& clusterIds, Range range = {}); - ScrobblingState getScrobblingState() const { return _scrobblingState; } - ObjectPtr getUser() const { return _user; } - ObjectPtr getTrack() const { return _track; } - const Wt::WDateTime& getDateTime() const { return _dateTime; } + static pointer getMostRecentListen(Session& session, UserId userId, Scrobbler scrobbler, ReleaseId releaseId); + static pointer getMostRecentListen(Session& session, UserId userId, Scrobbler scrobbler, TrackId releaseId); - void setScrobblingState(ScrobblingState state) { _scrobblingState = state; } + ScrobblingState getScrobblingState() const { return _scrobblingState; } + ObjectPtr getUser() const { return _user; } + ObjectPtr getTrack() const { return _track; } + const Wt::WDateTime& getDateTime() const { return _dateTime; } - template - void persist(Action& a) - { - Wt::Dbo::field(a, _dateTime, "date_time"); - Wt::Dbo::field(a, _scrobbler, "scrobbler"); - Wt::Dbo::field(a, _scrobblingState, "scrobbling_state"); + void setScrobblingState(ScrobblingState state) { _scrobblingState = state; } - Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); - } + template + void persist(Action& a) + { + Wt::Dbo::field(a, _dateTime, "date_time"); + Wt::Dbo::field(a, _scrobbler, "scrobbler"); + Wt::Dbo::field(a, _scrobblingState, "scrobbling_state"); - private: - friend class Session; - Listen(ObjectPtr user, ObjectPtr track, Scrobbler scrobbler, const Wt::WDateTime& dateTime); - static pointer create(Session& session, ObjectPtr user, ObjectPtr track, Scrobbler scrobbler, const Wt::WDateTime& dateTime); + Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); + } - Wt::WDateTime _dateTime; - Scrobbler _scrobbler; - ScrobblingState _scrobblingState {ScrobblingState::PendingAdd}; + private: + friend class Session; + Listen(ObjectPtr user, ObjectPtr track, Scrobbler scrobbler, const Wt::WDateTime& dateTime); + static pointer create(Session& session, ObjectPtr user, ObjectPtr track, Scrobbler scrobbler, const Wt::WDateTime& dateTime); - Wt::Dbo::ptr _user; - Wt::Dbo::ptr _track; -}; + Wt::WDateTime _dateTime; + Scrobbler _scrobbler; + ScrobblingState _scrobblingState{ ScrobblingState::PendingAdd }; + + Wt::Dbo::ptr _user; + Wt::Dbo::ptr _track; + }; } // namespace Database diff --git a/src/libs/services/database/test/Listen.cpp b/src/libs/services/database/test/Listen.cpp index d43f7ad0..1a821711 100644 --- a/src/libs/services/database/test/Listen.cpp +++ b/src/libs/services/database/test/Listen.cpp @@ -26,878 +26,977 @@ using ScopedListen = ScopedEntity; TEST_F(DatabaseFixture, Listen_getAll) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - EXPECT_EQ(Listen::getCount(session), 0); - } + EXPECT_EQ(Listen::getCount(session), 0); + } - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}}}; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - EXPECT_EQ(Listen::getCount(session), 1); - } + EXPECT_EQ(Listen::getCount(session), 1); + } - { - auto transaction {session.createUniqueTransaction()}; - ScopedListen listen2 {session, user.get(), track.get(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{13, 0, 1}}}; + { + auto transaction{ session.createUniqueTransaction() }; + ScopedListen listen2{ session, user.get(), track.get(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{13, 0, 1}} }; - EXPECT_EQ(Listen::getCount(session), 2); - } + EXPECT_EQ(Listen::getCount(session), 2); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - EXPECT_EQ(Listen::getCount(session), 1); - } + EXPECT_EQ(Listen::getCount(session), 1); + } } TEST_F(DatabaseFixture, Listen_get) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}}}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto listens {Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::ListenBrainz))}; - EXPECT_EQ(listens.results.size(), 0); - } + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::ListenBrainz)) }; + EXPECT_EQ(listens.results.size(), 0); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - { - auto listens {Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::Internal))}; - EXPECT_EQ(listens.moreResults, false); - ASSERT_EQ(listens.results.size(), 1); - EXPECT_EQ(listens.results.front(), listen->getId()); - } + { + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::Internal)) }; + EXPECT_EQ(listens.moreResults, false); + ASSERT_EQ(listens.results.size(), 1); + EXPECT_EQ(listens.results.front(), listen->getId()); + } - { - auto listens {Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::Internal).setScrobblingState(ScrobblingState::PendingAdd))}; - EXPECT_EQ(listens.results.size(), 1); - } - { - auto listens {Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::Internal).setScrobblingState(ScrobblingState::Synchronized))}; - EXPECT_EQ(listens.results.size(), 0); - } - } + { + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::Internal).setScrobblingState(ScrobblingState::PendingAdd)) }; + EXPECT_EQ(listens.results.size(), 1); + } + { + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::Internal).setScrobblingState(ScrobblingState::Synchronized)) }; + EXPECT_EQ(listens.results.size(), 0); + } + } } TEST_F(DatabaseFixture, Listen_get_multi) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - ScopedListen listen3 {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 3}}}; - ScopedListen listen1 {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}}}; - ScopedListen listen2 {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 2}}}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 3}} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 2}} }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto listens {Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::Internal))}; - ASSERT_EQ(listens.results.size(), 3); - EXPECT_EQ(listens.results[0], listen1.getId()); - EXPECT_EQ(listens.results[1], listen2.getId()); - EXPECT_EQ(listens.results[2], listen3.getId()); - } + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobbler(Scrobbler::Internal)) }; + ASSERT_EQ(listens.results.size(), 3); + EXPECT_EQ(listens.results[0], listen1.getId()); + EXPECT_EQ(listens.results[1], listen2.getId()); + EXPECT_EQ(listens.results[2], listen3.getId()); + } } TEST_F(DatabaseFixture, Listen_get_byDateTime) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime1 {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - const Wt::WDateTime dateTime2 {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 2}}; - ASSERT_GT(dateTime2, dateTime1); + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime2{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 2} }; + ASSERT_GT(dateTime2, dateTime1); - ScopedListen listen1 {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime1}; - ScopedListen listen2 {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime2}; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime1 }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime2 }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - { - Listen::pointer listen {Listen::find(session, user.getId(), track.getId(), Scrobbler::Internal, dateTime1)}; - ASSERT_TRUE(listen); - EXPECT_EQ(listen->getId(), listen1.getId()); - } + { + Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), Scrobbler::Internal, dateTime1) }; + ASSERT_TRUE(listen); + EXPECT_EQ(listen->getId(), listen1.getId()); + } - { - Listen::pointer listen {Listen::find(session, user.getId(), track.getId(), Scrobbler::Internal, dateTime2)}; - ASSERT_TRUE(listen); - EXPECT_EQ(listen->getId(), listen2.getId()); - } + { + Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), Scrobbler::Internal, dateTime2) }; + ASSERT_TRUE(listen); + EXPECT_EQ(listen->getId(), listen2.getId()); + } - { - Listen::pointer listen {Listen::find(session, user.getId(), track.getId(), Scrobbler::Internal, dateTime2.addSecs(56))}; - EXPECT_FALSE(listen); - } - } + { + Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), Scrobbler::Internal, dateTime2.addSecs(56)) }; + EXPECT_FALSE(listen); + } + } } TEST_F(DatabaseFixture, Listen_getTopArtists) { - ScopedTrack track1 {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime1 {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedListen listen1 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime1}; + ScopedTrack track1{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime1 }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - EXPECT_EQ(artists.results.size(), 0); - EXPECT_EQ(artists.moreResults, false); - } + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + EXPECT_EQ(artists.results.size(), 0); + EXPECT_EQ(artists.moreResults, false); + } - ScopedTrack track2 {session, "MyTrack2"}; - ScopedArtist artist1 {session, "MyArtist1"}; - ScopedListen listen2 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime1.addSecs(1)}; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedArtist artist1{ session, "MyArtist1" }; + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime1.addSecs(1) }; - { - auto transaction {session.createUniqueTransaction()}; + { + auto transaction{ session.createUniqueTransaction() }; - TrackArtistLink::create(session, track2.get(), artist1.get(), TrackArtistLinkType::Artist); - } + TrackArtistLink::create(session, track2.get(), artist1.get(), TrackArtistLinkType::Artist); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist1->getId()); - } - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist1->getId()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::ListenBrainz, {}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 0); - } - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::ListenBrainz, {}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 0); + } + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, TrackArtistLinkType::Producer)}; - EXPECT_EQ(artists.results.size(), 0); - } - { - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, TrackArtistLinkType::Producer) }; + EXPECT_EQ(artists.results.size(), 0); + } + { + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {cluster->getId()}, std::nullopt)}; - EXPECT_EQ(artists.results.size(), 0); - } - } + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {cluster->getId()}, std::nullopt) }; + EXPECT_EQ(artists.results.size(), 0); + } + } } TEST_F(DatabaseFixture, Listen_getTopArtists_multi) { - ScopedUser user {session, "MyUser"}; - ScopedTrack track1 {session, "MyTrack1"}; - ScopedArtist artist1 {session, "MyArtist1"}; - ScopedTrack track2 {session, "MyTrack2"}; - ScopedArtist artist2 {session, "MyArtist2"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; + ScopedUser user{ session, "MyUser" }; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedArtist artist1{ session, "MyArtist1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedArtist artist2{ session, "MyArtist2" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - { - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); - } + { + auto transaction{ session.createUniqueTransaction() }; + TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - EXPECT_EQ(artists.results.size(), 0); - } + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + EXPECT_EQ(artists.results.size(), 0); + } - ScopedListen listen1 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(1)}; - { - auto transaction {session.createSharedTransaction()}; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(1) }; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist1->getId()); - } - ScopedListen listen2 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(2)}; - ScopedListen listen3 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(3)}; - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist1->getId()); + } + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(2) }; + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(3) }; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], artist2->getId()); - EXPECT_EQ(artists.results[1], artist1->getId()); - } - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], artist2->getId()); + EXPECT_EQ(artists.results[1], artist1->getId()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt, Range {0, 1})}; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.moreResults, true); - EXPECT_EQ(artists.results[0], artist2->getId()); - } + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt, Range {0, 1}) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.moreResults, true); + EXPECT_EQ(artists.results[0], artist2->getId()); + } } TEST_F(DatabaseFixture, Listen_getTopArtists_cluster) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - ScopedArtist artist {session, "MyArtist"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedArtist artist{ session, "MyArtist" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - { - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } + { + auto transaction{ session.createUniqueTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {cluster.getId()}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 0); - } - { - auto transaction {session.createUniqueTransaction()}; - cluster.get().modify()->addTrack(track.get()); - } - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {cluster.getId()}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 0); + } + { + auto transaction{ session.createUniqueTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {cluster.getId()}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist.getId()); - } + auto artists{ Listen::getTopArtists(session, user->getId(), Scrobbler::Internal, {cluster.getId()}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist.getId()); + } } TEST_F(DatabaseFixture, Listen_getTopReleases) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedRelease release {session, "MyRelease"}; - { - auto transaction {session.createSharedTransaction()}; - track.get().modify()->setRelease(release.get()); - } + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedRelease release{ session, "MyRelease" }; + { + auto transaction{ session.createSharedTransaction() }; + track.get().modify()->setRelease(release.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } + auto releases{ Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } - { - auto transaction {session.createSharedTransaction()}; + auto releases{ Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getTopReleases(session, user->getId(), Scrobbler::ListenBrainz, {})}; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } + auto releases{ Listen::getTopReleases(session, user->getId(), Scrobbler::ListenBrainz, {}) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } } TEST_F(DatabaseFixture, Listen_getTopReleases_multi) { - ScopedTrack track1 {session, "MyTrack"}; - ScopedTrack track2 {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedListen listen1 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedRelease release1 {session, "MyRelease1"}; - ScopedRelease release2 {session, "MyRelease2"}; + ScopedTrack track1{ session, "MyTrack" }; + ScopedTrack track2{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; - { - auto transaction {session.createSharedTransaction()}; - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); - } + { + auto transaction{ session.createSharedTransaction() }; + track1.get().modify()->setRelease(release1.get()); + track2.get().modify()->setRelease(release2.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release1.getId()); - } - ScopedListen listen2 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedListen listen3 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime}; - { - auto transaction {session.createSharedTransaction()}; + auto releases{ Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release1.getId()); + } + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime }; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release2.getId()); - EXPECT_EQ(releases.results[1], release1.getId()); - } - ScopedListen listen4 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedListen listen5 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime}; - { - auto transaction {session.createSharedTransaction()}; + auto releases{ Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release2.getId()); + EXPECT_EQ(releases.results[1], release1.getId()); + } + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedListen listen5{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime }; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release1.getId()); - EXPECT_EQ(releases.results[1], release2.getId()); - } + auto releases{ Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release1.getId()); + EXPECT_EQ(releases.results[1], release2.getId()); + } } TEST_F(DatabaseFixture, Listen_getTopReleases_cluster) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - ScopedRelease release {session, "MyRelease"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + ScopedRelease release{ session, "MyRelease" }; - { - auto transaction {session.createSharedTransaction()}; - track.get().modify()->setRelease(release.get()); - } + { + auto transaction{ session.createSharedTransaction() }; + track.get().modify()->setRelease(release.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(releases.results.size(), 0); - } - { - auto transaction {session.createUniqueTransaction()}; - cluster.get().modify()->addTrack(track.get()); - } + auto releases{ Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(releases.results.size(), 0); + } + { + auto transaction{ session.createUniqueTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } + auto releases{ Listen::getTopReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } } TEST_F(DatabaseFixture, Listen_getTopTracks) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 0); - } + auto tracks{ Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 0); + } - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } - { - auto transaction {session.createSharedTransaction()}; + auto tracks{ Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getTopTracks(session, user->getId(), Scrobbler::ListenBrainz, {})}; - EXPECT_EQ(tracks.moreResults, false); - EXPECT_EQ(tracks.results.size(), 0); - } + auto tracks{ Listen::getTopTracks(session, user->getId(), Scrobbler::ListenBrainz, {}) }; + EXPECT_EQ(tracks.moreResults, false); + EXPECT_EQ(tracks.results.size(), 0); + } } TEST_F(DatabaseFixture, Listen_getTopTrack_multi) { - ScopedTrack track1 {session, "MyTrack1"}; - ScopedTrack track2 {session, "MyTrack2"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedListen listen1 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime}; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track1.getId()); - } - ScopedListen listen2 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedListen listen3 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime}; - { - auto transaction {session.createSharedTransaction()}; + auto tracks{ Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track1.getId()); + } + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime }; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track2.getId()); - EXPECT_EQ(tracks.results[1], track1.getId()); - } - ScopedListen listen4 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedListen listen5 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime}; - { - auto transaction {session.createSharedTransaction()}; + auto tracks{ Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track2.getId()); + EXPECT_EQ(tracks.results[1], track1.getId()); + } + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedListen listen5{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime }; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track1.getId()); - EXPECT_EQ(tracks.results[1], track2.getId()); - } + auto tracks{ Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track1.getId()); + EXPECT_EQ(tracks.results[1], track2.getId()); + } } TEST_F(DatabaseFixture, Listen_getTopTracks_cluster) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(tracks.results.size(), 0); - } - { - auto transaction {session.createUniqueTransaction()}; - cluster.get().modify()->addTrack(track.get()); - } + auto tracks{ Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(tracks.results.size(), 0); + } + { + auto transaction{ session.createUniqueTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } + auto tracks{ Listen::getTopTracks(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } } TEST_F(DatabaseFixture, Listen_getRecentArtists) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - ScopedArtist artist {session, "MyArtist"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedArtist artist{ session, "MyArtist" }; - { - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } + { + auto transaction{ session.createUniqueTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - EXPECT_EQ(artists.results.size(), 0); - EXPECT_EQ(artists.moreResults, false); - } + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + EXPECT_EQ(artists.results.size(), 0); + EXPECT_EQ(artists.moreResults, false); + } - const Wt::WDateTime dateTime {Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1}}; - ScopedListen listen1 {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist->getId()); - } - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist->getId()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::ListenBrainz, {}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 0); - } - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::ListenBrainz, {}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 0); + } + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, TrackArtistLinkType::Producer)}; - EXPECT_EQ(artists.results.size(), 0); - } - { - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, TrackArtistLinkType::Producer) }; + EXPECT_EQ(artists.results.size(), 0); + } + { + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {cluster->getId()}, std::nullopt)}; - EXPECT_EQ(artists.results.size(), 0); - } - } + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {cluster->getId()}, std::nullopt) }; + EXPECT_EQ(artists.results.size(), 0); + } + } } TEST_F(DatabaseFixture, Listen_getRecentArtists_multi) { - ScopedUser user {session, "MyUser"}; - ScopedTrack track1 {session, "MyTrack1"}; - ScopedArtist artist1 {session, "MyArtist1"}; - ScopedTrack track2 {session, "MyTrack2"}; - ScopedArtist artist2 {session, "MyArtist2"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; + ScopedUser user{ session, "MyUser" }; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedArtist artist1{ session, "MyArtist1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedArtist artist2{ session, "MyArtist2" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - { - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); - } + { + auto transaction{ session.createUniqueTransaction() }; + TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - EXPECT_EQ(artists.results.size(), 0); - } + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + EXPECT_EQ(artists.results.size(), 0); + } - ScopedListen listen1 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(1)}; - { - auto transaction {session.createSharedTransaction()}; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(1) }; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist1->getId()); - } - ScopedListen listen2 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(2)}; - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist1->getId()); + } + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(2) }; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], artist2->getId()); - EXPECT_EQ(artists.results[1], artist1->getId()); - } - ScopedListen listen3 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(-1)}; - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], artist2->getId()); + EXPECT_EQ(artists.results[1], artist1->getId()); + } + ScopedListen listen3{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(-1) }; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt, Range {0, 1})}; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.moreResults, true); - EXPECT_EQ(artists.results[0], artist2->getId()); - } + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {}, std::nullopt, Range {0, 1}) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.moreResults, true); + EXPECT_EQ(artists.results[0], artist2->getId()); + } } TEST_F(DatabaseFixture, Listen_getRecentArtists_cluster) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - ScopedArtist artist {session, "MyArtist"}; - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedArtist artist{ session, "MyArtist" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - { - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } + { + auto transaction{ session.createUniqueTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {cluster.getId()}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 0); - } - { - auto transaction {session.createUniqueTransaction()}; - cluster.get().modify()->addTrack(track.get()); - } - { - auto transaction {session.createSharedTransaction()}; + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {cluster.getId()}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 0); + } + { + auto transaction{ session.createUniqueTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto artists {Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {cluster.getId()}, std::nullopt)}; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist.getId()); - } + auto artists{ Listen::getRecentArtists(session, user->getId(), Scrobbler::Internal, {cluster.getId()}, std::nullopt) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist.getId()); + } } TEST_F(DatabaseFixture, Listen_getRecentReleases) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - ScopedRelease release {session, "MyRelease"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedRelease release{ session, "MyRelease" }; - { - auto transaction {session.createSharedTransaction()}; - track.get().modify()->setRelease(release.get()); - } + { + auto transaction{ session.createSharedTransaction() }; + track.get().modify()->setRelease(release.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 0); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 0); + } - const Wt::WDateTime dateTime {Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1}}; - ScopedListen listen1 {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } - { - auto transaction {session.createSharedTransaction()}; + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::ListenBrainz, {})}; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::ListenBrainz, {}) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } +} + + +TEST_F(DatabaseFixture, Listen_getMostRecentRelease) +{ + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedRelease release{ session, "MyRelease" }; + + { + auto transaction{ session.createSharedTransaction() }; + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createSharedTransaction() }; + + const auto listen{ Listen::getMostRecentListen(session, user->getId(), Scrobbler::Internal, release.getId()) }; + EXPECT_FALSE(listen); + } + + const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime1 }; + + { + auto transaction{ session.createSharedTransaction() }; + + const auto listen{ Listen::getMostRecentListen(session, user->getId(), Scrobbler::Internal, release.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime2{ Wt::WDate {1999, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime2 }; + + { + auto transaction{ session.createSharedTransaction() }; + + const auto listen{ Listen::getMostRecentListen(session, user->getId(), Scrobbler::Internal, release.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime3{ Wt::WDate {2001, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime3 }; + + { + auto transaction{ session.createSharedTransaction() }; + + const auto listen{ Listen::getMostRecentListen(session, user->getId(), Scrobbler::Internal, release.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime3); + } } TEST_F(DatabaseFixture, Listen_getRecentReleases_multi) { - ScopedTrack track1 {session, "MyTrack1"}; - ScopedTrack track2 {session, "MyTrack2"}; - ScopedUser user {session, "MyUser"}; - ScopedRelease release1 {session, "MyRelease1"}; - ScopedRelease release2 {session, "MyRelease2"}; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedUser user{ session, "MyUser" }; + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; - { - auto transaction {session.createSharedTransaction()}; - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); - } + { + auto transaction{ session.createSharedTransaction() }; + track1.get().modify()->setRelease(release1.get()); + track2.get().modify()->setRelease(release2.get()); + } - const Wt::WDateTime dateTime {Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1}}; - ScopedListen listen1 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime}; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release2.getId()); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release2.getId()); + } - ScopedListen listen2 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(1)}; - { - auto transaction {session.createSharedTransaction()}; + ScopedListen listen2{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(1) }; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release1.getId()); - EXPECT_EQ(releases.results[1], release2.getId()); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release1.getId()); + EXPECT_EQ(releases.results[1], release2.getId()); + } - ScopedListen listen3 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(2)}; - { - auto transaction {session.createSharedTransaction()}; + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(2) }; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release2.getId()); - EXPECT_EQ(releases.results[1], release1.getId()); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release2.getId()); + EXPECT_EQ(releases.results[1], release1.getId()); + } - ScopedListen listen4 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(-1)}; - { - auto transaction {session.createSharedTransaction()}; + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(-1) }; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release2.getId()); - EXPECT_EQ(releases.results[1], release1.getId()); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release2.getId()); + EXPECT_EQ(releases.results[1], release1.getId()); + } } TEST_F(DatabaseFixture, Listen_getRecentReleases_cluster) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - ScopedRelease release {session, "MyRelease"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + ScopedRelease release{ session, "MyRelease" }; - { - auto transaction {session.createSharedTransaction()}; - track.get().modify()->setRelease(release.get()); - } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; + track.get().modify()->setRelease(release.get()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(releases.results.size(), 0); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(releases.results.size(), 0); + } - const Wt::WDateTime dateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1}}; - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(releases.results.size(), 0); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(releases.results.size(), 0); + } - { - auto transaction {session.createUniqueTransaction()}; - cluster.get().modify()->addTrack(track.get()); - } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createUniqueTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + { + auto transaction{ session.createSharedTransaction() }; - auto releases {Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } + auto releases{ Listen::getRecentReleases(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } } TEST_F(DatabaseFixture, Listen_getRecentTracks) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 0); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 0); + } - const Wt::WDateTime dateTime {Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1}}; - ScopedListen listen1 {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::ListenBrainz, {})}; - EXPECT_EQ(tracks.moreResults, false); - EXPECT_EQ(tracks.results.size(), 0); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::ListenBrainz, {}) }; + EXPECT_EQ(tracks.moreResults, false); + EXPECT_EQ(tracks.results.size(), 0); + } +} + +TEST_F(DatabaseFixture, Listen_getMostRecentTrack) +{ + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + + { + auto transaction{ session.createSharedTransaction() }; + + auto listen{ Listen::getMostRecentListen(session, user->getId(), Scrobbler::Internal, track.getId()) }; + EXPECT_FALSE(listen); + } + + const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime1 }; + + { + auto transaction{ session.createSharedTransaction() }; + + auto listen{ Listen::getMostRecentListen(session, user->getId(), Scrobbler::Internal, track.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime2{ Wt::WDate {1999, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime2 }; + + { + auto transaction{ session.createSharedTransaction() }; + + auto listen{ Listen::getMostRecentListen(session, user->getId(), Scrobbler::Internal, track.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime3{ Wt::WDate {2001, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime3 }; + + { + auto transaction{ session.createSharedTransaction() }; + + auto listen{ Listen::getMostRecentListen(session, user->getId(), Scrobbler::Internal, track.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime3); + } } TEST_F(DatabaseFixture, Listen_getRecentTracks_multi) { - ScopedTrack track1 {session, "MyTrack1"}; - ScopedTrack track2 {session, "MyTrack2"}; - ScopedUser user {session, "MyUser"}; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime {Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1}}; - ScopedListen listen1 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime}; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track2.getId()); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track2.getId()); + } - ScopedListen listen2 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(1)}; - { - auto transaction {session.createSharedTransaction()}; + ScopedListen listen2{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(1) }; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track1.getId()); - EXPECT_EQ(tracks.results[1], track2.getId()); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track1.getId()); + EXPECT_EQ(tracks.results[1], track2.getId()); + } - ScopedListen listen3 {session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(2)}; - { - auto transaction {session.createSharedTransaction()}; + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(2) }; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track2.getId()); - EXPECT_EQ(tracks.results[1], track1.getId()); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track2.getId()); + EXPECT_EQ(tracks.results[1], track1.getId()); + } - ScopedListen listen4 {session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(-1)}; - { - auto transaction {session.createSharedTransaction()}; + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), Scrobbler::Internal, dateTime.addSecs(-1) }; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {})}; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track2.getId()); - EXPECT_EQ(tracks.results[1], track1.getId()); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {}) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track2.getId()); + EXPECT_EQ(tracks.results[1], track1.getId()); + } } TEST_F(DatabaseFixture, Listen_getRecentTracks_cluster) { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; - const Wt::WDateTime dateTime {Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1}}; - ScopedListen listen {session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), Scrobbler::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(tracks.results.size(), 0); - } - { - auto transaction {session.createUniqueTransaction()}; - cluster.get().modify()->addTrack(track.get()); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(tracks.results.size(), 0); + } + { + auto transaction{ session.createUniqueTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {cluster.getId()})}; - EXPECT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } + auto tracks{ Listen::getRecentTracks(session, user->getId(), Scrobbler::Internal, {cluster.getId()}) }; + EXPECT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } } diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.cpp b/src/libs/services/scrobbling/impl/ScrobblingService.cpp index cecb7c00..af331af5 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.cpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.cpp @@ -133,6 +133,32 @@ namespace Scrobbling return res; } + Wt::WDateTime ScrobblingService::getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId) + { + auto scrobbler{ getUserScrobbler(userId) }; + if (!scrobbler) + return {}; + + Session& session{ _db.getTLSSession() }; + auto transaction{ session.createSharedTransaction() }; + + const Database::Listen::pointer listen{ Database::Listen::getMostRecentListen(session, userId, *scrobbler, releaseId) }; + return listen ? listen->getDateTime() : Wt::WDateTime{}; + } + + Wt::WDateTime ScrobblingService::getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) + { + auto scrobbler{ getUserScrobbler(userId) }; + if (!scrobbler) + return {}; + + Session& session{ _db.getTLSSession() }; + auto transaction{ session.createSharedTransaction() }; + + const Database::Listen::pointer listen{ Database::Listen::getMostRecentListen(session, userId, *scrobbler, trackId) }; + return listen ? listen->getDateTime() : Wt::WDateTime{}; + } + // Top ScrobblingService::ArtistContainer ScrobblingService::getTopArtists(UserId userId, const std::vector& clusterIds, std::optional linkType, Range range) { diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.hpp b/src/libs/services/scrobbling/impl/ScrobblingService.hpp index 225338a1..68cc27ec 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.hpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.hpp @@ -52,6 +52,9 @@ namespace Scrobbling const std::vector& clusterIds, Database::Range range) override; + Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId) override; + Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) override; + ArtistContainer getTopArtists(Database::UserId userId, const std::vector& clusterIds, std::optional linkType, diff --git a/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp b/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp index fd27df5d..507f5ad4 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp @@ -69,6 +69,9 @@ namespace Scrobbling const std::vector& clusterIds, Database::Range range) = 0; + virtual Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId) = 0; + virtual Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) = 0; + // Top virtual ArtistContainer getTopArtists(Database::UserId userId, const std::vector& clusterIds, diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index 408a3fa9..effd21f4 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -138,6 +138,11 @@ namespace API::Subsonic if (!id3) albumNode.setAttribute("mediaType", "album"); + { + const Wt::WDateTime dateTime{ Service::get()->getLastListenDateTime(user->getId(), release->getId()) }; + albumNode.setAttribute("played", dateTime.isValid() ? StringUtils::toISO8601String(dateTime) : ""); + } + { std::optional mbid{ release->getMBID() }; albumNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index d3594260..d3ef6a79 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -162,6 +162,11 @@ namespace API::Subsonic // OpenSubsonic specific fields (must always be set) trackResponse.setAttribute("mediaType", "song"); + { + const Wt::WDateTime dateTime{ Service::get()->getLastListenDateTime(user->getId(), track->getId()) }; + trackResponse.setAttribute("played", dateTime.isValid() ? StringUtils::toISO8601String(dateTime) : ""); + } + { std::optional mbid{ track->getRecordingMBID() }; trackResponse.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : "");