From b7a8bf85b8c376b4543298ae7206d0644589a30d Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 10 Nov 2023 16:17:18 +0100 Subject: [PATCH] Rework db range primitives + fixed bug when playing all in releases/tracks views + raised limit to 1000 for ranges in subsonic API --- src/libs/services/cover/impl/CoverService.cpp | 4 +- src/libs/services/database/impl/Artist.cpp | 21 +- src/libs/services/database/impl/Cluster.cpp | 24 +- src/libs/services/database/impl/Listen.cpp | 26 +- src/libs/services/database/impl/Release.cpp | 22 +- .../services/database/impl/StarredTrack.cpp | 2 +- src/libs/services/database/impl/Track.cpp | 35 +- .../database/impl/TrackArtistLink.cpp | 2 +- .../services/database/impl/TrackBookmark.cpp | 77 +- .../services/database/impl/TrackFeatures.cpp | 143 ++- src/libs/services/database/impl/TrackList.cpp | 952 ++++++------------ src/libs/services/database/impl/User.cpp | 2 +- src/libs/services/database/impl/Utils.hpp | 84 +- .../include/services/database/Artist.hpp | 9 +- .../include/services/database/Cluster.hpp | 29 +- .../include/services/database/Listen.hpp | 22 +- .../include/services/database/Release.hpp | 9 +- .../services/database/StarredTrack.hpp | 4 +- .../include/services/database/Track.hpp | 373 +++---- .../services/database/TrackArtistLink.hpp | 5 +- .../services/database/TrackBookmark.hpp | 3 +- .../services/database/TrackFeatures.hpp | 3 +- .../include/services/database/TrackList.hpp | 228 ++--- .../include/services/database/Types.hpp | 5 +- .../include/services/database/User.hpp | 4 +- src/libs/services/database/test/Artist.cpp | 18 +- src/libs/services/database/test/Cluster.cpp | 411 +------- src/libs/services/database/test/Common.cpp | 24 +- .../services/database/test/DatabaseTest.cpp | 4 +- src/libs/services/database/test/Release.cpp | 16 +- src/libs/services/database/test/Track.cpp | 16 + .../services/database/test/TrackBookmark.cpp | 2 +- .../services/database/test/TrackFeatures.cpp | 2 +- src/libs/services/database/test/TrackList.cpp | 577 +++-------- .../feedback/impl/FeedbackService.cpp | 50 +- .../feedback/impl/FeedbackService.hpp | 6 +- .../services/feedback/IFeedbackService.hpp | 37 +- .../impl/features/FeaturesEngine.cpp | 2 +- .../impl/ScanStepCheckDuplicatedDbFiles.cpp | 2 +- .../impl/ScanStepRemoveOrphanDbFiles.cpp | 271 +++-- src/libs/subsonic/impl/SubsonicResponse.hpp | 2 + .../impl/entrypoints/AlbumSongLists.cpp | 62 +- .../subsonic/impl/entrypoints/Bookmarks.cpp | 2 +- .../subsonic/impl/entrypoints/Browsing.cpp | 30 +- .../subsonic/impl/entrypoints/Searching.cpp | 36 +- src/lms/ui/PlayQueue.cpp | 7 +- src/lms/ui/explore/ArtistCollector.cpp | 18 +- src/lms/ui/explore/ArtistCollector.hpp | 2 +- src/lms/ui/explore/DatabaseCollectorBase.cpp | 21 +- src/lms/ui/explore/DatabaseCollectorBase.hpp | 4 +- src/lms/ui/explore/Filters.cpp | 2 +- src/lms/ui/explore/PlayQueueController.cpp | 6 +- src/lms/ui/explore/ReleaseCollector.cpp | 17 +- src/lms/ui/explore/ReleaseCollector.hpp | 4 +- src/lms/ui/explore/ReleaseView.cpp | 166 +-- src/lms/ui/explore/ReleasesView.cpp | 182 ++-- src/lms/ui/explore/SearchView.cpp | 9 +- src/lms/ui/explore/TrackCollector.cpp | 17 +- src/lms/ui/explore/TrackCollector.hpp | 4 +- src/lms/ui/explore/TrackListView.cpp | 8 +- src/lms/ui/explore/TracksView.cpp | 182 ++-- 61 files changed, 1704 insertions(+), 2603 deletions(-) diff --git a/src/libs/services/cover/impl/CoverService.cpp b/src/libs/services/cover/impl/CoverService.cpp index 91c8c612..773e41f9 100644 --- a/src/libs/services/cover/impl/CoverService.cpp +++ b/src/libs/services/cover/impl/CoverService.cpp @@ -374,8 +374,8 @@ namespace Cover auto transaction{ session.createSharedTransaction() }; - const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(releaseId).setRange({0, 1}).setSortMethod(TrackSortMethod::Release)) }; - + // get a track in this release, consider the release is in a single directory + const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(releaseId).setRange(Range{ 0, 1 }).setSortMethod(TrackSortMethod::Release)) }; if (!tracks.results.empty()) { const Track::pointer& track{ tracks.results.front() }; diff --git a/src/libs/services/database/impl/Artist.cpp b/src/libs/services/database/impl/Artist.cpp index c9ca60a4..5f8db6eb 100644 --- a/src/libs/services/database/impl/Artist.cpp +++ b/src/libs/services/database/impl/Artist.cpp @@ -210,12 +210,11 @@ namespace Database } - RangeResults Artist::findOrphanIds(Session& session, Range range) + RangeResults Artist::findOrphanIds(Session& session, std::optional range) { session.checkSharedLocked(); auto query{ session.getDboSession().query("SELECT DISTINCT a.id FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id WHERE t.id = t_a_l.track_id)") }; - - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } RangeResults Artist::findIds(Session& session, const FindParameters& params) @@ -223,7 +222,7 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery(session, params) }; - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); } RangeResults Artist::find(Session& session, const FindParameters& params) @@ -231,10 +230,18 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery>(session, params) }; - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); } - RangeResults Artist::findSimilarArtistIds(EnumSet artistLinkTypes, Range range) const + void Artist::find(Session& session, const FindParameters& params, std::function func) + { + session.checkSharedLocked(); + + auto query{ createQuery>(session, params) }; + Utils::execQuery(query, params.range, func); + } + + RangeResults Artist::findSimilarArtistIds(EnumSet artistLinkTypes, std::optional range) const { assert(session()); @@ -278,7 +285,7 @@ namespace Database for (TrackArtistLinkType type : artistLinkTypes) query.bind(type); - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } std::vector> Artist::getClusterGroups(std::vector clusterTypes, std::size_t size) const diff --git a/src/libs/services/database/impl/Cluster.cpp b/src/libs/services/database/impl/Cluster.cpp index 675d1691..f93e8de2 100644 --- a/src/libs/services/database/impl/Cluster.cpp +++ b/src/libs/services/database/impl/Cluster.cpp @@ -95,7 +95,7 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery(session, params) }; - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); } RangeResults Cluster::find(Session& session, const FindParameters& params) @@ -103,15 +103,15 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery>(session, params) }; - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); } - RangeResults Cluster::findOrphans(Session& session, Range range) + RangeResults Cluster::findOrphans(Session& session, std::optional range) { session.checkSharedLocked(); auto query{ session.getDboSession().query("SELECT DISTINCT c.id FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } Cluster::pointer Cluster::find(Session& session, ClusterId id) @@ -142,14 +142,14 @@ namespace Database _tracks.insert(getDboPtr(track)); } - RangeResults Cluster::getTracks(Range range) const + RangeResults Cluster::getTracks(std::optional range) const { assert(session()); auto query{ session()->query("SELECT t.id FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id") .where("c.id = ?").bind(getId()) }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } ClusterType::ClusterType(std::string_view name) @@ -170,7 +170,7 @@ namespace Database } - RangeResults ClusterType::findOrphans(Session& session, Range range) + RangeResults ClusterType::findOrphans(Session& session, std::optional range) { session.checkSharedLocked(); @@ -179,10 +179,10 @@ namespace Database " LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id") .where("c.id IS NULL") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } - RangeResults ClusterType::findUsed(Session& session, Range range) + RangeResults ClusterType::findUsed(Session& session, std::optional range) { session.checkSharedLocked(); @@ -190,7 +190,7 @@ namespace Database "SELECT DISTINCT c_t.id from cluster_type c_t") .join("cluster c ON c_t.id = c.cluster_type_id") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } ClusterType::pointer ClusterType::find(Session& session, std::string_view name) @@ -207,13 +207,13 @@ namespace Database return session.getDboSession().find().where("id = ?").bind(id).resultValue(); } - RangeResults ClusterType::find(Session& session, Range range) + RangeResults ClusterType::find(Session& session, std::optional range) { session.checkSharedLocked(); auto query{ session.getDboSession().query("SELECT id from cluster_type") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } Cluster::pointer ClusterType::getCluster(const std::string& name) const diff --git a/src/libs/services/database/impl/Listen.cpp b/src/libs/services/database/impl/Listen.cpp index d7af57d0..4f645a04 100644 --- a/src/libs/services/database/impl/Listen.cpp +++ b/src/libs/services/database/impl/Listen.cpp @@ -172,7 +172,7 @@ namespace Database if (parameters.syncState) query.where("sync_state = ?").bind(*parameters.syncState); - return Utils::execQuery(query, parameters.range); + return Utils::execQuery(query, parameters.range); } Listen::pointer Listen::find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime) @@ -187,7 +187,7 @@ namespace Database .resultValue(); } - RangeResults Listen::getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType, Range range) + RangeResults Listen::getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType, std::optional range) { session.checkSharedLocked(); auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType) }; @@ -196,57 +196,57 @@ namespace Database .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, ScrobblingBackend backend, const std::vector& clusterIds, Range range) + RangeResults Listen::getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional range) { session.checkSharedLocked(); auto query{ createReleasesQuery(session.getDboSession(), userId, backend, 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, ScrobblingBackend backend, const std::vector& clusterIds, Range range) + RangeResults Listen::getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional range) { session.checkSharedLocked(); auto query{ createTracksQuery(session.getDboSession(), userId, backend, 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, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType, Range range) + RangeResults Listen::getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType, std::optional range) { session.checkSharedLocked(); auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType) .groupBy("a.id").having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } - RangeResults Listen::getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, Range range) + RangeResults Listen::getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional range) { session.checkSharedLocked(); auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds) .groupBy("r.id").having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } - RangeResults Listen::getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, Range range) + RangeResults Listen::getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional range) { session.checkSharedLocked(); auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds) .groupBy("t.id").having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } std::size_t Listen::getCount(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId) diff --git a/src/libs/services/database/impl/Release.cpp b/src/libs/services/database/impl/Release.cpp index 8021dc98..5ca1bfa4 100644 --- a/src/libs/services/database/impl/Release.cpp +++ b/src/libs/services/database/impl/Release.cpp @@ -188,7 +188,7 @@ namespace Database Wt::Dbo::Query createQuery(Session& session, const Release::FindParameters& params) { std::string_view itemToSelect; - + if constexpr (std::is_same_v) itemToSelect = "r.id"; else if constexpr (std::is_same_v>) @@ -256,7 +256,7 @@ namespace Database return session.getDboSession().query("SELECT COUNT(*) FROM release"); } - RangeResults Release::findIdsOrderedByArtist(Session& session, Range range) + RangeResults Release::findIdsOrderedByArtist(Session& session, std::optional range) { session.checkSharedLocked(); @@ -268,15 +268,15 @@ namespace Database " INNER JOIN artist a ON t_a_l.artist_id = a.id") .orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } - RangeResults Release::findOrphanIds(Session& session, Range range) + RangeResults Release::findOrphanIds(Session& session, std::optional range) { session.checkSharedLocked(); auto query{ session.getDboSession().query("select r.id from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } RangeResults Release::find(Session& session, const FindParameters& params) @@ -284,7 +284,15 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery>(session, params) }; - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); + } + + void Release::find(Session& session, const FindParameters& params, std::function func) + { + session.checkSharedLocked(); + + auto query{ createQuery>(session, params) }; + Utils::execQuery(query, params.range, func); } RangeResults Release::findIds(Session& session, const FindParameters& params) @@ -292,7 +300,7 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery(session, params) }; - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); } std::size_t Release::getDiscCount() const diff --git a/src/libs/services/database/impl/StarredTrack.cpp b/src/libs/services/database/impl/StarredTrack.cpp index 2f03fbe6..78112ded 100644 --- a/src/libs/services/database/impl/StarredTrack.cpp +++ b/src/libs/services/database/impl/StarredTrack.cpp @@ -76,7 +76,7 @@ namespace Database if (params.user.isValid()) query.where("s_t.user_id = ?").bind(params.user); - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); } void StarredTrack::setDateTime(const Wt::WDateTime& dateTime) diff --git a/src/libs/services/database/impl/Track.cpp b/src/libs/services/database/impl/Track.cpp index be1f0a16..0be23942 100644 --- a/src/libs/services/database/impl/Track.cpp +++ b/src/libs/services/database/impl/Track.cpp @@ -44,7 +44,8 @@ namespace Database { session.checkSharedLocked(); - auto query{ session.getDboSession().query(params.distinct ? "SELECT DISTINCT " + std::string{ itemToSelect } + " FROM track t" : "SELECT t.id FROM track t") }; + std::string selectStatement{ params.distinct ? "SELECT DISTINCT" : "SELECT" }; + auto query{ session.getDboSession().query(selectStatement + " " + std::string{ itemToSelect } + " FROM track t") }; assert(params.keywords.empty() || params.name.empty()); for (std::string_view keyword : params.keywords) @@ -129,7 +130,7 @@ namespace Database query.where("r.name = ?").bind(params.releaseName); } - if (params.trackList.isValid()) + if (params.trackList.isValid() || params.sortMethod == TrackSortMethod::TrackList) { query.join("tracklist t_l ON t_l_e.tracklist_id = t_l.id"); query.join("tracklist_entry t_l_e ON t.id = t_l_e.track_id"); @@ -249,7 +250,7 @@ namespace Database return std::vector(res.begin(), res.end()); } - RangeResults Track::findPaths(Session& session, Range range) + RangeResults Track::findPaths(Session& session, std::optional range) { using QueryResultType = std::tuple; session.checkSharedLocked(); @@ -257,7 +258,7 @@ namespace Database // TODO Dbo traits on filesystem auto query{ session.getDboSession().query("SELECT id, file_path FROM track") }; - RangeResults queryResults{ Utils::execQuery(query, range) }; + RangeResults queryResults{ Utils::execQuery(query, range) }; RangeResults res; res.range = queryResults.range; @@ -267,23 +268,23 @@ namespace Database std::transform(std::cbegin(queryResults.results), std::cend(queryResults.results), std::back_inserter(res.results), [](const QueryResultType& queryResult) { - return PathResult{ std::get<0>(queryResult), std::get<1>(queryResult) }; + return PathResult{ std::get(queryResult), std::move(std::get(queryResult)) }; }); return res; } - RangeResults Track::findIdsTrackMBIDDuplicates(Session& session, Range range) + RangeResults Track::findIdsTrackMBIDDuplicates(Session& session, std::optional range) { session.checkSharedLocked(); auto query{ session.getDboSession().query("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)") .orderBy("track.release_id,track.disc_number,track.track_number,track.mbid") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } - RangeResults Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, Range range) + RangeResults Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional range) { session.checkSharedLocked(); @@ -291,7 +292,7 @@ namespace Database .where("LENGTH(t.recording_mbid) > 0") .where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)") }; - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } std::vector Track::getClusters() const @@ -316,7 +317,7 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery(session, parameters) }; - return Utils::execQuery(query, parameters.range); + return Utils::execQuery(query, parameters.range); } RangeResults Track::find(Session& session, const FindParameters& parameters) @@ -324,10 +325,18 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery>(session, parameters) }; - return Utils::execQuery(query, parameters.range); + return Utils::execQuery(query, parameters.range); } - RangeResults Track::findSimilarTrackIds(Session& session, const std::vector& tracks, Range range) + void Track::find(Session& session, const FindParameters& params, std::function func) + { + session.checkSharedLocked(); + + auto query{ createQuery>(session, params)}; + Utils::execQuery(query, params.range, func); + } + + RangeResults Track::findSimilarTrackIds(Session& session, const std::vector& tracks, std::optional range) { assert(!tracks.empty()); session.checkSharedLocked(); @@ -354,7 +363,7 @@ namespace Database for (TrackId trackId : tracks) query.bind(trackId); - return Utils::execQuery(query, range); + return Utils::execQuery(query, range); } void Track::clearArtistLinks() diff --git a/src/libs/services/database/impl/TrackArtistLink.cpp b/src/libs/services/database/impl/TrackArtistLink.cpp index ab64672a..97fda619 100644 --- a/src/libs/services/database/impl/TrackArtistLink.cpp +++ b/src/libs/services/database/impl/TrackArtistLink.cpp @@ -84,7 +84,7 @@ namespace Database session.checkSharedLocked(); auto query{ createQuery(session, params) }; - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); } EnumSet TrackArtistLink::findUsedTypes(Session& session) diff --git a/src/libs/services/database/impl/TrackBookmark.cpp b/src/libs/services/database/impl/TrackBookmark.cpp index 71b09708..48dae983 100644 --- a/src/libs/services/database/impl/TrackBookmark.cpp +++ b/src/libs/services/database/impl/TrackBookmark.cpp @@ -27,57 +27,52 @@ namespace Database { -TrackBookmark::TrackBookmark(ObjectPtr user, ObjectPtr track) -: _user {getDboPtr(user)}, -_track {getDboPtr(track)} -{ -} + TrackBookmark::TrackBookmark(ObjectPtr user, ObjectPtr track) + : _user{ getDboPtr(user) }, + _track{ getDboPtr(track) } + { + } -TrackBookmark::pointer -TrackBookmark::create(Session& session, ObjectPtr user, ObjectPtr track) -{ - return session.getDboSession().add(std::unique_ptr {new TrackBookmark {user, track}}); -} + TrackBookmark::pointer TrackBookmark::create(Session& session, ObjectPtr user, ObjectPtr track) + { + return session.getDboSession().add(std::unique_ptr {new TrackBookmark{ user, track }}); + } -std::size_t -TrackBookmark::getCount(Session& session) -{ - session.checkSharedLocked(); + std::size_t TrackBookmark::getCount(Session& session) + { + session.checkSharedLocked(); - return session.getDboSession().query("SELECT COUNT(*) FROM track_bookmark"); -} + return session.getDboSession().query("SELECT COUNT(*) FROM track_bookmark"); + } -RangeResults -TrackBookmark::find(Session& session, UserId userId, Range range) -{ - session.checkSharedLocked(); + RangeResults TrackBookmark::find(Session& session, UserId userId, std::optional range) + { + session.checkSharedLocked(); - auto query {session.getDboSession().query("SELECT id from track_bookmark") - .where("user_id = ?").bind(userId)}; + auto query{ session.getDboSession().query("SELECT id from track_bookmark") + .where("user_id = ?").bind(userId) }; - return Utils::execQuery(query, range); -} + return Utils::execQuery(query, range); + } -TrackBookmark::pointer -TrackBookmark::find(Session& session, UserId userId, TrackId trackId) -{ - session.checkSharedLocked(); + TrackBookmark::pointer TrackBookmark::find(Session& session, UserId userId, TrackId trackId) + { + session.checkSharedLocked(); - return session.getDboSession().find() - .where("user_id = ?").bind(userId) - .where("track_id = ?").bind(trackId) - .resultValue(); -} + return session.getDboSession().find() + .where("user_id = ?").bind(userId) + .where("track_id = ?").bind(trackId) + .resultValue(); + } -TrackBookmark::pointer -TrackBookmark::find(Session& session, TrackBookmarkId id) -{ - session.checkSharedLocked(); + TrackBookmark::pointer TrackBookmark::find(Session& session, TrackBookmarkId id) + { + session.checkSharedLocked(); - return session.getDboSession().find() - .where("id = ?").bind(id) - .resultValue(); -} + return session.getDboSession().find() + .where("id = ?").bind(id) + .resultValue(); + } } // namespace Database diff --git a/src/libs/services/database/impl/TrackFeatures.cpp b/src/libs/services/database/impl/TrackFeatures.cpp index 35f92b27..c7a0293e 100644 --- a/src/libs/services/database/impl/TrackFeatures.cpp +++ b/src/libs/services/database/impl/TrackFeatures.cpp @@ -30,99 +30,92 @@ namespace Database { -TrackFeatures::TrackFeatures(ObjectPtr track, const std::string& jsonEncodedFeatures) -: _data {jsonEncodedFeatures}, -_track {getDboPtr(track)} -{ -} + TrackFeatures::TrackFeatures(ObjectPtr track, const std::string& jsonEncodedFeatures) + : _data{ jsonEncodedFeatures }, + _track{ getDboPtr(track) } + { + } -TrackFeatures::pointer -TrackFeatures::create(Session& session, ObjectPtr track, const std::string& jsonEncodedFeatures) -{ - return session.getDboSession().add(std::unique_ptr {new TrackFeatures {track, jsonEncodedFeatures}}); -} + TrackFeatures::pointer TrackFeatures::create(Session& session, ObjectPtr track, const std::string& jsonEncodedFeatures) + { + return session.getDboSession().add(std::unique_ptr {new TrackFeatures{ track, jsonEncodedFeatures }}); + } -std::size_t -TrackFeatures::getCount(Session& session) -{ - session.checkSharedLocked(); + std::size_t TrackFeatures::getCount(Session& session) + { + session.checkSharedLocked(); - return session.getDboSession().query("SELECT COUNT(*) FROM track_features"); -} + return session.getDboSession().query("SELECT COUNT(*) FROM track_features"); + } -TrackFeatures::pointer -TrackFeatures::find(Session& session, TrackFeaturesId id) -{ - session.checkSharedLocked(); + TrackFeatures::pointer TrackFeatures::find(Session& session, TrackFeaturesId id) + { + session.checkSharedLocked(); - return session.getDboSession().find() - .where("id = ?").bind(id) - .resultValue(); -} + return session.getDboSession().find() + .where("id = ?").bind(id) + .resultValue(); + } -TrackFeatures::pointer -TrackFeatures::find(Session& session, TrackId trackId) -{ - session.checkSharedLocked(); + TrackFeatures::pointer TrackFeatures::find(Session& session, TrackId trackId) + { + session.checkSharedLocked(); - return session.getDboSession().find() - .where("track_id = ?").bind(trackId) - .resultValue(); -} + return session.getDboSession().find() + .where("track_id = ?").bind(trackId) + .resultValue(); + } -RangeResults -TrackFeatures::find(Session& session, Range range) -{ - session.checkSharedLocked(); + RangeResults TrackFeatures::find(Session& session, std::optional range) + { + session.checkSharedLocked(); - auto query {session.getDboSession().query("SELECT id from track_features")}; + auto query{ session.getDboSession().query("SELECT id from track_features") }; - return Utils::execQuery(query, range); -} + return Utils::execQuery(query, range); + } -FeatureValues -TrackFeatures::getFeatureValues(const FeatureName& featureNode) const -{ - FeatureValuesMap featuresValuesMap {getFeatureValuesMap({featureNode})}; - return std::move(featuresValuesMap[featureNode]); -} + FeatureValues TrackFeatures::getFeatureValues(const FeatureName& featureNode) const + { + FeatureValuesMap featuresValuesMap{ getFeatureValuesMap({featureNode}) }; + return std::move(featuresValuesMap[featureNode]); + } -FeatureValuesMap -TrackFeatures::getFeatureValuesMap(const std::unordered_set& featureNames) const -{ - FeatureValuesMap res; + FeatureValuesMap TrackFeatures::getFeatureValuesMap(const std::unordered_set& featureNames) const + { + FeatureValuesMap res; - try - { - std::istringstream iss {_data}; - boost::property_tree::ptree root; + try + { + std::istringstream iss{ _data }; + boost::property_tree::ptree root; - boost::property_tree::read_json(iss, root); + boost::property_tree::read_json(iss, root); - for (const FeatureName& featureName : featureNames) - { - FeatureValues& featureValues {res[featureName]}; + for (const FeatureName& featureName : featureNames) + { + FeatureValues& featureValues{ res[featureName] }; - auto node {root.get_child(featureName)}; + auto node{ root.get_child(featureName) }; - bool hasChildren = false; - for (const auto& child : node.get_child("")) - { - hasChildren = true; - featureValues.push_back(child.second.get_value()); - } + bool hasChildren = false; + for (const auto& child : node.get_child("")) + { + hasChildren = true; + featureValues.push_back(child.second.get_value()); + } - if (!hasChildren) - featureValues.push_back(node.get_value()); - } - } - catch (boost::property_tree::ptree_error& error) - { - LMS_LOG(DB, ERROR) << "Track " << _track.id() << ": ptree exception: " << error.what(); - res.clear(); - } + if (!hasChildren) + featureValues.push_back(node.get_value()); + } + } + catch (boost::property_tree::ptree_error& error) + { + LMS_LOG(DB, ERROR) << "Track " << _track.id() << ": ptree exception: " << error.what(); + res.clear(); + } - return res; -} + return res; + } } // namespace Database diff --git a/src/libs/services/database/impl/TrackList.cpp b/src/libs/services/database/impl/TrackList.cpp index 490fc06a..1e98fd6e 100644 --- a/src/libs/services/database/impl/TrackList.cpp +++ b/src/libs/services/database/impl/TrackList.cpp @@ -33,665 +33,299 @@ #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace Database { - -TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr user) - : _name {name} -, _type {type} -, _isPublic {isPublic} -, _creationDateTime {Utils::normalizeDateTime(Wt::WDateTime::currentDateTime())} -, _lastModifiedDateTime {Utils::normalizeDateTime(Wt::WDateTime::currentDateTime())} -, _user {getDboPtr(user)} +namespace Database { - assert(user); -} - -TrackList::pointer -TrackList::create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr user) -{ - return session.getDboSession().add(std::unique_ptr {new TrackList {name, type, isPublic, user}}); -} - -std::size_t -TrackList::getCount(Session& session) -{ - session.checkSharedLocked(); - - return session.getDboSession().query("SELECT COUNT(*) FROM tracklist"); -} - - -TrackList::pointer -TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId) -{ - session.checkSharedLocked(); - assert(userId.isValid()); - - return session.getDboSession().find() - .where("name = ?").bind(name) - .where("type = ?").bind(type) - .where("user_id = ?").bind(userId).resultValue(); -} - -RangeResults -TrackList::find(Session& session, const FindParameters& params) -{ - session.checkSharedLocked(); - - auto query {session.getDboSession().query("SELECT DISTINCT t_l.id FROM tracklist t_l")}; - - if (params.user.isValid()) - query.where("t_l.user_id = ?").bind(params.user); - - if (params.type) - query.where("t_l.type = ?").bind(*params.type); - - if (!params.clusters.empty()) - { - query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id"); - query.join("track t ON t.id = t_l_e.track_id"); - - std::ostringstream oss; - oss << "t.id IN (SELECT DISTINCT t.id FROM track t" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " INNER JOIN cluster c ON c.id = t_c.cluster_id"; - - WhereClause clusterClause; - for (const ClusterId clusterId : params.clusters) - { - clusterClause.Or(WhereClause("c.id = ?")); - query.bind(clusterId); - } - - oss << " " << clusterClause.get(); - oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")"; - - query.where(oss.str()); - } - - switch (params.sortMethod) - { - case TrackListSortMethod::None: - break; - case TrackListSortMethod::Name: - query.orderBy("t_l.name COLLATE NOCASE"); - break; - case TrackListSortMethod::LastModifiedDesc: - query.orderBy("t_l.last_modified_date_time DESC"); - break; - } - - return Utils::execQuery(query, params.range); -} - -TrackList::pointer -TrackList::find(Session& session, TrackListId id) -{ - session.checkSharedLocked(); - - return session.getDboSession().find().where("id = ?").bind(id).resultValue(); -} - -bool -TrackList::isEmpty() const -{ - return _entries.empty(); -} - -std::size_t -TrackList::getCount() const -{ - return _entries.size(); -} - -TrackListEntry::pointer -TrackList::getEntry(std::size_t pos) const -{ - TrackListEntry::pointer res; - - auto entries = getEntries(Range {pos, 1}); - if (!entries.empty()) - res = entries.front(); - - return res; -} - -std::vector -TrackList::getEntries(std::optional range) const -{ - assert(session()); - - auto entries { - session()->find() - .where("tracklist_id = ?").bind(getId()) - .orderBy("id") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - return std::vector(entries.begin(), entries.end()); -} - -TrackListEntry::pointer -TrackList::getEntryByTrackAndDateTime(ObjectPtr track, const Wt::WDateTime& dateTime) const -{ - assert(session()); - - return session()->find() - .where("tracklist_id = ?").bind(getId()) - .where("track_id = ?").bind(track->getId()) - .where("date_time = ?").bind(Utils::normalizeDateTime(dateTime)) - .resultValue(); -} - -static -Wt::Dbo::Query> -createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, TrackListId tracklistId, const std::vector& clusterIds, std::optional linkType) -{ - auto query {session.query>(queryStr)}; - query.join("track t ON t.id = t_a_l.track_id"); - query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id"); - query.join("tracklist_entry p_e ON p_e.track_id = t.id"); - query.join("tracklist p ON p.id = p_e.tracklist_id"); - - query.where("p.id = ?").bind(tracklistId); - - 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"; - - 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() << ")"; - - query.where(oss.str()); - } - - return query; -} - -static -Wt::Dbo::Query> -createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, TrackListId tracklistId, const std::vector& clusterIds) -{ - auto query {session.query>(queryStr)}; - query.join("track t ON t.release_id = r.id"); - query.join("tracklist_entry p_e ON p_e.track_id = t.id"); - query.join("tracklist p ON p.id = p_e.tracklist_id"); - - query.where("p.id = ?").bind(tracklistId); - - 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); - } - - oss << " " << clusterClause.get(); - oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; - - query.where(oss.str()); - } - - return query; -} - -static -Wt::Dbo::Query> -createTracksQuery(Wt::Dbo::Session& session, TrackListId tracklistId, const std::vector& clusterIds) -{ - auto query {session.query>("SELECT t from track t INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")}; - - query.where("p.id = ?").bind(tracklistId); - - 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); - } - - oss << " " << clusterClause.get(); - oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")"; - - query.where(oss.str()); - } - - return query; -} - -std::vector -TrackList::getArtists(const std::vector& clusterIds, std::optional linkType, ArtistSortMethod sortMethod, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto query {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType) - .groupBy("a.id").having("p_e.date_time = MAX(p_e.date_time)") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1)}; - - switch (sortMethod) - { - case ArtistSortMethod::None: - break; - case ArtistSortMethod::ByName: - query.orderBy("a.name COLLATE NOCASE"); - break; - case ArtistSortMethod::BySortName: - query.orderBy("a.sort_name COLLATE NOCASE"); - break; - case ArtistSortMethod::Random: - query.orderBy("RANDOM()"); - break; - case ArtistSortMethod::LastWritten: - case ArtistSortMethod::StarredDateDesc: - assert(false); // Not implemented! - break; - } - - Wt::Dbo::collection> collection {query.resultList()}; - - auto res {std::vector(collection.begin(), collection.end())}; - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - - -std::vector> -TrackList::getReleases(const std::vector& clusterIds, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto collection {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds) - .groupBy("r.id").having("p_e.date_time = MAX(p_e.date_time)") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - std::vector res(collection.begin(), collection.end()); - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - -std::vector> -TrackList::getTracks(const std::vector& clusterIds, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto collection {createTracksQuery(*session(), getId(), clusterIds) - .groupBy("t.id").having("p_e.date_time = MAX(p_e.date_time)") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - std::vector res(collection.begin(), collection.end()); - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - -std::vector -TrackList::getArtistsOrderedByRecentFirst(const std::vector& clusterIds, std::optional linkType, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto collection {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType) - .groupBy("a.id").having("p_e.date_time = MAX(p_e.date_time)") - .orderBy("p_e.date_time DESC, p_e.id DESC") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - auto res {std::vector(collection.begin(), collection.end())}; - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - -std::vector -TrackList::getReleasesOrderedByRecentFirst(const std::vector& clusterIds, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto collection {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds) - .groupBy("r.id").having("p_e.date_time = MAX(p_e.date_time)") - .orderBy("p_e.date_time DESC, p_e.id DESC") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - std::vector res(collection.begin(), collection.end()); - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - -std::vector -TrackList::getTracksOrderedByRecentFirst(const std::vector& clusterIds, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto collection {createTracksQuery(*session(), getId(), clusterIds) - .groupBy("t.id").having("p_e.date_time = MAX(p_e.date_time)") - .orderBy("p_e.date_time DESC, p_e.id DESC") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - std::vector res(collection.begin(), collection.end()); - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - -std::vector -TrackList::getClusters() const -{ - assert(session()); - - auto res {session()->query>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id") - .where("p.id = ?").bind(getId()) - .groupBy("c.id") - .orderBy("COUNT(c.id) DESC") - .resultList()}; - - return std::vector(res.begin(), res.end()); -} - -std::vector> -TrackList::getClusterGroups(const std::vector& clusterTypes, std::size_t size) const -{ - assert(session()); - std::vector> res; - - if (clusterTypes.empty()) - return res; - - auto query {session()->query>("SELECT c from cluster c")}; - - query.join("track t ON c.id = t_c.cluster_id") - .join("track_cluster t_c ON t_c.track_id = t.id") - .join("cluster_type c_type ON c.cluster_type_id = c_type.id") - .join("tracklist_entry t_l_e ON t_l_e.track_id = t.id") - .join("tracklist t_l ON t_l.id = t_l_e.tracklist_id") - .where("t_l.id = ?").bind(getId()); - - { - std::ostringstream oss; - oss << "c_type.id IN ("; - bool first {true}; - for (auto clusterType : clusterTypes) - { - if (!first) - oss << ", "; - oss << "?"; - query.bind(clusterType ->getId()); - first = false; - } - oss << ")"; - query.where(oss.str()); - } - query.groupBy("c.id"); - query.orderBy("COUNT(c.id) DESC"); - - auto queryRes {query.resultList()}; - - std::map> clustersByType; - for (const Wt::Dbo::ptr& cluster : queryRes) - { - if (clustersByType[cluster->getType()->getId()].size() < size) - clustersByType[cluster->getType()->getId()].push_back(cluster); - } - - for (const auto& [clusterTypeId, clusters] : clustersByType) - res.push_back(clusters); - - return res; -} - -bool -TrackList::hasTrack(TrackId trackId) const -{ - assert(session()); - - Wt::Dbo::collection res = session()->query("SELECT p_e from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id") - .where("p_e.track_id = ?").bind(trackId) - .where("p.id = ?").bind(getId()); - - return res.size() > 0; -} - -std::vector -TrackList::getSimilarTracks(std::optional offset, std::optional size) const -{ - assert(session()); - - auto res {session()->query>( - "SELECT t FROM track t" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " WHERE " - " (t_c.cluster_id IN (SELECT DISTINCT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id WHERE p.id = ?)" - " AND t.id NOT IN (SELECT tracklist_t.id FROM track tracklist_t INNER JOIN tracklist_entry t_e ON t_e.track_id = tracklist_t.id WHERE t_e.tracklist_id = ?))" - ) - .bind(getId()) - .bind(getId()) - .groupBy("t.id") - .orderBy("COUNT(*) DESC, RANDOM()") - .limit(size ? static_cast(*size) : -1) - .offset(offset ? static_cast(*offset) : -1) - .resultList()}; - - return std::vector(res.begin(), res.end()); -} - -std::vector -TrackList::getTrackIds() const -{ - assert(session()); - - Wt::Dbo::collection res = session()->query("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id") - .where("p.id = ?").bind(getId()); - - return std::vector(res.begin(), res.end()); -} - -std::chrono::milliseconds -TrackList::getDuration() const -{ - assert(session()); - - using milli = std::chrono::duration; - - Wt::Dbo::Query query {session()->query("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id") - .where("p_e.tracklist_id = ?").bind(getId())}; - - return query.resultValue(); -} - -void -TrackList::setLastModifiedDateTime(const Wt::WDateTime& dateTime) -{ - _lastModifiedDateTime = Utils::normalizeDateTime(dateTime); -} - -std::vector -TrackList::getTopArtists(const std::vector& clusterIds, std::optional linkType, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto query {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType)}; - - auto collection {query - .orderBy("COUNT(a.id) DESC") - .groupBy("a.id") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - std::vector res(collection.begin(), collection.end()); - - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - -std::vector -TrackList::getTopReleases(const std::vector& clusterIds, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto query {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds)}; - auto collection {query - .orderBy("COUNT(r.id) DESC") - .groupBy("r.id") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - std::vector res(collection.begin(), collection.end()); - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - -std::vector -TrackList::getTopTracks(const std::vector& clusterIds, std::optional range, bool& moreResults) const -{ - assert(session()); - - auto query {createTracksQuery(*session(), getId(), clusterIds)}; - auto collection {query - .orderBy("COUNT(t.id) DESC") - .groupBy("t.id") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList()}; - - std::vector res(collection.begin(), collection.end()); - if (range && res.size() == static_cast(range->size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - -TrackListEntry::TrackListEntry(ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime) -: _dateTime {Utils::normalizeDateTime(dateTime)} -, _track {getDboPtr(track)} -, _tracklist {getDboPtr(tracklist)} -{ - assert(track); - assert(tracklist); -} - -TrackListEntry::pointer -TrackListEntry::create(Session& session, ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime) -{ - return session.getDboSession().add(std::unique_ptr {new TrackListEntry {track, tracklist, dateTime}}); -} - -void -TrackListEntry::onPostCreated() -{ - _tracklist.modify()->setLastModifiedDateTime(Utils::normalizeDateTime(Wt::WDateTime::currentDateTime())); -} - -void -TrackListEntry::onPreRemove() -{ - _tracklist.modify()->setLastModifiedDateTime(Utils::normalizeDateTime(Wt::WDateTime::currentDateTime())); -} - -TrackListEntry::pointer -TrackListEntry::getById(Session& session, TrackListEntryId id) -{ - session.checkSharedLocked(); - - return session.getDboSession().find().where("id = ?").bind(id).resultValue(); -} + TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr user) + : _name{ name } + , _type{ type } + , _isPublic{ isPublic } + , _creationDateTime{ Utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) } + , _lastModifiedDateTime{ Utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) } + , _user{ getDboPtr(user) } + { + assert(user); + } + + TrackList::pointer TrackList::create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr user) + { + return session.getDboSession().add(std::unique_ptr {new TrackList{ name, type, isPublic, user }}); + } + + std::size_t TrackList::getCount(Session& session) + { + session.checkSharedLocked(); + + return session.getDboSession().query("SELECT COUNT(*) FROM tracklist"); + } + + + TrackList::pointer TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId) + { + session.checkSharedLocked(); + assert(userId.isValid()); + + return session.getDboSession().find() + .where("name = ?").bind(name) + .where("type = ?").bind(type) + .where("user_id = ?").bind(userId).resultValue(); + } + + RangeResults TrackList::find(Session& session, const FindParameters& params) + { + session.checkSharedLocked(); + + auto query{ session.getDboSession().query("SELECT DISTINCT t_l.id FROM tracklist t_l") }; + + if (params.user.isValid()) + query.where("t_l.user_id = ?").bind(params.user); + + if (params.type) + query.where("t_l.type = ?").bind(*params.type); + + if (!params.clusters.empty()) + { + query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id"); + query.join("track t ON t.id = t_l_e.track_id"); + + std::ostringstream oss; + oss << "t.id IN (SELECT DISTINCT t.id FROM track t" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " INNER JOIN cluster c ON c.id = t_c.cluster_id"; + + WhereClause clusterClause; + for (const ClusterId clusterId : params.clusters) + { + clusterClause.Or(WhereClause("c.id = ?")); + query.bind(clusterId); + } + + oss << " " << clusterClause.get(); + oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")"; + + query.where(oss.str()); + } + + switch (params.sortMethod) + { + case TrackListSortMethod::None: + break; + case TrackListSortMethod::Name: + query.orderBy("t_l.name COLLATE NOCASE"); + break; + case TrackListSortMethod::LastModifiedDesc: + query.orderBy("t_l.last_modified_date_time DESC"); + break; + } + + return Utils::execQuery(query, params.range); + } + + TrackList::pointer TrackList::find(Session& session, TrackListId id) + { + session.checkSharedLocked(); + + return session.getDboSession().find().where("id = ?").bind(id).resultValue(); + } + + bool TrackList::isEmpty() const + { + return _entries.empty(); + } + + std::size_t TrackList::getCount() const + { + return _entries.size(); + } + + TrackListEntry::pointer TrackList::getEntry(std::size_t pos) const + { + TrackListEntry::pointer res; + + auto entries = getEntries(Range{ pos, 1 }); + if (!entries.empty()) + res = entries.front(); + + return res; + } + + std::vector TrackList::getEntries(std::optional range) const + { + assert(session()); + + auto entries{ + session()->find() + .where("tracklist_id = ?").bind(getId()) + .orderBy("id") + .limit(range ? static_cast(range->size) + 1 : -1) + .offset(range ? static_cast(range->offset) : -1) + .resultList() }; + + return std::vector(entries.begin(), entries.end()); + } + + TrackListEntry::pointer TrackList::getEntryByTrackAndDateTime(ObjectPtr track, const Wt::WDateTime& dateTime) const + { + assert(session()); + + return session()->find() + .where("tracklist_id = ?").bind(getId()) + .where("track_id = ?").bind(track->getId()) + .where("date_time = ?").bind(Utils::normalizeDateTime(dateTime)) + .resultValue(); + } + + std::vector TrackList::getClusters() const + { + assert(session()); + + auto res{ session()->query>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id") + .where("p.id = ?").bind(getId()) + .groupBy("c.id") + .orderBy("COUNT(c.id) DESC") + .resultList() }; + + return std::vector(res.begin(), res.end()); + } + + std::vector> TrackList::getClusterGroups(const std::vector& clusterTypes, std::size_t size) const + { + assert(session()); + std::vector> res; + + if (clusterTypes.empty()) + return res; + + auto query{ session()->query>("SELECT c from cluster c") }; + + query.join("track t ON c.id = t_c.cluster_id") + .join("track_cluster t_c ON t_c.track_id = t.id") + .join("cluster_type c_type ON c.cluster_type_id = c_type.id") + .join("tracklist_entry t_l_e ON t_l_e.track_id = t.id") + .join("tracklist t_l ON t_l.id = t_l_e.tracklist_id") + .where("t_l.id = ?").bind(getId()); + + { + std::ostringstream oss; + oss << "c_type.id IN ("; + bool first{ true }; + for (auto clusterType : clusterTypes) + { + if (!first) + oss << ", "; + oss << "?"; + query.bind(clusterType->getId()); + first = false; + } + oss << ")"; + query.where(oss.str()); + } + query.groupBy("c.id"); + query.orderBy("COUNT(c.id) DESC"); + + auto queryRes{ query.resultList() }; + + std::map> clustersByType; + for (const Wt::Dbo::ptr& cluster : queryRes) + { + if (clustersByType[cluster->getType()->getId()].size() < size) + clustersByType[cluster->getType()->getId()].push_back(cluster); + } + + for (const auto& [clusterTypeId, clusters] : clustersByType) + res.push_back(clusters); + + return res; + } + + bool TrackList::hasTrack(TrackId trackId) const + { + assert(session()); + + Wt::Dbo::collection res = session()->query("SELECT p_e from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id") + .where("p_e.track_id = ?").bind(trackId) + .where("p.id = ?").bind(getId()); + + return res.size() > 0; + } + + std::vector TrackList::getSimilarTracks(std::optional offset, std::optional size) const + { + assert(session()); + + auto res{ session()->query>( + "SELECT t FROM track t" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " WHERE " + " (t_c.cluster_id IN (SELECT DISTINCT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id WHERE p.id = ?)" + " AND t.id NOT IN (SELECT tracklist_t.id FROM track tracklist_t INNER JOIN tracklist_entry t_e ON t_e.track_id = tracklist_t.id WHERE t_e.tracklist_id = ?))" + ) + .bind(getId()) + .bind(getId()) + .groupBy("t.id") + .orderBy("COUNT(*) DESC, RANDOM()") + .limit(size ? static_cast(*size) : -1) + .offset(offset ? static_cast(*offset) : -1) + .resultList() }; + + return std::vector(res.begin(), res.end()); + } + + std::vector TrackList::getTrackIds() const + { + assert(session()); + + Wt::Dbo::collection res = session()->query("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id") + .where("p.id = ?").bind(getId()); + + return std::vector(res.begin(), res.end()); + } + + std::chrono::milliseconds TrackList::getDuration() const + { + assert(session()); + + using milli = std::chrono::duration; + + Wt::Dbo::Query query{ session()->query("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id") + .where("p_e.tracklist_id = ?").bind(getId()) }; + + return query.resultValue(); + } + + void TrackList::setLastModifiedDateTime(const Wt::WDateTime& dateTime) + { + _lastModifiedDateTime = Utils::normalizeDateTime(dateTime); + } + + TrackListEntry::TrackListEntry(ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime) + : _dateTime{ Utils::normalizeDateTime(dateTime) } + , _track{ getDboPtr(track) } + , _tracklist{ getDboPtr(tracklist) } + { + assert(track); + assert(tracklist); + } + + TrackListEntry::pointer TrackListEntry::create(Session& session, ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime) + { + return session.getDboSession().add(std::unique_ptr {new TrackListEntry{ track, tracklist, dateTime }}); + } + + void TrackListEntry::onPostCreated() + { + _tracklist.modify()->setLastModifiedDateTime(Utils::normalizeDateTime(Wt::WDateTime::currentDateTime())); + } + + void TrackListEntry::onPreRemove() + { + _tracklist.modify()->setLastModifiedDateTime(Utils::normalizeDateTime(Wt::WDateTime::currentDateTime())); + } + + TrackListEntry::pointer TrackListEntry::getById(Session& session, TrackListEntryId id) + { + session.checkSharedLocked(); + + return session.getDboSession().find().where("id = ?").bind(id).resultValue(); + } } // namespace Database diff --git a/src/libs/services/database/impl/User.cpp b/src/libs/services/database/impl/User.cpp index 926b4ac3..ca833435 100644 --- a/src/libs/services/database/impl/User.cpp +++ b/src/libs/services/database/impl/User.cpp @@ -58,7 +58,7 @@ namespace Database { if (params.feedbackBackend) query.where("feedback_backend = ?").bind(*params.feedbackBackend); - return Utils::execQuery(query, params.range); + return Utils::execQuery(query, params.range); } User::pointer User::findDemoUser(Session& session) diff --git a/src/libs/services/database/impl/Utils.hpp b/src/libs/services/database/impl/Utils.hpp index 213bce59..85623814 100644 --- a/src/libs/services/database/impl/Utils.hpp +++ b/src/libs/services/database/impl/Utils.hpp @@ -19,6 +19,7 @@ #pragma once +#include #include #include @@ -30,57 +31,54 @@ namespace Database::Utils { #define ESCAPE_CHAR_STR "\\" - static inline constexpr char escapeChar {'\\'}; - std::string escapeLikeKeyword(std::string_view keywords); + static inline constexpr char escapeChar{ '\\' }; + std::string escapeLikeKeyword(std::string_view keywords); - template - RangeResults - execQuery(Wt::Dbo::Query& query, Range range) - { - RangeResults res; + template + void applyRange(Query& query, std::optional range) + { + if (range) + { + query.limit(static_cast(range->size)); + query.offset(static_cast(range->offset)); + } + } - auto collection {query.limit(range.size ? static_cast(range.size) + 1 : -1) - .offset(range.offset ? static_cast(range.offset) : -1) - .resultList()}; + template + RangeResults execQuery(Query& query, std::optional range) + { + RangeResults res; - res.results.assign(collection.begin(), collection.end()); - if (range.size && res.results.size() == static_cast(range.size) + 1) - { - res.moreResults = true; - res.results.pop_back(); - } - else - res.moreResults = false; + if (range) + applyRange(query, Range{ range->offset, range->size + 1 }); - res.range.offset = range.offset; - res.range.size = res.results.size(); - return res; - } + auto collection{ query.resultList() }; + res.results.assign(collection.begin(), collection.end()); + if (range && res.results.size() == static_cast(range->size) + 1) + { + // TODO may optim by not actually requesting the last one + res.moreResults = true; + res.results.pop_back(); + } + else + res.moreResults = false; - template - RangeResults - execQuery(Wt::Dbo::Query>& query, Range range) - { - RangeResults res; + res.range.offset = range->offset; + res.range.size = res.results.size(); - auto collection {query.limit(range.size ? static_cast(range.size) + 1 : -1) - .offset(range.offset ? static_cast(range.offset) : -1) - .resultList()}; + return res; + } - res.results.assign(collection.begin(), collection.end()); - if (range.size && res.results.size() == static_cast(range.size) + 1) - { - res.moreResults = true; - res.results.pop_back(); - } - else - res.moreResults = false; + template + void execQuery(Query& query, std::optional range, std::function func) + { + if (range) + applyRange(query, range); - res.range.offset = range.offset; - res.range.size = res.results.size(); - return res; - } + for (const auto& res : query.resultList()) + func(res); + } - Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); + Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); } // namespace Database::Utils diff --git a/src/libs/services/database/include/services/database/Artist.hpp b/src/libs/services/database/include/services/database/Artist.hpp index 7fcaadac..e561df6b 100644 --- a/src/libs/services/database/include/services/database/Artist.hpp +++ b/src/libs/services/database/include/services/database/Artist.hpp @@ -58,7 +58,7 @@ namespace Database std::vector keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field) std::optional linkType; // if set, only artists that have produced at least one track with this link type ArtistSortMethod sortMethod{ ArtistSortMethod::None }; - Range range; + std::optional range; Wt::WDateTime writtenAfter; UserId starringUser; // only artists starred by this user std::optional feedbackBackend; // and for this feedback backend @@ -69,7 +69,7 @@ namespace Database FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } FindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } FindParameters& setSortMethod(ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } - FindParameters& setRange(Range _range) { range = _range; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } FindParameters& setTrack(TrackId _track) { track = _track; return *this; } @@ -84,8 +84,9 @@ namespace Database static pointer find(Session& session, ArtistId id); static std::vector find(Session& session, const std::string& name); // exact match on name field static RangeResults find(Session& session, const FindParameters& parameters); + static void find(Session& session, const FindParameters& parameters, std::function func); static RangeResults findIds(Session& session, const FindParameters& parameters); - static RangeResults findOrphanIds(Session& session, Range range); // No track related + static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // No track related static bool exists(Session& session, ArtistId id); // Accessors @@ -94,7 +95,7 @@ namespace Database std::optional getMBID() const { return UUID::fromString(_MBID); } // No artistLinkTypes means get them all - RangeResults findSimilarArtistIds(EnumSet artistLinkTypes = {}, Range range = {}) const; + RangeResults findSimilarArtistIds(EnumSet artistLinkTypes = {}, std::optional range = std::nullopt) const; // Get the cluster of the tracks made by this artist // Each clusters are grouped by cluster type, sorted by the number of occurence diff --git a/src/libs/services/database/include/services/database/Cluster.hpp b/src/libs/services/database/include/services/database/Cluster.hpp index fa7c1c0a..7220d647 100644 --- a/src/libs/services/database/include/services/database/Cluster.hpp +++ b/src/libs/services/database/include/services/database/Cluster.hpp @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include @@ -45,12 +46,12 @@ namespace Database { public: struct FindParameters { - Range range; - ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type - TrackId track; // if set, clusters involved in this track - ReleaseId release; // if set, clusters involved in this release + std::optional range; + ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type + TrackId track; // if set, clusters involved in this track + ReleaseId release; // if set, clusters involved in this release - FindParameters& setRange(Range _range) { range = _range; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } FindParameters& setClusterType(ClusterTypeId _clusterType) { clusterType = _clusterType; return *this; } FindParameters& setTrack(TrackId _track) { track = _track; return *this; } FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } @@ -60,11 +61,11 @@ namespace Database { // Find utility static std::size_t getCount(Session& session); - static RangeResults findIds(Session& session, const FindParameters& range); - static RangeResults find(Session& session, const FindParameters& range); - static void find(Session& session, const FindParameters& range, std::function _func); - static pointer find(Session& session, ClusterId id); - static RangeResults findOrphans(Session& session, Range range); + static RangeResults findIds(Session& session, const FindParameters& params); + static RangeResults find(Session& session, const FindParameters& params); + static void find(Session& session, const FindParameters& params, std::function _func); + static pointer find(Session& session, ClusterId id); + static RangeResults findOrphans(Session& session, std::optional range = std::nullopt); // May be very slow static std::size_t computeTrackCount(Session& session, ClusterId id); @@ -74,7 +75,7 @@ namespace Database { std::string_view getName() const { return _name; } ObjectPtr getType() const { return _clusterType; } std::size_t getTracksCount() const { return _trackCount; } - RangeResults getTracks(Range range) const; + RangeResults getTracks(std::optional range = std::nullopt) const; std::size_t getReleasesCount() const { return _releaseCount; }; void setReleaseCount(std::size_t releaseCount) { _releaseCount = releaseCount; } @@ -116,11 +117,11 @@ namespace Database { // Getters static std::size_t getCount(Session& session); - static RangeResults find(Session& session, Range range); + static RangeResults find(Session& session, std::optional range = std::nullopt); static pointer find(Session& session, std::string_view name); static pointer find(Session& session, ClusterTypeId id); - static RangeResults findOrphans(Session& session, Range range); - static RangeResults findUsed(Session& session, Range range); + static RangeResults findOrphans(Session& session, std::optional range = std::nullopt); + static RangeResults findUsed(Session& session, std::optional range = std::nullopt); static void remove(Session& session, const std::string& name); diff --git a/src/libs/services/database/include/services/database/Listen.hpp b/src/libs/services/database/include/services/database/Listen.hpp index 20930bc8..66eadcf3 100644 --- a/src/libs/services/database/include/services/database/Listen.hpp +++ b/src/libs/services/database/include/services/database/Listen.hpp @@ -19,6 +19,8 @@ #pragma once +#include + #include #include @@ -45,10 +47,10 @@ namespace Database struct FindParameters { - UserId user; - std::optional backend; - std::optional syncState; - Range range; + UserId user; + std::optional backend; + std::optional syncState; + std::optional range; FindParameters& setUser(UserId _user) { user = _user; return *this; } FindParameters& setScrobblingBackend(ScrobblingBackend _backend) { backend = _backend; return *this; } @@ -63,13 +65,13 @@ namespace Database static RangeResults find(Session& session, const FindParameters& parameters); // Stats - static RangeResults getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType, Range range = {}); - static RangeResults getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, Range range = {}); - static RangeResults getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, Range range = {}); + static RangeResults getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType, std::optional range = std::nullopt); + static RangeResults getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional range = std::nullopt); + static RangeResults getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional range = std::nullopt); - static RangeResults getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType, Range range = {}); - static RangeResults getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, Range range = {}); - static RangeResults getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, Range range = {}); + static RangeResults getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType, std::optional range = std::nullopt); + static RangeResults getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional range = std::nullopt); + static RangeResults getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional range = std::nullopt); static std::size_t getCount(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId); static std::size_t getCount(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId trackId); diff --git a/src/libs/services/database/include/services/database/Release.hpp b/src/libs/services/database/include/services/database/Release.hpp index 28035a38..2c943227 100644 --- a/src/libs/services/database/include/services/database/Release.hpp +++ b/src/libs/services/database/include/services/database/Release.hpp @@ -53,7 +53,7 @@ namespace Database std::vector clusters; // if non empty, releases that belong to these clusters std::vector keywords; // if non empty, name must match all of these keywords ReleaseSortMethod sortMethod{ ReleaseSortMethod::None }; - Range range; + std::optional range; Wt::WDateTime writtenAfter; std::optional dateRange; UserId starringUser; // only releases starred by this user @@ -67,7 +67,7 @@ namespace Database FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } FindParameters& setSortMethod(ReleaseSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } - FindParameters& setRange(Range _range) { range = _range; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } FindParameters& setDateRange(const std::optional& _dateRange) { dateRange = _dateRange; return *this; } FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } @@ -89,9 +89,10 @@ namespace Database static std::vector find(Session& session, const std::string& name); static pointer find(Session& session, ReleaseId id); static RangeResults find(Session& session, const FindParameters& parameters); + static void find(Session& session, const FindParameters& parameters, std::function func); static RangeResults findIds(Session& session, const FindParameters& parameters); - static RangeResults findOrphanIds(Session& session, Range range); // not track related - static RangeResults findIdsOrderedByArtist(Session& session, Range range); + static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // not track related + static RangeResults findIdsOrderedByArtist(Session& session, std::optional range = std::nullopt); // Get the cluster of the tracks that belong to this release // Each clusters are grouped by cluster type, sorted by the number of occurence (max to min) diff --git a/src/libs/services/database/include/services/database/StarredTrack.hpp b/src/libs/services/database/include/services/database/StarredTrack.hpp index 7b3e50f7..5db11aaa 100644 --- a/src/libs/services/database/include/services/database/StarredTrack.hpp +++ b/src/libs/services/database/include/services/database/StarredTrack.hpp @@ -45,11 +45,11 @@ namespace Database std::optional backend; // for this backend std::optional syncState; // and these states UserId user; // and this user - Range range; + std::optional range; FindParameters& setFeedbackBackend(FeedbackBackend _backend, SyncState _syncState) { backend = _backend; syncState = _syncState; return *this; } FindParameters& setUser(UserId _user) { user = _user; return *this; } - FindParameters& setRange(Range _range) { range = _range; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } }; // Search utility diff --git a/src/libs/services/database/include/services/database/Track.hpp b/src/libs/services/database/include/services/database/Track.hpp index e15aaf1e..07de5a7c 100644 --- a/src/libs/services/database/include/services/database/Track.hpp +++ b/src/libs/services/database/include/services/database/Track.hpp @@ -46,207 +46,208 @@ namespace Database { -class Artist; -class Cluster; -class ClusterType; -class Release; -class Session; -class TrackArtistLink; -class TrackStats; -class User; + class Artist; + class Cluster; + class ClusterType; + class Release; + class Session; + class TrackArtistLink; + class TrackStats; + class User; -class Track final : public Object -{ - public: - struct FindParameters - { - std::vector clusters; // if non empty, tracks that belong to these clusters - std::vector keywords; // if non empty, name must match all of these keywords - std::string name; // if non empty, must match this name - TrackSortMethod sortMethod {TrackSortMethod::None}; - Range range; - Wt::WDateTime writtenAfter; - UserId starringUser; // only tracks starred by this user - std::optional feedbackBackend; // and for this feedback backend - ArtistId artist; // only tracks that involve this artist - std::string artistName; // only tracks that involve this artist name - EnumSet trackArtistLinkTypes; // and for these link types - bool nonRelease {}; // only tracks that do not belong to a release - ReleaseId release; // matching this release - std::string releaseName; // matching this release name - TrackListId trackList; // matching this trackList - std::optional trackNumber; // matching this track number - bool distinct {true}; + class Track final : public Object + { + public: + struct FindParameters + { + std::vector clusters; // if non empty, tracks that belong to these clusters + std::vector keywords; // if non empty, name must match all of these keywords + std::string name; // if non empty, must match this name + TrackSortMethod sortMethod{ TrackSortMethod::None }; + std::optional range; + Wt::WDateTime writtenAfter; + UserId starringUser; // only tracks starred by this user + std::optional feedbackBackend; // and for this feedback backend + ArtistId artist; // only tracks that involve this artist + std::string artistName; // only tracks that involve this artist name + EnumSet trackArtistLinkTypes; // and for these link types + bool nonRelease{}; // only tracks that do not belong to a release + ReleaseId release; // matching this release + std::string releaseName; // matching this release name + TrackListId trackList; // matching this trackList + std::optional trackNumber; // matching this track number + bool distinct{ true }; - FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } - FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } - FindParameters& setName(std::string_view _name) { name = _name; return *this; } - FindParameters& setSortMethod(TrackSortMethod _method) { sortMethod = _method; return *this; } - FindParameters& setRange(Range _range) { range = _range; return *this; } - FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } - FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } - FindParameters& setArtist(ArtistId _artist, EnumSet _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } - FindParameters& setArtistName(std::string_view _artistName, EnumSet _trackArtistLinkTypes = {}) { artistName = _artistName; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } - FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; } - FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } - FindParameters& setReleaseName(std::string_view _releaseName) { releaseName = _releaseName; return *this; } - FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; } - FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; } - FindParameters& setDistinct(bool _distinct) { distinct = _distinct; return *this; } - }; + FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } + FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } + FindParameters& setName(std::string_view _name) { name = _name; return *this; } + FindParameters& setSortMethod(TrackSortMethod _method) { sortMethod = _method; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } + FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } + FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } + FindParameters& setArtist(ArtistId _artist, EnumSet _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } + FindParameters& setArtistName(std::string_view _artistName, EnumSet _trackArtistLinkTypes = {}) { artistName = _artistName; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } + FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; } + FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } + FindParameters& setReleaseName(std::string_view _releaseName) { releaseName = _releaseName; return *this; } + FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; } + FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; } + FindParameters& setDistinct(bool _distinct) { distinct = _distinct; return *this; } + }; - struct PathResult - { - TrackId trackId; - std::filesystem::path path; - }; + struct PathResult + { + TrackId trackId; + std::filesystem::path path; + }; - Track() = default; + Track() = default; - // Find utility functions - static std::size_t getCount(Session& session); - static pointer findByPath(Session& session, const std::filesystem::path& p); - static pointer find(Session& session, TrackId id); - static bool exists(Session& session, TrackId id); - static std::vector findByRecordingMBID(Session& session, const UUID& MBID); - static std::vector findByMBID(Session& session, const UUID& MBID); - static RangeResults findSimilarTrackIds(Session& session, const std::vector& trackIds, Range range); + // Find utility functions + static std::size_t getCount(Session& session); + static pointer findByPath(Session& session, const std::filesystem::path& p); + static pointer find(Session& session, TrackId id); + static bool exists(Session& session, TrackId id); + static std::vector findByRecordingMBID(Session& session, const UUID& MBID); + static std::vector findByMBID(Session& session, const UUID& MBID); + static RangeResults findSimilarTrackIds(Session& session, const std::vector& trackIds, std::optional range = std::nullopt); - static RangeResults findIds(Session& session, const FindParameters& parameters); - static RangeResults find(Session& session, const FindParameters& parameters); - static RangeResults findPaths(Session& session, Range range); - static RangeResults findIdsTrackMBIDDuplicates(Session& session, Range range); - static RangeResults findIdsWithRecordingMBIDAndMissingFeatures(Session& session, Range range); + static RangeResults findIds(Session& session, const FindParameters& parameters); + static RangeResults find(Session& session, const FindParameters& parameters); + static void find(Session& session, const FindParameters& parameters, std::function func); + static RangeResults findPaths(Session& session, std::optional range = std::nullopt); + static RangeResults findIdsTrackMBIDDuplicates(Session& session, std::optional range = std::nullopt); + static RangeResults findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional range = std::nullopt); - // Accessors - void setScanVersion(std::size_t version) { _scanVersion = version; } - void setTrackNumber(std::optional num) { _trackNumber = num; } - void setDiscNumber(std::optional num) { _discNumber = num; } - void setTotalTrack(std::optional totalTrack) { _totalTrack = totalTrack; } - void setDiscSubtitle(const std::string& name) { _discSubtitle = name; } - void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); } - void setPath(const std::filesystem::path& filePath) { _filePath = filePath; } - void setDuration(std::chrono::milliseconds duration) { _duration = duration; } - void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; } - void setAddedTime(Wt::WDateTime time) { _fileAdded = time; } - void setDate(const Wt::WDate& date) { _date = date; } - void setOriginalDate(const Wt::WDate& date) { _originalDate = date; } - void setHasCover(bool hasCover) { _hasCover = hasCover; } - void setTrackMBID(const std::optional& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; } - void setRecordingMBID(const std::optional& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; } - void setCopyright(const std::string& copyright) { _copyright = std::string(copyright, 0, _maxCopyrightLength); } - void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); } - void setTrackReplayGain(std::optional replayGain) { _trackReplayGain = replayGain; } - void setReleaseReplayGain(std::optional replayGain) { _releaseReplayGain = replayGain; } // may be by disc! - void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; } - void clearArtistLinks(); - void addArtistLink(const ObjectPtr& artistLink); - void setRelease(ObjectPtr release) { _release = getDboPtr(release); } - void setClusters(const std::vector>& clusters ); + // Accessors + void setScanVersion(std::size_t version) { _scanVersion = version; } + void setTrackNumber(std::optional num) { _trackNumber = num; } + void setDiscNumber(std::optional num) { _discNumber = num; } + void setTotalTrack(std::optional totalTrack) { _totalTrack = totalTrack; } + void setDiscSubtitle(const std::string& name) { _discSubtitle = name; } + void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); } + void setPath(const std::filesystem::path& filePath) { _filePath = filePath; } + void setDuration(std::chrono::milliseconds duration) { _duration = duration; } + void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; } + void setAddedTime(Wt::WDateTime time) { _fileAdded = time; } + void setDate(const Wt::WDate& date) { _date = date; } + void setOriginalDate(const Wt::WDate& date) { _originalDate = date; } + void setHasCover(bool hasCover) { _hasCover = hasCover; } + void setTrackMBID(const std::optional& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; } + void setRecordingMBID(const std::optional& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; } + void setCopyright(const std::string& copyright) { _copyright = std::string(copyright, 0, _maxCopyrightLength); } + void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); } + void setTrackReplayGain(std::optional replayGain) { _trackReplayGain = replayGain; } + void setReleaseReplayGain(std::optional replayGain) { _releaseReplayGain = replayGain; } // may be by disc! + void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; } + void clearArtistLinks(); + void addArtistLink(const ObjectPtr& artistLink); + void setRelease(ObjectPtr release) { _release = getDboPtr(release); } + void setClusters(const std::vector>& clusters); - std::size_t getScanVersion() const { return _scanVersion; } - std::optional getTrackNumber() const { return _trackNumber; } - std::optional getTotalTrack() const { return _totalTrack; } - std::optional getDiscNumber() const { return _discNumber; } - const std::string& getDiscSubtitle() const { return _discSubtitle; } - std::string getName() const { return _name; } - std::filesystem::path getPath() const { return _filePath; } - std::chrono::milliseconds getDuration() const { return _duration; } - const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; } - std::optional getYear() const; - std::optional getOriginalYear() const; - Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; } - Wt::WDateTime getAddedTime() const { return _fileAdded; } - bool hasCover() const { return _hasCover; } - std::optional getTrackMBID() const { return UUID::fromString(_trackMBID); } - std::optional getRecordingMBID() const { return UUID::fromString(_recordingMBID); } - std::optional getCopyright() const; - std::optional getCopyrightURL() const; - std::optional getTrackReplayGain() const { return _trackReplayGain; } - std::optional getReleaseReplayGain() const { return _releaseReplayGain; } - std::string_view getArtistDisplayName() const { return _artistDisplayName; } - // no artistLinkTypes means get all - std::vector> getArtists(EnumSet artistLinkTypes) const; // no type means all - std::vector getArtistIds(EnumSet artistLinkTypes) const; // no type means all - std::vector> getArtistLinks() const; - ObjectPtr getRelease() const { return _release; } - std::vector> getClusters() const; - std::vector getClusterIds() const; + std::size_t getScanVersion() const { return _scanVersion; } + std::optional getTrackNumber() const { return _trackNumber; } + std::optional getTotalTrack() const { return _totalTrack; } + std::optional getDiscNumber() const { return _discNumber; } + const std::string& getDiscSubtitle() const { return _discSubtitle; } + std::string getName() const { return _name; } + std::filesystem::path getPath() const { return _filePath; } + std::chrono::milliseconds getDuration() const { return _duration; } + const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; } + std::optional getYear() const; + std::optional getOriginalYear() const; + Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; } + Wt::WDateTime getAddedTime() const { return _fileAdded; } + bool hasCover() const { return _hasCover; } + std::optional getTrackMBID() const { return UUID::fromString(_trackMBID); } + std::optional getRecordingMBID() const { return UUID::fromString(_recordingMBID); } + std::optional getCopyright() const; + std::optional getCopyrightURL() const; + std::optional getTrackReplayGain() const { return _trackReplayGain; } + std::optional getReleaseReplayGain() const { return _releaseReplayGain; } + std::string_view getArtistDisplayName() const { return _artistDisplayName; } + // no artistLinkTypes means get all + std::vector> getArtists(EnumSet artistLinkTypes) const; // no type means all + std::vector getArtistIds(EnumSet artistLinkTypes) const; // no type means all + std::vector> getArtistLinks() const; + ObjectPtr getRelease() const { return _release; } + std::vector> getClusters() const; + std::vector getClusterIds() const; - std::vector>> getClusterGroups(const std::vector>& clusterTypes, std::size_t size) const; + std::vector>> getClusterGroups(const std::vector>& clusterTypes, std::size_t size) const; - template - void persist(Action& a) - { - Wt::Dbo::field(a, _scanVersion, "scan_version"); - Wt::Dbo::field(a, _trackNumber, "track_number"); - Wt::Dbo::field(a, _discNumber, "disc_number"); - Wt::Dbo::field(a, _totalTrack, "total_track"); // here in Track since Release does not have concept of "disc" (yet?) - Wt::Dbo::field(a, _discSubtitle, "disc_subtitle"); // here in Track since Release does not have concept of "disc" (yet?) - Wt::Dbo::field(a, _name, "name"); - Wt::Dbo::field(a, _duration, "duration"); - Wt::Dbo::field(a, _date, "date"); - Wt::Dbo::field(a, _originalDate, "original_date"); - Wt::Dbo::field(a, _filePath, "file_path"); - Wt::Dbo::field(a, _fileLastWrite, "file_last_write"); - Wt::Dbo::field(a, _fileAdded, "file_added"); - Wt::Dbo::field(a, _hasCover, "has_cover"); - Wt::Dbo::field(a, _trackMBID, "mbid"); - Wt::Dbo::field(a, _recordingMBID, "recording_mbid"); - Wt::Dbo::field(a, _copyright, "copyright"); - Wt::Dbo::field(a, _copyrightURL, "copyright_url"); - Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain"); - Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?) - Wt::Dbo::field(a, _artistDisplayName, "artist_display_name"); - Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track"); - Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); - } + template + void persist(Action& a) + { + Wt::Dbo::field(a, _scanVersion, "scan_version"); + Wt::Dbo::field(a, _trackNumber, "track_number"); + Wt::Dbo::field(a, _discNumber, "disc_number"); + Wt::Dbo::field(a, _totalTrack, "total_track"); // here in Track since Release does not have concept of "disc" (yet?) + Wt::Dbo::field(a, _discSubtitle, "disc_subtitle"); // here in Track since Release does not have concept of "disc" (yet?) + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _duration, "duration"); + Wt::Dbo::field(a, _date, "date"); + Wt::Dbo::field(a, _originalDate, "original_date"); + Wt::Dbo::field(a, _filePath, "file_path"); + Wt::Dbo::field(a, _fileLastWrite, "file_last_write"); + Wt::Dbo::field(a, _fileAdded, "file_added"); + Wt::Dbo::field(a, _hasCover, "has_cover"); + Wt::Dbo::field(a, _trackMBID, "mbid"); + Wt::Dbo::field(a, _recordingMBID, "recording_mbid"); + Wt::Dbo::field(a, _copyright, "copyright"); + Wt::Dbo::field(a, _copyrightURL, "copyright_url"); + Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain"); + Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?) + Wt::Dbo::field(a, _artistDisplayName, "artist_display_name"); + Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track"); + Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); + } - private: - friend class ::Database::Session; - Track(const std::filesystem::path& p); - static pointer create(Session& session, const std::filesystem::path& p); + private: + friend class ::Database::Session; + Track(const std::filesystem::path& p); + static pointer create(Session& session, const std::filesystem::path& p); - static constexpr std::size_t _maxNameLength {128}; - static constexpr std::size_t _maxCopyrightLength {128}; - static constexpr std::size_t _maxCopyrightURLLength {128}; + static constexpr std::size_t _maxNameLength{ 128 }; + static constexpr std::size_t _maxCopyrightLength{ 128 }; + static constexpr std::size_t _maxCopyrightURLLength{ 128 }; - int _scanVersion {}; - std::optional _trackNumber {}; - std::optional _discNumber {}; - std::optional _totalTrack {}; - std::string _discSubtitle; - std::string _name; - std::chrono::duration _duration {}; - Wt::WDate _date; - Wt::WDate _originalDate; - std::string _filePath; - Wt::WDateTime _fileLastWrite; - Wt::WDateTime _fileAdded; - bool _hasCover {}; - std::string _trackMBID; - std::string _recordingMBID; - std::string _copyright; - std::string _copyrightURL; - std::optional _trackReplayGain; - std::optional _releaseReplayGain; - std::string _artistDisplayName; + int _scanVersion{}; + std::optional _trackNumber{}; + std::optional _discNumber{}; + std::optional _totalTrack{}; + std::string _discSubtitle; + std::string _name; + std::chrono::duration _duration{}; + Wt::WDate _date; + Wt::WDate _originalDate; + std::string _filePath; + Wt::WDateTime _fileLastWrite; + Wt::WDateTime _fileAdded; + bool _hasCover{}; + std::string _trackMBID; + std::string _recordingMBID; + std::string _copyright; + std::string _copyrightURL; + std::optional _trackReplayGain; + std::optional _releaseReplayGain; + std::string _artistDisplayName; - Wt::Dbo::ptr _release; - Wt::Dbo::collection> _trackArtistLinks; - Wt::Dbo::collection> _clusters; -}; + Wt::Dbo::ptr _release; + Wt::Dbo::collection> _trackArtistLinks; + Wt::Dbo::collection> _clusters; + }; -namespace Debug -{ - struct TrackInfo - { - Session& session; - TrackId trackId; - }; - std::ostream& operator<<(std::ostream& os, const TrackInfo& trackInfo); -} + namespace Debug + { + struct TrackInfo + { + Session& session; + TrackId trackId; + }; + std::ostream& operator<<(std::ostream& os, const TrackInfo& trackInfo); + } } // namespace database diff --git a/src/libs/services/database/include/services/database/TrackArtistLink.hpp b/src/libs/services/database/include/services/database/TrackArtistLink.hpp index a1dec682..657d6ae3 100644 --- a/src/libs/services/database/include/services/database/TrackArtistLink.hpp +++ b/src/libs/services/database/include/services/database/TrackArtistLink.hpp @@ -19,6 +19,7 @@ #pragma once +#include #include #include @@ -45,13 +46,13 @@ namespace Database public: struct FindParameters { - Range range; + std::optional range; std::optional linkType; // if set, only artists that have produced at least one track with this link type ArtistId artist; // if set, links involved with this artist ReleaseId release; // if set, artists involved in this release TrackId track; // if set, artists involved in this track - FindParameters& setRange(Range _range) { range = _range; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } FindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } FindParameters& setArtist(ArtistId _artist) { artist = _artist; return *this; } FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } diff --git a/src/libs/services/database/include/services/database/TrackBookmark.hpp b/src/libs/services/database/include/services/database/TrackBookmark.hpp index fd8ae8df..15aaaf07 100644 --- a/src/libs/services/database/include/services/database/TrackBookmark.hpp +++ b/src/libs/services/database/include/services/database/TrackBookmark.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include @@ -45,7 +46,7 @@ class TrackBookmark final : public Object // Find utility functions static std::size_t getCount(Session& session); static pointer find(Session& session, TrackBookmarkId id); - static RangeResults find(Session& session, UserId userId, Range range); + static RangeResults find(Session& session, UserId userId, std::optional range = std::nullopt); static pointer find(Session& session, UserId userId, TrackId trackId); // Setters diff --git a/src/libs/services/database/include/services/database/TrackFeatures.hpp b/src/libs/services/database/include/services/database/TrackFeatures.hpp index 93dcbcd6..5c7715ec 100644 --- a/src/libs/services/database/include/services/database/TrackFeatures.hpp +++ b/src/libs/services/database/include/services/database/TrackFeatures.hpp @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include @@ -51,7 +52,7 @@ class TrackFeatures final : public Object static std::size_t getCount(Session& session); static pointer find(Session& session, TrackFeaturesId id); static pointer find(Session& session, TrackId trackId); - static RangeResults find(Session& session, Range range); + static RangeResults find(Session& session, std::optional range = std::nullopt); FeatureValues getFeatureValues(const FeatureName& feature) const; FeatureValuesMap getFeatureValuesMap(const std::unordered_set& featureNames) const; diff --git a/src/libs/services/database/include/services/database/TrackList.hpp b/src/libs/services/database/include/services/database/TrackList.hpp index ce84c1a4..dc29ef33 100644 --- a/src/libs/services/database/include/services/database/TrackList.hpp +++ b/src/libs/services/database/include/services/database/TrackList.hpp @@ -36,151 +36,141 @@ namespace Database { -class Artist; -class Cluster; -class ClusterType; -class Release; -class Session; -class Track; -class TrackListEntry; -class User; + class Artist; + class Cluster; + class ClusterType; + class Release; + class Session; + class Track; + class TrackListEntry; + class User; -class TrackList final : public Object -{ - public: - TrackList() = default; + class TrackList final : public Object + { + public: + TrackList() = default; - // Stats utility - std::vector> getTopArtists(const std::vector& clusterIds, std::optional linkType, std::optional range, bool& moreResults) const; - std::vector> getTopReleases(const std::vector& clusterIds, std::optional range, bool& moreResults) const; - std::vector> getTopTracks(const std::vector& clusterIds, std::optional range, bool& moreResults) const; + // Search utility + struct FindParameters + { + std::vector clusters; // if non empty, tracklists that have tracks that belong to these clusters + std::optional range; + std::optional type; + UserId user; // only tracklists owned by this user + TrackListSortMethod sortMethod{ TrackListSortMethod::None }; - // Search utility - struct FindParameters - { - std::vector clusters; // if non empty, tracklists that have tracks that belong to these clusters - Range range; - std::optional type; - UserId user; // only tracklists owned by this user - TrackListSortMethod sortMethod {TrackListSortMethod::None}; + FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } + FindParameters& setType(TrackListType _type) { type = _type; return *this; } + FindParameters& setUser(UserId _user) { user = _user; return *this; } + FindParameters& setSortMethod(TrackListSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } + }; + static std::size_t getCount(Session& session); + static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId); + static pointer find(Session& session, TrackListId tracklistId); + static RangeResults find(Session& session, const FindParameters& params); - FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } - FindParameters& setRange(Range _range) { range = _range; return *this; } - FindParameters& setType(TrackListType _type) { type = _type; return *this; } - FindParameters& setUser(UserId _user) { user = _user; return *this; } - FindParameters& setSortMethod(TrackListSortMethod _sortMethod) {sortMethod = _sortMethod; return *this; } - }; - static std::size_t getCount(Session& session); - static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId); - static pointer find(Session& session, TrackListId tracklistId); - static RangeResults find(Session& session, const FindParameters& params); + // Accessors + std::string_view getName() const { return _name; } + bool isPublic() const { return _isPublic; } + TrackListType getType() const { return _type; } + ObjectPtr getUser() const { return _user; } - // Accessors - std::string_view getName() const { return _name; } - bool isPublic() const { return _isPublic; } - TrackListType getType() const { return _type; } - ObjectPtr getUser() const { return _user; } + // Modifiers + void setName(const std::string& name) { _name = name; } + void setIsPublic(bool isPublic) { _isPublic = isPublic; } + void clear() { _entries.clear(); } - // Modifiers - void setName(const std::string& name) { _name = name; } - void setIsPublic(bool isPublic) { _isPublic = isPublic; } - void clear() { _entries.clear(); } + // Get tracks, ordered by position + bool isEmpty() const; + std::size_t getCount() const; + ObjectPtr getEntry(std::size_t pos) const; + std::vector> getEntries(std::optional range = {}) const; + ObjectPtr getEntryByTrackAndDateTime(ObjectPtr track, const Wt::WDateTime& dateTime) const; - // Get tracks, ordered by position - bool isEmpty() const; - std::size_t getCount() const; - ObjectPtr getEntry(std::size_t pos) const; - std::vector> getEntries(std::optional range = {}) const; - ObjectPtr getEntryByTrackAndDateTime(ObjectPtr track, const Wt::WDateTime& dateTime) const; + RangeResults> getArtists(const std::vector& clusters, std::optional linkType, ArtistSortMethod sortMethod, std::optional range, bool& moreResults) const; + RangeResults> getReleases(const std::vector& clusters, std::optional range, bool& moreResults) const; + RangeResults> getTracks(const std::vector& clusters, std::optional range, bool& moreResults) const; - std::vector> getArtists(const std::vector& clusters, std::optional linkType, ArtistSortMethod sortMethod, std::optional range, bool& moreResults) const; - std::vector> getReleases(const std::vector& clusters, std::optional range, bool& moreResults) const; - std::vector> getTracks(const std::vector& clusters, std::optional range, bool& moreResults) const; + std::vector getTrackIds() const; + std::chrono::milliseconds getDuration() const; - // Sorted by date time - std::vector> getArtistsOrderedByRecentFirst(const std::vector& clusterIds, std::optional linkType, std::optional range, bool& moreResults) const; - std::vector> getReleasesOrderedByRecentFirst(const std::vector& clusterIds, std::optional range, bool& moreResults) const; - std::vector> getTracksOrderedByRecentFirst(const std::vector& clusterIds, std::optional range, bool& moreResults) const; + void setLastModifiedDateTime(const Wt::WDateTime& dateTime); - std::vector getTrackIds() const; - std::chrono::milliseconds getDuration() const; + // Get clusters, order by occurence + std::vector> getClusters() const; + std::vector>> getClusterGroups(const std::vector>& clusterTypes, std::size_t size) const; - void setLastModifiedDateTime(const Wt::WDateTime& dateTime); + bool hasTrack(TrackId trackId) const; - // Get clusters, order by occurence - std::vector> getClusters() const; - std::vector>> getClusterGroups(const std::vector>& clusterTypes, std::size_t size) const; + // Ordered from most clusters in common + std::vector> getSimilarTracks(std::optional offset = {}, std::optional size = {}) const; - bool hasTrack(TrackId trackId) const; + template + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _type, "type"); + Wt::Dbo::field(a, _isPublic, "public"); + Wt::Dbo::field(a, _creationDateTime, "creation_date_time"); + Wt::Dbo::field(a, _lastModifiedDateTime, "last_modified_date_time"); - // Ordered from most clusters in common - std::vector> getSimilarTracks(std::optional offset = {}, std::optional size = {}) const; + Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "tracklist"); + } - template - void persist(Action& a) - { - Wt::Dbo::field(a, _name, "name"); - Wt::Dbo::field(a, _type, "type"); - Wt::Dbo::field(a, _isPublic, "public"); - Wt::Dbo::field(a, _creationDateTime, "creation_date_time"); - Wt::Dbo::field(a, _lastModifiedDateTime, "last_modified_date_time"); + private: + friend class Session; + TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr user); + static pointer create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr user); - Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "tracklist"); - } + std::string _name; + TrackListType _type{ TrackListType::Playlist }; + bool _isPublic{ false }; + Wt::WDateTime _creationDateTime; + Wt::WDateTime _lastModifiedDateTime; - private: - friend class Session; - TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr user); - static pointer create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr user); + Wt::Dbo::ptr _user; + Wt::Dbo::collection> _entries; + }; - std::string _name; - TrackListType _type {TrackListType::Playlist}; - bool _isPublic {false}; - Wt::WDateTime _creationDateTime; - Wt::WDateTime _lastModifiedDateTime; + class TrackListEntry final : public Object + { + public: + TrackListEntry() = default; - Wt::Dbo::ptr _user; - Wt::Dbo::collection> _entries; -}; + bool hasOnPostCreated() const override { return true; } + void onPostCreated() override; -class TrackListEntry final : public Object -{ - public: - TrackListEntry() = default; + bool hasOnPreRemove() const override { return true; } + void onPreRemove() override; - bool hasOnPostCreated() const override { return true; } - void onPostCreated() override; + // find utility + static pointer getById(Session& session, TrackListEntryId id); - bool hasOnPreRemove() const override { return true; } - void onPreRemove() override; + // Accessors + ObjectPtr getTrack() const { return _track; } + const Wt::WDateTime& getDateTime() const { return _dateTime; } - // find utility - static pointer getById(Session& session, TrackListEntryId id); + template + void persist(Action& a) + { + Wt::Dbo::field(a, _dateTime, "date_time"); - // Accessors - ObjectPtr getTrack() const { return _track; } - const Wt::WDateTime& getDateTime() const { return _dateTime; } + Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _tracklist, "tracklist", Wt::Dbo::OnDeleteCascade); + } - template - void persist(Action& a) - { - Wt::Dbo::field(a, _dateTime, "date_time"); + private: + friend class Session; + TrackListEntry(ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime); + TrackListEntry(ObjectPtr track, ObjectPtr tracklist); + static pointer create(Session& session, ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime = {}); - Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::belongsTo(a, _tracklist, "tracklist", Wt::Dbo::OnDeleteCascade); - } - - private: - friend class Session; - TrackListEntry(ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime); - TrackListEntry(ObjectPtr track, ObjectPtr tracklist); - static pointer create(Session& session, ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime = {}); - - Wt::WDateTime _dateTime; // optional date time - Wt::Dbo::ptr _track; - Wt::Dbo::ptr _tracklist; -}; + Wt::WDateTime _dateTime; // optional date time + Wt::Dbo::ptr _track; + Wt::Dbo::ptr _tracklist; + }; } // namespace Database diff --git a/src/libs/services/database/include/services/database/Types.hpp b/src/libs/services/database/include/services/database/Types.hpp index 881d3f97..5cc16877 100644 --- a/src/libs/services/database/include/services/database/Types.hpp +++ b/src/libs/services/database/include/services/database/Types.hpp @@ -29,7 +29,7 @@ namespace Database // Caution: do not change enum values if they are set! // Request: - // size = 0 => no size limit! + // size = 0 => means we don't want data // Response (via RangeResults) // size => results size struct Range @@ -37,8 +37,7 @@ namespace Database std::size_t offset{}; std::size_t size{}; - // TODO remove this - operator bool() const { return size != 0; } + bool operator==(const Range& rhs) const { return offset == rhs.offset && size == rhs.size; } }; // Func must return true to continue iterating diff --git a/src/libs/services/database/include/services/database/User.hpp b/src/libs/services/database/include/services/database/User.hpp index f177120f..42938e4f 100644 --- a/src/libs/services/database/include/services/database/User.hpp +++ b/src/libs/services/database/include/services/database/User.hpp @@ -49,11 +49,11 @@ namespace Database { { std::optional scrobblingBackend; std::optional feedbackBackend; - Range range; + std::optional range; FindParameters& setFeedbackBackend(FeedbackBackend _feedbackBackend) { feedbackBackend = _feedbackBackend; return *this; } FindParameters& setScrobblingBackend(ScrobblingBackend _scrobblingBackend) { scrobblingBackend = _scrobblingBackend; return *this; } - FindParameters& setRange(Range _range) { range = _range; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } }; static inline constexpr std::size_t MinNameLength{ 3 }; diff --git a/src/libs/services/database/test/Artist.cpp b/src/libs/services/database/test/Artist.cpp index 6e9020fd..b3127c04 100644 --- a/src/libs/services/database/test/Artist.cpp +++ b/src/libs/services/database/test/Artist.cpp @@ -51,7 +51,7 @@ TEST_F(DatabaseFixture, Artist) ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); - artists = Artist::findOrphanIds(session, Range{}); + artists = Artist::findOrphanIds(session); ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); } @@ -64,6 +64,18 @@ TEST_F(DatabaseFixture, Artist) ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front()->getId(), artist.getId()); } + + { + auto transaction{ session.createSharedTransaction() }; + + bool visited{}; + Artist::find(session, Artist::FindParameters{}, [&](const Artist::pointer& a) + { + visited = true; + EXPECT_EQ(a->getId(), artist.getId()); + }); + EXPECT_TRUE(visited); + } } TEST_F(DatabaseFixture, Artist_singleTrack) @@ -80,7 +92,7 @@ TEST_F(DatabaseFixture, Artist_singleTrack) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); + EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); } { @@ -215,7 +227,7 @@ TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); + EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); } { diff --git a/src/libs/services/database/test/Cluster.cpp b/src/libs/services/database/test/Cluster.cpp index 075fdb9a..aeea9be6 100644 --- a/src/libs/services/database/test/Cluster.cpp +++ b/src/libs/services/database/test/Cluster.cpp @@ -55,20 +55,20 @@ TEST_F(DatabaseFixture, Cluster) } { - const auto clusters{ Cluster::findOrphans(session, Range{}) }; + const auto clusters{ Cluster::findOrphans(session) }; ASSERT_EQ(clusters.results.size(), 1); EXPECT_EQ(clusters.results.front(), cluster.getId()); } - auto clusterTypes{ ClusterType::find(session, Range {}) }; + auto clusterTypes{ ClusterType::find(session) }; ASSERT_EQ(clusterTypes.results.size(), 1); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); - clusterTypes = ClusterType::findUsed(session, Range{}); + clusterTypes = ClusterType::findUsed(session); ASSERT_EQ(clusterTypes.results.size(), 1); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); - clusterTypes = ClusterType::findOrphans(session, Range{}); + clusterTypes = ClusterType::findOrphans(session); EXPECT_TRUE(clusterTypes.results.empty()); } } @@ -76,11 +76,11 @@ TEST_F(DatabaseFixture, Cluster) { auto transaction{ session.createUniqueTransaction() }; - auto clusterTypes{ ClusterType::findOrphans(session, Range {}) }; + auto clusterTypes{ ClusterType::findOrphans(session) }; ASSERT_EQ(clusterTypes.results.size(), 1); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); - ASSERT_TRUE(ClusterType::findUsed(session, Range{}).results.empty()); + ASSERT_TRUE(ClusterType::findUsed(session).results.empty()); } } @@ -91,8 +91,8 @@ TEST_F(DatabaseFixture, Cluster_singleTrack) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); - auto clusterTypes{ ClusterType::findOrphans(session, Range {}) }; + EXPECT_TRUE(Cluster::findOrphans(session).results.empty()); + auto clusterTypes{ ClusterType::findOrphans(session) }; ASSERT_EQ(clusterTypes.results.size(), 1); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); } @@ -102,7 +102,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrack) { auto transaction{ session.createSharedTransaction() }; - auto clusters{ Cluster::findOrphans(session, Range {}) }; + auto clusters{ Cluster::findOrphans(session) }; EXPECT_EQ(clusters.results.size(), 2); EXPECT_TRUE(track->getClusters().empty()); EXPECT_TRUE(track->getClusterIds().empty()); @@ -127,11 +127,11 @@ TEST_F(DatabaseFixture, Cluster_singleTrack) { auto transaction{ session.createSharedTransaction() }; - auto clusters{ Cluster::findOrphans(session, Range {}) }; + auto clusters{ Cluster::findOrphans(session) }; ASSERT_EQ(clusters.results.size(), 1); EXPECT_EQ(clusters.results.front(), cluster2.getId()); - EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty()); + EXPECT_TRUE(ClusterType::findOrphans(session).results.empty()); } { @@ -225,11 +225,11 @@ TEST_F(DatabaseFixture, Cluster_multiTracks) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); + EXPECT_TRUE(Cluster::findOrphans(session).results.empty()); EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), tracks.size()); - for (TrackId trackId : cluster->getTracks(Range{}).results) + for (TrackId trackId : cluster->getTracks().results) { auto it{ std::find_if(std::cbegin(tracks), std::cend(tracks), [&](const ScopedTrack& track) { return trackId == track.getId(); }) }; EXPECT_TRUE(it != std::cend(tracks)); @@ -237,102 +237,6 @@ TEST_F(DatabaseFixture, Cluster_multiTracks) } } -TEST_F(DatabaseFixture, Cluster_multiTracksMultipleClustersTopRelease) -{ - ScopedClusterType clusterType{ session, "ClusterType" }; - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "Cluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "Cluster2" }; - ScopedCluster cluster3{ session, clusterType.lockAndGet(), "Cluster3" }; - ScopedTrack trackA{ session, "TrackA" }; - ScopedTrack trackB{ session, "TrackB" }; - ScopedTrack trackC{ session, "TrackC" }; - ScopedRelease releaseA{ session, "ReleaseA" }; - ScopedRelease releaseB{ session, "ReleaseB" }; - ScopedRelease releaseC{ session, "ReleaseC" }; - - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList{ session, "TrackList", TrackListType::Playlist, false, user.lockAndGet() }; - - { - auto transaction{ session.createSharedTransaction() }; - - EXPECT_EQ(trackList->getDuration(), std::chrono::seconds{ 0 }); - } - - { - auto transaction{ session.createUniqueTransaction() }; - - cluster1.get().modify()->addTrack(trackA.get()); - cluster2.get().modify()->addTrack(trackB.get()); - cluster2.get().modify()->addTrack(trackC.get()); - cluster3.get().modify()->addTrack(trackC.get()); - - trackA.get().modify()->setRelease(releaseA.get()); - trackB.get().modify()->setRelease(releaseB.get()); - trackC.get().modify()->setRelease(releaseC.get()); - } - - { - auto transaction{ session.createUniqueTransaction() }; - - session.create(trackA.get(), trackList.get()); - session.create(trackB.get(), trackList.get()); - session.create(trackB.get(), trackList.get()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool hasMore; - const auto releases{ trackList->getTopReleases({}, std::nullopt, hasMore) }; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), releaseB.getId()); - EXPECT_EQ(releases[1]->getId(), releaseA.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool hasMore; - auto releases{ trackList->getTopReleases({cluster1.getId()}, std::nullopt, hasMore) }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases[0]->getId(), releaseA.getId()); - - releases = trackList->getTopReleases({ cluster2.getId() }, std::nullopt, hasMore); - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases[0]->getId(), releaseB.getId()); - - releases = trackList->getTopReleases({ cluster2.getId(), cluster1.getId() }, std::nullopt, hasMore); - EXPECT_TRUE(releases.empty()); - - releases = trackList->getTopReleases({ cluster2.getId(), cluster3.getId() }, std::nullopt, hasMore); - EXPECT_TRUE(releases.empty()); - } - - - { - auto transaction{ session.createUniqueTransaction() }; - - session.create(trackC.get(), trackList.get()); - session.create(trackC.get(), trackList.get()); - session.create(trackC.get(), trackList.get()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool hasMore; - auto releases{ trackList->getTopReleases({cluster2.getId(), cluster3.getId()}, std::nullopt, hasMore) }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases[0]->getId(), releaseC.getId()); - - releases = trackList->getTopReleases({ cluster2.getId() }, std::nullopt, hasMore); - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), releaseC.getId()); - EXPECT_EQ(releases[1]->getId(), releaseB.getId()); - } -} - TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster) { ScopedTrack track{ session, "MyTrackFile" }; @@ -340,7 +244,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); + EXPECT_TRUE(Cluster::findOrphans(session).results.empty()); } ScopedClusterType clusterType{ session, "MyClusterType" }; @@ -349,7 +253,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster) { auto transaction{ session.createSharedTransaction() }; - ASSERT_EQ(Cluster::findOrphans(session, Range{}).results.size(), 2); + ASSERT_EQ(Cluster::findOrphans(session).results.size(), 2); EXPECT_TRUE(Release::find(session, Release::FindParameters{}.setClusters({ unusedCluster.getId() })).results.empty()); EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1); EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0); @@ -367,7 +271,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster) auto transaction{ session.createSharedTransaction() }; { - auto clusters{ Cluster::findOrphans(session, Range {}) }; + auto clusters{ Cluster::findOrphans(session) }; ASSERT_EQ(clusters.results.size(), 1); EXPECT_EQ(clusters.results.front(), unusedCluster.getId()); } @@ -425,10 +329,10 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty()); - EXPECT_EQ(Cluster::findOrphans(session, Range{}).results.size(), 2); - EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); + EXPECT_TRUE(ClusterType::findOrphans(session).results.empty()); + EXPECT_EQ(Cluster::findOrphans(session).results.size(), 2); + EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); + EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); } { @@ -486,9 +390,9 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); - EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); + EXPECT_TRUE(Cluster::findOrphans(session).results.empty()); + EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); + EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); } { @@ -526,8 +430,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); + EXPECT_TRUE(Cluster::findOrphans(session).results.empty()); + EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); } { @@ -562,7 +466,7 @@ TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity) { auto transaction{ session.createSharedTransaction() }; - const auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}, Range {}) }; + const auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) }; EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1); for (const TrackId similarTrackId : similarTracks.results) { @@ -610,7 +514,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleClustersSimilarity) } { - auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}, Range {}) }; + auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) }; EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1); for (const TrackId similarTrackId : similarTracks.results) EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks)); @@ -637,10 +541,10 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); - EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); - EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty()); + EXPECT_TRUE(Cluster::findOrphans(session).results.empty()); + EXPECT_TRUE(ClusterType::findOrphans(session).results.empty()); + EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); + EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); } { @@ -775,259 +679,6 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters) } } -TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClustersRecentlyPlayed) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; - ScopedCluster cluster3{ session, clusterType.lockAndGet(), "MyCluster3" }; - ScopedTrack track1{ session, "MyTrack1" }; - ScopedTrack track2{ session, "MyTrack1" }; - ScopedArtist artist1{ session, "MyArtist1" }; - ScopedArtist artist2{ session, "MyArtist2" }; - ScopedRelease release1{ session, "MyRelease1" }; - ScopedRelease release2{ session, "MyRelease2" }; - - const Wt::WDateTime now{ Wt::WDateTime::currentDateTime() }; - - { - auto transaction{ session.createUniqueTransaction() }; - - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); - TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); - - cluster1.get().modify()->addTrack(track1.get()); - cluster2.get().modify()->addTrack(track2.get()); - cluster3.get().modify()->addTrack(track1.get()); - cluster3.get().modify()->addTrack(track2.get()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - EXPECT_TRUE(trackList->getArtistsOrderedByRecentFirst({}, std::nullopt, std::nullopt, moreResults).empty()); - EXPECT_TRUE(trackList->getReleasesOrderedByRecentFirst({}, std::nullopt, moreResults).empty()); - EXPECT_TRUE(trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults).empty()); - } - - { - auto transaction{ session.createUniqueTransaction() }; - - session.create(track1.get(), trackList.get(), now); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release1.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 1); - EXPECT_EQ(tracks.front()->getId(), track1.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({cluster1.getId()}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({cluster1.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release1.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({cluster1.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 1); - EXPECT_EQ(tracks.front()->getId(), track1.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({cluster3.getId()}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({cluster3.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release1.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({cluster3.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 1); - EXPECT_EQ(tracks.front()->getId(), track1.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({cluster1.getId()}, TrackArtistLinkType::Artist, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({}, TrackArtistLinkType::Artist, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - EXPECT_TRUE(trackList->getArtistsOrderedByRecentFirst({ cluster2.getId() }, std::nullopt, std::nullopt, moreResults).empty()); - EXPECT_TRUE(trackList->getReleasesOrderedByRecentFirst({ cluster2.getId() }, std::nullopt, moreResults).empty()); - EXPECT_TRUE(trackList->getTracksOrderedByRecentFirst({ cluster2.getId() }, std::nullopt, moreResults).empty()); - - EXPECT_TRUE(trackList->getArtistsOrderedByRecentFirst({}, TrackArtistLinkType::ReleaseArtist, std::nullopt, moreResults).empty()); - } - - { - auto transaction{ session.createUniqueTransaction() }; - - session.create(track2.get(), trackList.get(), now.addSecs(1)); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 2); - EXPECT_EQ(artists[0]->getId(), artist2.getId()); - EXPECT_EQ(artists[1]->getId(), artist1.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release2.getId()); - EXPECT_EQ(releases[1]->getId(), release1.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 2); - EXPECT_EQ(tracks[0]->getId(), track2.getId()); - EXPECT_EQ(tracks[1]->getId(), track1.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({cluster3.getId()}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 2); - EXPECT_EQ(artists[0]->getId(), artist2.getId()); - EXPECT_EQ(artists[1]->getId(), artist1.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({cluster3.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release2.getId()); - EXPECT_EQ(releases[1]->getId(), release1.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({cluster3.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 2); - EXPECT_EQ(tracks[0]->getId(), track2.getId()); - EXPECT_EQ(tracks[1]->getId(), track1.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({cluster1.getId()}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({cluster1.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release1.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({cluster1.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 1); - EXPECT_EQ(tracks.front()->getId(), track1.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({cluster2.getId()}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist2.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({cluster2.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release2.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({cluster2.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 1); - EXPECT_EQ(tracks.front()->getId(), track2.getId()); - } - - { - auto transaction{ session.createUniqueTransaction() }; - - session.create(track1.get(), trackList.get(), now.addSecs(2)); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 2); - EXPECT_EQ(artists[0]->getId(), artist1.getId()); - EXPECT_EQ(artists[1]->getId(), artist2.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release1.getId()); - EXPECT_EQ(releases[1]->getId(), release2.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 2); - EXPECT_EQ(tracks[0]->getId(), track1.getId()); - EXPECT_EQ(tracks[1]->getId(), track2.getId()); - } - - { - auto transaction{ session.createSharedTransaction() }; - - bool moreResults{}; - const auto artists{ trackList->getArtistsOrderedByRecentFirst({cluster3.getId()}, std::nullopt, std::nullopt, moreResults) }; - ASSERT_EQ(artists.size(), 2); - EXPECT_EQ(artists[0]->getId(), artist1.getId()); - EXPECT_EQ(artists[1]->getId(), artist2.getId()); - - const auto releases{ trackList->getReleasesOrderedByRecentFirst({cluster3.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release1.getId()); - EXPECT_EQ(releases[1]->getId(), release2.getId()); - - const auto tracks{ trackList->getTracksOrderedByRecentFirst({cluster3.getId()}, std::nullopt, moreResults) }; - ASSERT_EQ(tracks.size(), 2); - EXPECT_EQ(tracks[0]->getId(), track1.getId()); - EXPECT_EQ(tracks[1]->getId(), track2.getId()); - } -} - TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters) { ScopedArtist artist1{ session, "MyArtist1" }; diff --git a/src/libs/services/database/test/Common.cpp b/src/libs/services/database/test/Common.cpp index 68c49382..d63ce4e4 100644 --- a/src/libs/services/database/test/Common.cpp +++ b/src/libs/services/database/test/Common.cpp @@ -42,8 +42,7 @@ TmpDatabase::TmpDatabase() { } -Database::Db& -TmpDatabase::getDb() +Database::Db& TmpDatabase::getDb() { return _db; } @@ -53,8 +52,7 @@ DatabaseFixture::~DatabaseFixture() testDatabaseEmpty(); } -void -DatabaseFixture::SetUpTestCase() +void DatabaseFixture::SetUpTestCase() { _tmpDb = std::make_unique(); { @@ -65,8 +63,8 @@ DatabaseFixture::SetUpTestCase() // remove default created entries { auto transaction{ s.createUniqueTransaction() }; - auto clusterTypes{ Database::ClusterType::find(s, Database::Range {}) }; - for (auto clusterTypeId : clusterTypes.results) + + for (const Database::ClusterTypeId clusterTypeId : Database::ClusterType::find(s).results) { auto clusterType{ Database::ClusterType::find(s, clusterTypeId) }; clusterType.remove(); @@ -75,14 +73,12 @@ DatabaseFixture::SetUpTestCase() } } -void -DatabaseFixture::TearDownTestCase() +void DatabaseFixture::TearDownTestCase() { _tmpDb.reset(); } -void -DatabaseFixture::testDatabaseEmpty() +void DatabaseFixture::testDatabaseEmpty() { using namespace Database; @@ -153,10 +149,10 @@ TEST_F(DatabaseFixture, Common_subRangeForeach) { std::vector subRanges; foreachSubRange(test.range, test.subRangeSize, [&](Range subRange) - { - subRanges.push_back(subRange); - return true; - }); + { + subRanges.push_back(subRange); + return true; + }); EXPECT_EQ(subRanges, test.expectedSubRanges) << ", test index = " << std::distance(std::cbegin(testCases), &test); } diff --git a/src/libs/services/database/test/DatabaseTest.cpp b/src/libs/services/database/test/DatabaseTest.cpp index 2b35929f..bb1926dd 100644 --- a/src/libs/services/database/test/DatabaseTest.cpp +++ b/src/libs/services/database/test/DatabaseTest.cpp @@ -42,8 +42,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); + EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); + EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); } { diff --git a/src/libs/services/database/test/Release.cpp b/src/libs/services/database/test/Release.cpp index cb3a35b2..a07a6e62 100644 --- a/src/libs/services/database/test/Release.cpp +++ b/src/libs/services/database/test/Release.cpp @@ -40,7 +40,7 @@ TEST_F(DatabaseFixture, Release) EXPECT_TRUE(Release::exists(session, release.getId())); { - const auto releases{ Release::findOrphanIds(session, Range {}) }; + const auto releases{ Release::findOrphanIds(session) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); } @@ -57,6 +57,16 @@ TEST_F(DatabaseFixture, Release) ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front()->getId(), release.getId()); } + + { + bool visited{}; + Release::find(session, Release::FindParameters{}, [&](const Release::pointer& r) + { + visited = true; + EXPECT_EQ(r->getId(), release.getId()); + }); + EXPECT_TRUE(visited); + } } } @@ -76,7 +86,7 @@ TEST_F(DatabaseFixture, Release_singleTrack) { auto transaction{ session.createSharedTransaction() }; - EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty()); + EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; ASSERT_EQ(tracks.results.size(), 1); @@ -114,7 +124,7 @@ TEST_F(DatabaseFixture, Release_singleTrack) const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; EXPECT_TRUE(tracks.results.empty()); - auto releases{ Release::findOrphanIds(session, Range {}) }; + auto releases{ Release::findOrphanIds(session) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); } diff --git a/src/libs/services/database/test/Track.cpp b/src/libs/services/database/test/Track.cpp index 0711edb6..c88e9b69 100644 --- a/src/libs/services/database/test/Track.cpp +++ b/src/libs/services/database/test/Track.cpp @@ -31,6 +31,12 @@ TEST_F(DatabaseFixture, Track) EXPECT_EQ(Track::findIds(session, Track::FindParameters{}).results.size(), 0); EXPECT_EQ(Track::getCount(session), 0); EXPECT_FALSE(Track::exists(session, 0)); + + { + bool visited{}; + Track::find(session, Track::FindParameters{}, [&](const Track::pointer&) {visited = true;}); + EXPECT_FALSE(visited); + } } ScopedTrack track{ session, "MyTrackFile" }; @@ -44,6 +50,16 @@ TEST_F(DatabaseFixture, Track) auto myTrack{ Track::find(session, track.getId()) }; ASSERT_TRUE(myTrack); EXPECT_EQ(myTrack->getId(), track.getId()); + + { + bool visited{}; + Track::find(session, Track::FindParameters{}, [&](const Track::pointer& t) + { + visited = true; + EXPECT_EQ(t->getId(), track.getId()); + }); + EXPECT_TRUE(visited); + } } } diff --git a/src/libs/services/database/test/TrackBookmark.cpp b/src/libs/services/database/test/TrackBookmark.cpp index d99b0bd5..d8720beb 100644 --- a/src/libs/services/database/test/TrackBookmark.cpp +++ b/src/libs/services/database/test/TrackBookmark.cpp @@ -49,7 +49,7 @@ TEST_F(DatabaseFixture, TrackBookmark) EXPECT_EQ(TrackBookmark::getCount(session), 1); - const auto bookmarks {TrackBookmark::find(session, user.getId(), Range {})}; + const auto bookmarks {TrackBookmark::find(session, user.getId())}; ASSERT_EQ(bookmarks.results.size(), 1); EXPECT_EQ(bookmarks.results.front(), bookmark.getId()); } diff --git a/src/libs/services/database/test/TrackFeatures.cpp b/src/libs/services/database/test/TrackFeatures.cpp index 5d919a40..0f824124 100644 --- a/src/libs/services/database/test/TrackFeatures.cpp +++ b/src/libs/services/database/test/TrackFeatures.cpp @@ -41,7 +41,7 @@ TEST_F(DatabaseFixture, TrackFeatures) auto transaction {session.createUniqueTransaction()}; EXPECT_EQ(TrackFeatures::getCount(session), 1); - auto allTrackFeatures {TrackFeatures::find(session, Range {})}; + auto allTrackFeatures {TrackFeatures::find(session)}; ASSERT_EQ(allTrackFeatures.results.size(), 1); EXPECT_EQ(allTrackFeatures.results.front(), trackFeatures.getId()); } diff --git a/src/libs/services/database/test/TrackList.cpp b/src/libs/services/database/test/TrackList.cpp index f05cb48a..70467128 100644 --- a/src/libs/services/database/test/TrackList.cpp +++ b/src/libs/services/database/test/TrackList.cpp @@ -25,497 +25,200 @@ using namespace Database; TEST_F(DatabaseFixture, SingleTrackList) { - ScopedUser user {session, "MyUser"}; - { - auto transaction {session.createSharedTransaction()}; - EXPECT_EQ(TrackList::getCount(session), 0); - } + ScopedUser user{ session, "MyUser" }; + { + auto transaction{ session.createSharedTransaction() }; + EXPECT_EQ(TrackList::getCount(session), 0); + } - ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()}; + ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() }; - { - auto transaction {session.createSharedTransaction()}; - EXPECT_EQ(TrackList::getCount(session), 1); - } + { + auto transaction{ session.createSharedTransaction() }; + EXPECT_EQ(TrackList::getCount(session), 1); + } } TEST_F(DatabaseFixture, SingleTrackListSingleTrack) { - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrack track {session, "MyTrack"}; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrack track{ session, "MyTrack" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId()))}; - EXPECT_EQ(tracks.results.size(), 0); + auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) }; + EXPECT_EQ(tracks.results.size(), 0); - tracks = Track::findIds(session, Track::FindParameters {}.setTrackList(trackList2.getId())); - EXPECT_EQ(tracks.results.size(), 0); - } + tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId())); + EXPECT_EQ(tracks.results.size(), 0); + } - { - auto transaction {session.createUniqueTransaction()}; + { + auto transaction{ session.createUniqueTransaction() }; - session.create(track.get(), trackList1.get()); - } + session.create(track.get(), trackList1.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto tracks {Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId()))}; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track.getId()); + auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track.getId()); - tracks = Track::findIds(session, Track::FindParameters {}.setTrackList(trackList2.getId())); - EXPECT_EQ(tracks.results.size(), 0); - } + tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId())); + EXPECT_EQ(tracks.results.size(), 0); + } } TEST_F(DatabaseFixture, TrackList_SortMethod) { - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrack track {session, "MyTrack"}; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrack track{ session, "MyTrack" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - const auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::Name))}; - ASSERT_EQ(trackLists.results.size(), 2); - EXPECT_EQ(trackLists.results[0], trackList1.getId()); - EXPECT_EQ(trackLists.results[1], trackList2.getId()); - } + const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::Name)) }; + ASSERT_EQ(trackLists.results.size(), 2); + EXPECT_EQ(trackLists.results[0], trackList1.getId()); + EXPECT_EQ(trackLists.results[1], trackList2.getId()); + } - { - auto transaction {session.createUniqueTransaction()}; + { + auto transaction{ session.createUniqueTransaction() }; - trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime {Wt::WDate {1900,1,1}}); - trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime {Wt::WDate {1900,1,2}}); - } + trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} }); + trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} }); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - const auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc))}; - ASSERT_EQ(trackLists.results.size(), 2); - EXPECT_EQ(trackLists.results[0], trackList2.getId()); - EXPECT_EQ(trackLists.results[1], trackList1.getId()); - } + const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; + ASSERT_EQ(trackLists.results.size(), 2); + EXPECT_EQ(trackLists.results[0], trackList2.getId()); + EXPECT_EQ(trackLists.results[1], trackList1.getId()); + } - { - auto transaction {session.createUniqueTransaction()}; + { + auto transaction{ session.createUniqueTransaction() }; - trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime {Wt::WDate {1900,1,2}}); - trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime {Wt::WDate {1900,1,1}}); - } + trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} }); + trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} }); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - const auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc))}; - ASSERT_EQ(trackLists.results.size(), 2); - EXPECT_EQ(trackLists.results[0], trackList1.getId()); - EXPECT_EQ(trackLists.results[1], trackList2.getId()); - } + const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; + ASSERT_EQ(trackLists.results.size(), 2); + EXPECT_EQ(trackLists.results[0], trackList1.getId()); + EXPECT_EQ(trackLists.results[1], trackList2.getId()); + } } TEST_F(DatabaseFixture, SingleTrackListMultipleTrack) { - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()}; - std::list tracks; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() }; + std::list tracks; - for (std::size_t i {}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + for (std::size_t i{}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - auto transaction {session.createUniqueTransaction()}; - session.create(tracks.back().get(), trackList.get()); - } + auto transaction{ session.createUniqueTransaction() }; + session.create(tracks.back().get(), trackList.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - ASSERT_EQ(trackList->getCount(), tracks.size()); - const auto trackIds {trackList->getTrackIds()}; - ASSERT_EQ(trackIds.size(), tracks.size()); + ASSERT_EQ(trackList->getCount(), tracks.size()); + const auto trackIds{ trackList->getTrackIds() }; + ASSERT_EQ(trackIds.size(), tracks.size()); - // Same order - std::size_t i {}; - for (const ScopedTrack& track : tracks) - EXPECT_EQ(track.getId(), trackIds[i++]); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto reverseTracks {trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults)}; - ASSERT_EQ(reverseTracks.size(), tracks.size()); - - // Reverse order - ASSERT_TRUE(tracks.size() > 0); - std::size_t i {tracks.size() - 1}; - for (const ScopedTrack& track : tracks) - EXPECT_EQ(track.getId(), reverseTracks[i--]->getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackListMultipleTrackDateTime) -{ - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrack track1 {session, "MyTrack1"}; - ScopedTrack track2 {session, "MyTrack2"}; - ScopedTrack track3 {session, "MyTrack3"}; - - { - Wt::WDateTime now {Wt::WDateTime::currentDateTime()}; - auto transaction {session.createUniqueTransaction()}; - session.create(track1.get(), trackList.get(), now); - session.create(track2.get(), trackList.get(), now.addSecs(-1)); - session.create(track3.get(), trackList.get(), now.addSecs(1)); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults; - const auto tracks {trackList.get()->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults)}; - ASSERT_EQ(tracks.size(), 3); - EXPECT_EQ(tracks.front()->getId(), track3.getId()); - EXPECT_EQ(tracks.back()->getId(), track2.getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackListMultipleTrackRecentlyPlayed) -{ - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrack track1 {session, "MyTrack1"}; - ScopedTrack track2 {session, "MyTrack1"}; - ScopedArtist artist1 {session, "MyArtist1"}; - ScopedArtist artist2 {session, "MyArtist2"}; - ScopedRelease release1 {session, "MyRelease1"}; - ScopedRelease release2 {session, "MyRelease2"}; - - const Wt::WDateTime now {Wt::WDateTime::currentDateTime()}; - - { - auto transaction {session.createUniqueTransaction()}; - - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); - TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); - } - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - EXPECT_TRUE(trackList->getArtistsOrderedByRecentFirst({}, std::nullopt, std::nullopt, moreResults).empty()); - EXPECT_TRUE(trackList->getReleasesOrderedByRecentFirst({}, std::nullopt, moreResults).empty()); - EXPECT_TRUE(trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults).empty()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - session.create(track1.get(), trackList.get(), now); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsOrderedByRecentFirst({}, std::nullopt, std::nullopt, moreResults)}; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - - const auto releases {trackList->getReleasesOrderedByRecentFirst({}, std::nullopt, moreResults)}; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release1.getId()); - - const auto tracks {trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults)}; - EXPECT_EQ(tracks.size(), 1); - } - - { - auto transaction {session.createUniqueTransaction()}; - - session.create(track2.get(), trackList.get(), now.addSecs(1)); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsOrderedByRecentFirst({}, std::nullopt, std::nullopt, moreResults)}; - ASSERT_EQ(artists.size(), 2); - EXPECT_EQ(artists[0]->getId(), artist2.getId()); - EXPECT_EQ(artists[1]->getId(), artist1.getId()); - - const auto releases {trackList->getReleasesOrderedByRecentFirst({}, std::nullopt, moreResults)}; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release2.getId()); - EXPECT_EQ(releases[1]->getId(), release1.getId()); - - const auto tracks {trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults)}; - ASSERT_EQ(tracks.size(), 2); - EXPECT_EQ(tracks[0]->getId(), track2.getId()); - EXPECT_EQ(tracks[1]->getId(), track1.getId()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - session.create(track1.get(), trackList.get(), now.addSecs(2)); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsOrderedByRecentFirst({}, std::nullopt, std::nullopt, moreResults)}; - ASSERT_EQ(artists.size(), 2); - EXPECT_EQ(artists[0]->getId(), artist1.getId()); - EXPECT_EQ(artists[1]->getId(), artist2.getId()); - - const auto releases {trackList->getReleasesOrderedByRecentFirst({}, std::nullopt, moreResults)}; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release1.getId()); - EXPECT_EQ(releases[1]->getId(), release2.getId()); - - const auto tracks {trackList->getTracksOrderedByRecentFirst({}, std::nullopt, moreResults)}; - ASSERT_EQ(tracks.size(), 2); - EXPECT_EQ(tracks[0]->getId(), track1.getId()); - EXPECT_EQ(tracks[1]->getId(), track2.getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackList_getArtists) -{ - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrack track1 {session, "MyTrack1"}; - ScopedTrack track2 {session, "MyTrack2"}; - ScopedRelease release {session, "MyRelease"}; - ScopedArtist artist1 {session, "MyArtist1"}; - ScopedArtist artist2 {session, "MyArtist2"}; - - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_EQ(trackList->getCount(), 0); - bool moreResults {}; - const auto artists {trackList->getArtists({} /*clusters*/, std::nullopt /* linkType */, ArtistSortMethod::ByName, std::nullopt /* range */, moreResults)}; - ASSERT_TRUE(artists.empty()); - } - - { - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); - session.create(track1.get(), trackList.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_EQ(trackList->getCount(), 1); - bool moreResults {}; - const auto artists {trackList->getArtists({} /*clusters*/, std::nullopt /* linkType */, ArtistSortMethod::ByName, std::nullopt /* range */, moreResults)}; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtists({} /*clusters*/, TrackArtistLinkType::ReleaseArtist, ArtistSortMethod::ByName, std::nullopt /* range */, moreResults)}; - EXPECT_TRUE(artists.empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtists({} /*clusters*/, TrackArtistLinkType::Artist, ArtistSortMethod::ByName, std::nullopt /* range */, moreResults)}; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - } - - { - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); - session.create(track2.get(), trackList.get()); - artist1.get().modify()->setSortName("ZZZ"); - artist2.get().modify()->setSortName("AAA"); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtists({} /*clusters*/, TrackArtistLinkType::Artist, ArtistSortMethod::ByName, std::nullopt /* range */, moreResults)}; - ASSERT_EQ(artists.size(), 2); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - EXPECT_EQ(artists.back()->getId(), artist2.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtists({} /*clusters*/, TrackArtistLinkType::Artist, ArtistSortMethod::BySortName, std::nullopt /* range */, moreResults)}; - ASSERT_EQ(artists.size(), 2); - EXPECT_EQ(artists.front()->getId(), artist2.getId()); - EXPECT_EQ(artists.back()->getId(), artist1.getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackList_getReleases) -{ - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrack track {session, "MyTrack"}; - ScopedRelease release {session, "MyRelease"}; - - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_EQ(trackList->getCount(), 0); - bool moreResults {}; - const auto releases {trackList->getReleases({} /*clusters*/, std::nullopt /* range */, moreResults)}; - ASSERT_TRUE(releases.empty()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - track.get().modify()->setRelease(release.get()); - - session.create(track.get(), trackList.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - ASSERT_EQ(trackList->getCount(), 1); - bool moreResults {}; - const auto releases {trackList->getReleases({} /*clusters*/, std::nullopt /* range */, moreResults)}; - ASSERT_EQ(releases.size(), 1); - ASSERT_EQ(releases.front()->getId(), release->getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackList_getTracks) -{ - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrack track1 {session, "MyTrack1"}; - ScopedTrack track2 {session, "MyTrack1"}; - - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_EQ(trackList->getCount(), 0); - bool moreResults {}; - const auto tracks {trackList->getTracks({} /*clusters*/, std::nullopt /* range */, moreResults)}; - ASSERT_TRUE(tracks.empty()); - } - - { - auto transaction {session.createUniqueTransaction()}; - session.create(track1.get(), trackList.get()); - session.create(track2.get(), trackList.get()); - session.create(track1.get(), trackList.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - ASSERT_EQ(trackList->getCount(), 3); - bool moreResults {}; - const auto tracks {trackList->getTracks({} /*clusters*/, std::nullopt /* range */, moreResults)}; - ASSERT_EQ(tracks.size(), 2); - ASSERT_EQ(tracks[0]->getId(), track1->getId()); - ASSERT_EQ(tracks[1]->getId(), track2->getId()); - } + // Same order + std::size_t i{}; + for (const ScopedTrack& track : tracks) + EXPECT_EQ(track.getId(), trackIds[i++]); + } } TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster) { - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - ScopedTrack track {session, "MyTrack"}; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + ScopedTrack track{ session, "MyTrack" }; - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()}))}; - EXPECT_EQ(trackLists.results.size(), 0); - } + auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) }; + EXPECT_EQ(trackLists.results.size(), 0); + } - { - auto transaction {session.createUniqueTransaction()}; + { + auto transaction{ session.createUniqueTransaction() }; - session.create(track.get(), trackList1.get()); - cluster.get().modify()->addTrack(track.get()); - } + session.create(track.get(), trackList1.get()); + cluster.get().modify()->addTrack(track.get()); + } - { - auto transaction {session.createSharedTransaction()}; + { + auto transaction{ session.createSharedTransaction() }; - auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()}))}; - ASSERT_EQ(trackLists.results.size(), 1); - EXPECT_EQ(trackLists.results.front(), trackList1.getId()); - } + auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) }; + ASSERT_EQ(trackLists.results.size(), 1); + EXPECT_EQ(trackLists.results.front(), trackList1.getId()); + } } TEST_F(DatabaseFixture, SingleTrackList_getEntries) { - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()}; - ScopedTrack track1 {session, "MyTrack"}; - ScopedTrack track2 {session, "MyTrack"}; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrack track1{ session, "MyTrack" }; + ScopedTrack track2{ session, "MyTrack" }; - { - auto transaction {session.createUniqueTransaction()}; - session.create(track1.get(), trackList.get()); - } + { + auto transaction{ session.createUniqueTransaction() }; + session.create(track1.get(), trackList.get()); + } - { - auto transaction {session.createSharedTransaction()}; - auto entries {trackList.get()->getEntries()}; - ASSERT_EQ(entries.size(), 1); - EXPECT_EQ(entries.front()->getTrack()->getId(), track1.getId()); - } + { + auto transaction{ session.createSharedTransaction() }; + auto entries{ trackList.get()->getEntries() }; + ASSERT_EQ(entries.size(), 1); + EXPECT_EQ(entries.front()->getTrack()->getId(), track1.getId()); + } - { - auto transaction {session.createUniqueTransaction()}; - session.create(track2.get(), trackList.get()); - } + { + auto transaction{ session.createUniqueTransaction() }; + session.create(track2.get(), trackList.get()); + } - { - auto transaction {session.createSharedTransaction()}; - auto entries {trackList.get()->getEntries()}; - ASSERT_EQ(entries.size(), 2); - EXPECT_EQ(entries[0]->getTrack()->getId(), track1.getId()); - EXPECT_EQ(entries[1]->getTrack()->getId(), track2.getId()); - } + { + auto transaction{ session.createSharedTransaction() }; + auto entries{ trackList.get()->getEntries() }; + ASSERT_EQ(entries.size(), 2); + EXPECT_EQ(entries[0]->getTrack()->getId(), track1.getId()); + EXPECT_EQ(entries[1]->getTrack()->getId(), track2.getId()); + } - { - auto transaction {session.createSharedTransaction()}; - auto entries {trackList.get()->getEntries(Range {1, 1})}; - ASSERT_EQ(entries.size(), 1); - EXPECT_EQ(entries[0]->getTrack()->getId(), track2.getId()); - } + { + auto transaction{ session.createSharedTransaction() }; + auto entries{ trackList.get()->getEntries(Range {1, 1}) }; + ASSERT_EQ(entries.size(), 1); + EXPECT_EQ(entries[0]->getTrack()->getId(), track2.getId()); + } } diff --git a/src/libs/services/feedback/impl/FeedbackService.cpp b/src/libs/services/feedback/impl/FeedbackService.cpp index 2a9a60a9..fb66c740 100644 --- a/src/libs/services/feedback/impl/FeedbackService.cpp +++ b/src/libs/services/feedback/impl/FeedbackService.cpp @@ -87,23 +87,23 @@ namespace Feedback return getStarredDateTime(userId, artistId); } - FeedbackService::ArtistContainer FeedbackService::getStarredArtists(UserId userId, const std::vector& clusterIds, std::optional linkType, ArtistSortMethod sortMethod, Range range) + FeedbackService::ArtistContainer FeedbackService::findStarredArtists(const ArtistFindParameters& params) { - auto backend{ getUserFeedbackBackend(userId) }; + auto backend{ getUserFeedbackBackend(params.user) }; if (!backend) return {}; - Artist::FindParameters params; - params.setStarringUser(userId, *backend); - params.setClusters(clusterIds); - params.setLinkType(linkType); - params.setSortMethod(sortMethod); - params.setRange(range); + Artist::FindParameters searchParams; + searchParams.setStarringUser(params.user, *backend); + searchParams.setClusters(params.clusters); + searchParams.setLinkType(params.linkType); + searchParams.setSortMethod(params.sortMethod); + searchParams.setRange(params.range); Session& session{ _db.getTLSSession() }; auto transaction{ session.createSharedTransaction() }; - return Artist::findIds(session, params); + return Artist::findIds(session, searchParams); } void FeedbackService::star(UserId userId, ReleaseId releaseId) @@ -126,22 +126,22 @@ namespace Feedback return getStarredDateTime(userId, releaseId); } - FeedbackService::ReleaseContainer FeedbackService::getStarredReleases(UserId userId, const std::vector& clusterIds, Range range) + FeedbackService::ReleaseContainer FeedbackService::findStarredReleases(const FindParameters& params) { - auto backend{ getUserFeedbackBackend(userId) }; + auto backend{ getUserFeedbackBackend(params.user) }; if (!backend) return {}; - Release::FindParameters params; - params.setStarringUser(userId, *backend); - params.setClusters(clusterIds); - params.setSortMethod(ReleaseSortMethod::StarredDateDesc); - params.setRange(range); + Release::FindParameters searchParams; + searchParams.setStarringUser(params.user, *backend); + searchParams.setClusters(params.clusters); + searchParams.setSortMethod(ReleaseSortMethod::StarredDateDesc); + searchParams.setRange(params.range); Session& session{ _db.getTLSSession() }; auto transaction{ session.createSharedTransaction() }; - return Release::findIds(session, params); + return Release::findIds(session, searchParams); } void FeedbackService::star(UserId userId, TrackId trackId) @@ -164,22 +164,22 @@ namespace Feedback return getStarredDateTime(userId, trackId); } - FeedbackService::TrackContainer FeedbackService::getStarredTracks(UserId userId, const std::vector& clusterIds, Range range) + FeedbackService::TrackContainer FeedbackService::findStarredTracks(const FindParameters& params) { - auto backend{ getUserFeedbackBackend(userId) }; + auto backend{ getUserFeedbackBackend(params.user) }; if (!backend) return {}; - Track::FindParameters params; - params.setStarringUser(userId, *backend); - params.setClusters(clusterIds); - params.setSortMethod(TrackSortMethod::StarredDateDesc); - params.setRange(range); + Track::FindParameters searchParams; + searchParams.setStarringUser(params.user, *backend); + searchParams.setClusters(params.clusters); + searchParams.setSortMethod(TrackSortMethod::StarredDateDesc); + searchParams.setRange(params.range); Session& session{ _db.getTLSSession() }; auto transaction{ session.createSharedTransaction() }; - return Track::findIds(session, params); + return Track::findIds(session, searchParams); } } // ns Feedback diff --git a/src/libs/services/feedback/impl/FeedbackService.hpp b/src/libs/services/feedback/impl/FeedbackService.hpp index 279a8fa7..3b28d469 100644 --- a/src/libs/services/feedback/impl/FeedbackService.hpp +++ b/src/libs/services/feedback/impl/FeedbackService.hpp @@ -46,19 +46,19 @@ namespace Feedback void unstar(Database::UserId userId, Database::ArtistId artistId) override; bool isStarred(Database::UserId userId, Database::ArtistId artistId) override; Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) override; - ArtistContainer getStarredArtists(Database::UserId userId, const std::vector& clusterIds, std::optional linkType, Database::ArtistSortMethod sortMethod, Database::Range range) override; + ArtistContainer findStarredArtists(const ArtistFindParameters& params) override; void star(Database::UserId userId, Database::ReleaseId releaseId) override; void unstar(Database::UserId userId, Database::ReleaseId releaseId) override; bool isStarred(Database::UserId userId, Database::ReleaseId releasedId) override; Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ReleaseId releasedId) override; - ReleaseContainer getStarredReleases(Database::UserId userId, const std::vector& clusterIds, Database::Range range) override; + ReleaseContainer findStarredReleases(const FindParameters& params) override; void star(Database::UserId userId, Database::TrackId trackId) override; void unstar(Database::UserId userId, Database::TrackId trackId) override; bool isStarred(Database::UserId userId, Database::TrackId trackId) override; Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::TrackId trackId) override; - TrackContainer getStarredTracks(Database::UserId userId, const std::vector& clusterIds, Database::Range range) override; + TrackContainer findStarredTracks(const FindParameters& params) override; std::optional getUserFeedbackBackend(Database::UserId userId); diff --git a/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp b/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp index dbd8085f..05ed487f 100644 --- a/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp +++ b/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp @@ -48,23 +48,46 @@ namespace Feedback using ReleaseContainer = Database::RangeResults; using TrackContainer = Database::RangeResults; - virtual void star(Database::UserId userId, Database::ArtistId artistId) = 0; - virtual void unstar(Database::UserId userId, Database::ArtistId artistId) = 0; - virtual bool isStarred(Database::UserId userId, Database::ArtistId artistId) = 0; - virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) = 0; - virtual ArtistContainer getStarredArtists(Database::UserId userId, const std::vector& clusterIds, std::optional linkType, Database::ArtistSortMethod sortMethod, Database::Range range) = 0; + struct FindParameters + { + Database::UserId user; + std::vector clusters; // if non empty, at least one artist that belongs to these clusters + std::optional range; + FindParameters& setUser(const Database::UserId _user) { user = _user; return *this; } + FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } + }; + + // Artists + struct ArtistFindParameters : public FindParameters + { + std::optional linkType; // if set, only artists that have produced at least one track with this link type + Database::ArtistSortMethod sortMethod{ Database::ArtistSortMethod::None }; + + ArtistFindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } + ArtistFindParameters& setSortMethod(Database::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } + }; + + virtual void star(Database::UserId userId, Database::ArtistId artistId) = 0; + virtual void unstar(Database::UserId userId, Database::ArtistId artistId) = 0; + virtual bool isStarred(Database::UserId userId, Database::ArtistId artistId) = 0; + virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) = 0; + virtual ArtistContainer findStarredArtists(const ArtistFindParameters& params) = 0; + + // Releases virtual void star(Database::UserId userId, Database::ReleaseId releaseId) = 0; virtual void unstar(Database::UserId userId, Database::ReleaseId releaseId) = 0; virtual bool isStarred(Database::UserId userId, Database::ReleaseId artistId) = 0; virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ReleaseId artistId) = 0; - virtual ReleaseContainer getStarredReleases(Database::UserId userId, const std::vector& clusterIds, Database::Range range) = 0; + virtual ReleaseContainer findStarredReleases(const FindParameters& params) = 0; + // Tracks virtual void star(Database::UserId userId, Database::TrackId trackId) = 0; virtual void unstar(Database::UserId userId, Database::TrackId trackId) = 0; virtual bool isStarred(Database::UserId userId, Database::TrackId artistId) = 0; virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::TrackId artistId) = 0; - virtual TrackContainer getStarredTracks(Database::UserId userId, const std::vector& clusterIds, Database::Range range) = 0; + virtual TrackContainer findStarredTracks(const FindParameters& params) = 0; }; std::unique_ptr createFeedbackService(boost::asio::io_service& ioService, Database::Db& db); diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp b/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp index 1cfb6811..60113e0b 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp @@ -120,7 +120,7 @@ FeaturesEngine::loadFromTraining(const TrainSettings& trainSettings, const Progr auto transaction {session.createSharedTransaction()}; LMS_LOG(RECOMMENDATION, DEBUG) << "Getting Track features..."; - trackFeaturesIds = TrackFeatures::find(session, Range {}); + trackFeaturesIds = TrackFeatures::find(session); LMS_LOG(RECOMMENDATION, DEBUG) << "Getting Track features DONE (found " << trackFeaturesIds.results.size() << " track features)"; } diff --git a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp b/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp index 0e18f4a5..e1893a8a 100644 --- a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp @@ -37,7 +37,7 @@ namespace Scanner Session& session {_db.getTLSSession()}; auto transaction {session.createSharedTransaction()}; - const RangeResults tracks = Track::findIdsTrackMBIDDuplicates(session, Range {}); + const RangeResults tracks = Track::findIdsTrackMBIDDuplicates(session); for (const TrackId trackId : tracks.results) { const Track::pointer track {Track::find(session, trackId)}; diff --git a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp b/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp index a9572fac..1ae6781d 100644 --- a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp @@ -30,174 +30,169 @@ namespace Scanner { - void - ScanStepRemoveOrphanDbFiles::process(ScanContext& context) - { - removeOrphanTracks(context); - removeOrphanClusters(); - removeOrphanArtists(); - removeOrphanReleases(); - } + void ScanStepRemoveOrphanDbFiles::process(ScanContext& context) + { + removeOrphanTracks(context); + removeOrphanClusters(); + removeOrphanArtists(); + removeOrphanReleases(); + } - void ScanStepRemoveOrphanDbFiles::removeOrphanTracks(ScanContext& context) - { - using namespace Database; + void ScanStepRemoveOrphanDbFiles::removeOrphanTracks(ScanContext& context) + { + using namespace Database; - if (_abortScan) - return; + if (_abortScan) + return; - static constexpr std::size_t batchSize {50}; - Session& session {_db.getTLSSession()}; + static constexpr std::size_t batchSize{ 50 }; + Session& session{ _db.getTLSSession() }; - LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks to be removed..."; - std::size_t trackCount {}; + LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks to be removed..."; + std::size_t trackCount{}; - { - auto transaction {session.createSharedTransaction()}; - trackCount = Track::getCount(session); - } - LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks to be checked..."; + { + auto transaction{ session.createSharedTransaction() }; + trackCount = Track::getCount(session); + } + LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks to be checked..."; - context.currentStepStats.totalElems = trackCount; + context.currentStepStats.totalElems = trackCount; - RangeResults trackPaths; - std::vector tracksToRemove; + RangeResults trackPaths; + std::vector tracksToRemove; - // TODO handle only files in context.directory - for (std::size_t i {trackCount < batchSize ? 0 : trackCount - batchSize}; ; i -= (i > batchSize ? batchSize : i)) - { - tracksToRemove.clear(); + // TODO handle only files in context.directory + for (std::size_t i{ trackCount < batchSize ? 0 : trackCount - batchSize }; ; i -= (i > batchSize ? batchSize : i)) + { + tracksToRemove.clear(); - { - auto transaction {session.createSharedTransaction()}; - trackPaths = Track::findPaths(session, Range {i, batchSize}); - } + { + auto transaction{ session.createSharedTransaction() }; + trackPaths = Track::findPaths(session, Range{ i, batchSize }); + } - for (const Track::PathResult& trackPath : trackPaths.results) - { - if (_abortScan) - return; + for (const Track::PathResult& trackPath : trackPaths.results) + { + if (_abortScan) + return; - if (!checkFile(trackPath.path)) - tracksToRemove.push_back(trackPath.trackId); + if (!checkFile(trackPath.path)) + tracksToRemove.push_back(trackPath.trackId); - context.currentStepStats.processedElems++; - } + context.currentStepStats.processedElems++; + } - if (!tracksToRemove.empty()) - { - auto transaction {session.createSharedTransaction()}; + if (!tracksToRemove.empty()) + { + auto transaction{ session.createSharedTransaction() }; - for (const TrackId trackId : tracksToRemove) - { - Track::pointer track {Track::find(session, trackId)}; - if (track) - { - track.remove(); - context.stats.deletions++; - } - } - } + for (const TrackId trackId : tracksToRemove) + { + Track::pointer track{ Track::find(session, trackId) }; + if (track) + { + track.remove(); + context.stats.deletions++; + } + } + } - _progressCallback(context.currentStepStats); + _progressCallback(context.currentStepStats); - if (i == 0) - break; - } + if (i == 0) + break; + } - LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks checked!"; - } + LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks checked!"; + } - void - ScanStepRemoveOrphanDbFiles::removeOrphanClusters() - { - using namespace Database; + void ScanStepRemoveOrphanDbFiles::removeOrphanClusters() + { + using namespace Database; - LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters..."; - Session& session {_db.getTLSSession()}; - auto transaction {session.createUniqueTransaction()}; + LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters..."; + Session& session{ _db.getTLSSession() }; + auto transaction{ session.createUniqueTransaction() }; - // Now process orphan Cluster (no track) - auto clusterIds {Cluster::findOrphans(session, Range {})}; - for (ClusterId clusterId : clusterIds.results) - { - Cluster::pointer cluster {Cluster::find(session, clusterId)}; - LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'"; - cluster.remove(); - } - } + // Now process orphan Cluster (no track) + auto clusterIds{ Cluster::findOrphans(session) }; + for (ClusterId clusterId : clusterIds.results) + { + Cluster::pointer cluster{ Cluster::find(session, clusterId) }; + LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'"; + cluster.remove(); + } + } - void - ScanStepRemoveOrphanDbFiles::removeOrphanArtists() - { - using namespace Database; + void ScanStepRemoveOrphanDbFiles::removeOrphanArtists() + { + using namespace Database; - LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan artists..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan artists..."; - Session& session {_db.getTLSSession()}; - auto transaction {session.createUniqueTransaction()}; + Session& session{ _db.getTLSSession() }; + auto transaction{ session.createUniqueTransaction() }; - auto artistIds {Artist::findOrphanIds(session, Range {})}; - for (const ArtistId artistId : artistIds.results) - { - Artist::pointer artist {Artist::find(session, artistId)}; - LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'"; - artist.remove(); - } - } + auto artistIds{ Artist::findOrphanIds(session) }; + for (const ArtistId artistId : artistIds.results) + { + Artist::pointer artist{ Artist::find(session, artistId) }; + LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'"; + artist.remove(); + } + } - void - ScanStepRemoveOrphanDbFiles::removeOrphanReleases() - { - using namespace Database; + void ScanStepRemoveOrphanDbFiles::removeOrphanReleases() + { + using namespace Database; - LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases..."; - // TODO, by batch - Session& session {_db.getTLSSession()}; - auto transaction {session.createUniqueTransaction()}; + // TODO, by batch + Session& session{ _db.getTLSSession() }; + auto transaction{ session.createUniqueTransaction() }; - auto releases {Release::findOrphanIds(session, Range {})}; - for (const ReleaseId releaseId : releases.results) - { - Release::pointer release {Release::find(session, releaseId)}; - LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'"; - release.remove(); - } - } + auto releases{ Release::findOrphanIds(session) }; + for (const ReleaseId releaseId : releases.results) + { + Release::pointer release{ Release::find(session, releaseId) }; + LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'"; + release.remove(); + } + } - bool - ScanStepRemoveOrphanDbFiles::checkFile(const std::filesystem::path& p) - { - try - { - // For each track, make sure the the file still exists - // and still belongs to a media directory - if (!std::filesystem::exists( p ) - || !std::filesystem::is_regular_file( p ) ) - { - LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': missing"; - return false; - } + bool ScanStepRemoveOrphanDbFiles::checkFile(const std::filesystem::path& p) + { + try + { + // For each track, make sure the the file still exists + // and still belongs to a media directory + if (!std::filesystem::exists(p) + || !std::filesystem::is_regular_file(p)) + { + LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': missing"; + return false; + } - if (!PathUtils::isPathInRootPath(p, _settings.mediaDirectory, &excludeDirFileName)) - { - LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': out of media directory"; - return false; - } + if (!PathUtils::isPathInRootPath(p, _settings.mediaDirectory, &excludeDirFileName)) + { + LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': out of media directory"; + return false; + } - if (!PathUtils::hasFileAnyExtension(p, _settings.supportedExtensions)) - { - LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': file format no longer handled"; - return false; - } + if (!PathUtils::hasFileAnyExtension(p, _settings.supportedExtensions)) + { + LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': file format no longer handled"; + return false; + } - return true; - } - catch (std::filesystem::filesystem_error& e) - { - LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p.string() << "': " << e.what(); - return false; - } - } + return true; + } + catch (std::filesystem::filesystem_error& e) + { + LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p.string() << "': " << e.what(); + return false; + } + } } diff --git a/src/libs/subsonic/impl/SubsonicResponse.hpp b/src/libs/subsonic/impl/SubsonicResponse.hpp index 18cef189..60690dae 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.hpp +++ b/src/libs/subsonic/impl/SubsonicResponse.hpp @@ -29,6 +29,8 @@ namespace API::Subsonic { + // Max count expected from all API methods that expose a count + static inline constexpr std::size_t defaultMaxCountSize{ 1000 }; enum class ResponseFormat { diff --git a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp index 5dc3b2b4..6a55ffc6 100644 --- a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp +++ b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp @@ -47,8 +47,8 @@ namespace API::Subsonic // Optional params const std::size_t size{ getParameterAs(context.parameters, "size").value_or(10) }; const std::size_t offset{ getParameterAs(context.parameters, "offset").value_or(0) }; - if (size > 500) - throw ParameterValueTooHighGenericError{ "size", 500 }; + if (size > defaultMaxCountSize) + throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize }; const Range range{ offset, size }; @@ -122,7 +122,7 @@ namespace API::Subsonic // (no seed provided by subsonic, ot it would require to store some kind of context for each user/client when iterating over the random albums) Release::FindParameters params; params.setSortMethod(ReleaseSortMethod::Random); - params.setRange({ 0, size }); + params.setRange(Range{ 0, size }); releases = Release::findIds(context.dbSession, params); } @@ -132,10 +132,15 @@ namespace API::Subsonic } else if (type == "starred") { - releases = feedbackService.getStarredReleases(context.userId, {}, range); + Feedback::IFeedbackService::FindParameters params; + params.setUser(context.userId); + params.setRange(range); + releases = feedbackService.findStarredReleases(params); } else + { throw NotImplementedGenericError{}; + } Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response::Node& albumListNode{ response.createNode(id3 ? Response::Node::Key{ "albumList2" } : Response::Node::Key{ "albumList" }) }; @@ -162,19 +167,27 @@ namespace API::Subsonic Feedback::IFeedbackService& feedbackService{ *Service::get() }; - for (const ArtistId artistId : feedbackService.getStarredArtists(context.userId, {} /* clusters */, std::nullopt /* linkType */, ArtistSortMethod::BySortName, Range{}).results) + Feedback::IFeedbackService::FindParameters findParameters; + findParameters.setUser(context.userId); + { - if (auto artist{ Artist::find(context.dbSession, artistId) }) - starredNode.addArrayChild("artist", createArtistNode(context, artist, user, id3)); + Feedback::IFeedbackService::ArtistFindParameters artistFindParams; + artistFindParams.setUser(context.userId); + artistFindParams.setSortMethod(ArtistSortMethod::BySortName); + for (const ArtistId artistId : feedbackService.findStarredArtists(artistFindParams).results) + { + if (auto artist{ Artist::find(context.dbSession, artistId) }) + starredNode.addArrayChild("artist", createArtistNode(context, artist, user, id3)); + } } - for (const ReleaseId releaseId : feedbackService.getStarredReleases(context.userId, {} /* clusters */, Range{}).results) + for (const ReleaseId releaseId : feedbackService.findStarredReleases(findParameters).results) { if (auto release{ Release::find(context.dbSession, releaseId) }) starredNode.addArrayChild("album", createAlbumNode(context, release, user, id3)); } - for (const TrackId trackId : feedbackService.getStarredTracks(context.userId, {} /* clusters */, Range{}).results) + for (const TrackId trackId : feedbackService.findStarredTracks(findParameters).results) { if (auto track{ Track::find(context.dbSession, trackId) }) starredNode.addArrayChild("song", createSongNode(context, track, user)); @@ -198,8 +211,8 @@ namespace API::Subsonic { // Optional params std::size_t size{ getParameterAs(context.parameters, "size").value_or(50) }; - if (size > 500) - throw ParameterValueTooHighGenericError{"size", 500}; + if (size > defaultMaxCountSize) + throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize }; auto transaction{ context.dbSession.createSharedTransaction() }; @@ -207,13 +220,17 @@ namespace API::Subsonic if (!user) throw UserNotAuthorizedError{}; - const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setSortMethod(TrackSortMethod::Random).setRange({0, size})) }; - Response response{ Response::createOkResponse(context.serverProtocolVersion) }; - Response::Node& randomSongsNode{ response.createNode("randomSongs") }; - for (const Track::pointer& track : tracks.results) - randomSongsNode.addArrayChild("song", createSongNode(context, track, user)); + + Track::FindParameters params; + params.setSortMethod(TrackSortMethod::Random); + params.setRange(Range{ 0, size }); + + Track::find(context.dbSession, params, [&](const Track::pointer& track) + { + randomSongsNode.addArrayChild("song", createSongNode(context, track, user)); + }); return response; } @@ -225,8 +242,8 @@ namespace API::Subsonic // Optional params std::size_t count{ getParameterAs(context.parameters, "count").value_or(10) }; - if (count > 500) - throw ParameterValueTooHighGenericError{"count", 500}; + if (count > defaultMaxCountSize) + throw ParameterValueTooHighGenericError{"count", defaultMaxCountSize}; std::size_t offset{ getParameterAs(context.parameters, "offset").value_or(0) }; @@ -249,11 +266,12 @@ namespace API::Subsonic Track::FindParameters params; params.setClusters({ cluster->getId() }); - params.setRange({ offset, count }); + params.setRange(Range{ offset, count }); - const auto tracks{ Track::find(context.dbSession, params) }; - for (const Track::pointer& track : tracks.results) - songsByGenreNode.addArrayChild("song", createSongNode(context, track, user)); + Track::find(context.dbSession, params, [&](const Track::pointer& track) + { + songsByGenreNode.addArrayChild("song", createSongNode(context, track, user)); + }); return response; } diff --git a/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp b/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp index a149555c..1d20eb76 100644 --- a/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp +++ b/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp @@ -40,7 +40,7 @@ namespace API::Subsonic if (!user) throw UserNotAuthorizedError{}; - const auto bookmarkIds{ TrackBookmark::find(context.dbSession, user->getId(), Range {}) }; + const auto bookmarkIds{ TrackBookmark::find(context.dbSession, user->getId()) }; Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response::Node& bookmarksNode{ response.createNode("bookmarks") }; diff --git a/src/libs/subsonic/impl/entrypoints/Browsing.cpp b/src/libs/subsonic/impl/entrypoints/Browsing.cpp index 16b8ed8d..c9dcd7e0 100644 --- a/src/libs/subsonic/impl/entrypoints/Browsing.cpp +++ b/src/libs/subsonic/impl/entrypoints/Browsing.cpp @@ -124,7 +124,7 @@ namespace API::Subsonic // first pass: dispatch the artists by first letter LMS_LOG(API_SUBSONIC, DEBUG) << "GetArtists: fetching all artists..."; std::map> artistsSortedByFirstChar; - std::size_t currentArtistOffset{0}; + std::size_t currentArtistOffset{ 0 }; constexpr std::size_t batchSize{ 100 }; bool hasMoreArtists{ true }; while (hasMoreArtists) @@ -190,7 +190,7 @@ namespace API::Subsonic { Track::FindParameters params; params.setArtist(id); - params.setRange({ 0, meanTrackCountPerArtist }); + params.setRange(Range{ 0, meanTrackCountPerArtist }); params.setSortMethod(TrackSortMethod::Random); const auto artistTracks{ Track::findIds(context.dbSession, params) }; @@ -221,7 +221,7 @@ namespace API::Subsonic { Track::FindParameters params; params.setRelease(id); - params.setRange({ 0, meanTrackCountPerRelease }); + params.setRange(Range{ 0, meanTrackCountPerRelease }); params.setSortMethod(TrackSortMethod::Random); const auto releaseTracks{ Track::findIds(context.dbSession, params) }; @@ -242,6 +242,8 @@ namespace API::Subsonic { // Optional params std::size_t count{ getParameterAs(context.parameters, "count").value_or(50) }; + if (count > defaultMaxCountSize) + throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize }; std::vector tracks; @@ -316,9 +318,11 @@ namespace API::Subsonic directoryNode.setAttribute("id", idToString(RootId{})); directoryNode.setAttribute("name", "Music"); - const auto rootArtistIds{ Artist::find(context.dbSession, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName)) }; - for (const Artist::pointer& artist : rootArtistIds.results) - directoryNode.addArrayChild("child", createArtistNode(context, artist, user, false /* no id3 */)); + // TODO: this does not scale when a lot of artists are present + Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::BySortName), [&](const Artist::pointer& artist) + { + directoryNode.addArrayChild("child", createArtistNode(context, artist, user, false /* no id3 */)); + }); } else if (artistId) { @@ -330,9 +334,10 @@ namespace API::Subsonic directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(artist->getName())); - const auto artistReleases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(*artistId)) }; - for (const Release::pointer& release : artistReleases.results) - directoryNode.addArrayChild("child", createAlbumNode(context, release, user, false /* no id3 */)); + Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release) + { + directoryNode.addArrayChild("child", createAlbumNode(context, release, user, false /* no id3 */)); + }); } else if (releaseId) { @@ -344,9 +349,10 @@ namespace API::Subsonic directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(release->getName())); - const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release)) }; - for (const Track::pointer& track : tracks.results) - directoryNode.addArrayChild("child", createSongNode(context, track, user)); + Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track) + { + directoryNode.addArrayChild("child", createSongNode(context, track, user)); + }); } else throw BadParameterGenericError{ "id" }; diff --git a/src/libs/subsonic/impl/entrypoints/Searching.cpp b/src/libs/subsonic/impl/entrypoints/Searching.cpp index 79dccac7..41d1e774 100644 --- a/src/libs/subsonic/impl/entrypoints/Searching.cpp +++ b/src/libs/subsonic/impl/entrypoints/Searching.cpp @@ -57,12 +57,12 @@ namespace API::Subsonic std::size_t songCount{ getParameterAs(context.parameters, "songCount").value_or(20) }; std::size_t songOffset{ getParameterAs(context.parameters, "songOffset").value_or(0) }; - if (artistCount > 500) - throw ParameterValueTooHighGenericError{ "artistCount", 500 }; - else if (albumCount > 500) - throw ParameterValueTooHighGenericError{ "albumCount", 500 }; - else if (songCount > 500) - throw ParameterValueTooHighGenericError{ "songCount", 500 }; + if (artistCount > defaultMaxCountSize) + throw ParameterValueTooHighGenericError{ "artistCount", defaultMaxCountSize }; + else if (albumCount > defaultMaxCountSize) + throw ParameterValueTooHighGenericError{ "albumCount", defaultMaxCountSize }; + else if (songCount > defaultMaxCountSize) + throw ParameterValueTooHighGenericError{ "songCount", defaultMaxCountSize }; Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response::Node& searchResult2Node{ response.createNode(id3 ? "searchResult3" : "searchResult2") }; @@ -77,30 +77,36 @@ namespace API::Subsonic { Artist::FindParameters params; params.setKeywords(keywords); - params.setRange({ artistOffset, artistCount }); + params.setRange(Range{ artistOffset, artistCount }); - for (const Artist::pointer& artist : Artist::find(context.dbSession, params).results) - searchResult2Node.addArrayChild("artist", createArtistNode(context, artist, user, id3)); + Artist::find(context.dbSession, params, [&](const Artist::pointer& artist) + { + searchResult2Node.addArrayChild("artist", createArtistNode(context, artist, user, id3)); + }); } if (albumCount > 0) { Release::FindParameters params; params.setKeywords(keywords); - params.setRange({ albumOffset, albumCount }); + params.setRange(Range{ albumOffset, albumCount }); - for (const Release::pointer& release : Release::find(context.dbSession, params).results) - searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3)); + Release::find(context.dbSession, params, [&](const Release::pointer& release) + { + searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3)); + }); } if (songCount > 0) { Track::FindParameters params; params.setKeywords(keywords); - params.setRange({ songOffset, songCount }); + params.setRange(Range{ songOffset, songCount }); - for (const Track::pointer& track : Track::find(context.dbSession, params).results) - searchResult2Node.addArrayChild("song", createSongNode(context, track, user)); + Track::find(context.dbSession, params, [&](const Track::pointer& track) + { + searchResult2Node.addArrayChild("song", createSongNode(context, track, user)); + }); } return response; diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index fab4cc70..bb414679 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -799,8 +799,9 @@ namespace UserInterface params.setDistinct(false); params.setSortMethod(TrackSortMethod::TrackList); - const auto tracks{ Track::find(session, params) }; - for (const Track::pointer& track : tracks.results) - session.create(track, trackList); + Track::find(session, params, [&](const Track::pointer& track) + { + session.create(track, trackList); + }); } } // namespace UserInterface diff --git a/src/lms/ui/explore/ArtistCollector.cpp b/src/lms/ui/explore/ArtistCollector.cpp index 67d08238..3f5ca7b5 100644 --- a/src/lms/ui/explore/ArtistCollector.cpp +++ b/src/lms/ui/explore/ArtistCollector.cpp @@ -33,16 +33,14 @@ namespace UserInterface { using namespace Database; - RangeResults ArtistCollector::get(Database::Range range) + RangeResults ArtistCollector::get(std::optional requestedRange) { Feedback::IFeedbackService& feedbackService{ *Service::get() }; Scrobbling::IScrobblingService& scrobblingService{ *Service::get() }; - RangeResults artists; + const Range range{ getActualRange(requestedRange) }; - range = getActualRange(range); - if (range.size == 0) - return artists; + RangeResults artists; switch (getMode()) { @@ -51,8 +49,16 @@ namespace UserInterface break; case Mode::Starred: - artists = feedbackService.getStarredArtists(LmsApp->getUserId(), getFilters().getClusterIds(), _linkType, ArtistSortMethod::StarredDateDesc, range); + { + Feedback::IFeedbackService::ArtistFindParameters params; + params.setUser(LmsApp->getUserId()); + params.setClusters(getFilters().getClusterIds()); + params.setLinkType(_linkType); + params.setSortMethod(ArtistSortMethod::StarredDateDesc); + params.setRange(range); + artists = feedbackService.findStarredArtists(params); break; + } case Mode::RecentlyPlayed: artists = scrobblingService.getRecentArtists(LmsApp->getUserId(), getFilters().getClusterIds(), _linkType, range); diff --git a/src/lms/ui/explore/ArtistCollector.hpp b/src/lms/ui/explore/ArtistCollector.hpp index ab9d43fc..2c918ed7 100644 --- a/src/lms/ui/explore/ArtistCollector.hpp +++ b/src/lms/ui/explore/ArtistCollector.hpp @@ -37,7 +37,7 @@ namespace UserInterface public: using DatabaseCollectorBase::DatabaseCollectorBase; - Database::RangeResults get(Database::Range range); + Database::RangeResults get(std::optional range = std::nullopt); void reset() { _randomArtists.reset(); } void setArtistLinkType(std::optional linkType) { _linkType = linkType; } diff --git a/src/lms/ui/explore/DatabaseCollectorBase.cpp b/src/lms/ui/explore/DatabaseCollectorBase.cpp index 16afcc7f..fcb80343 100644 --- a/src/lms/ui/explore/DatabaseCollectorBase.cpp +++ b/src/lms/ui/explore/DatabaseCollectorBase.cpp @@ -30,16 +30,25 @@ namespace UserInterface { } - DatabaseCollectorBase::Range DatabaseCollectorBase::getActualRange(Range range) const + DatabaseCollectorBase::Range DatabaseCollectorBase::getActualRange(std::optional requestedRange) const { - assert(range.size > 0); + Database::Range res; - if (range.offset < _maxCount) - range.size = std::min(_maxCount - range.offset, range.size); + if (!requestedRange) + { + res.offset = 0; + res.size = _maxCount; + } else - range.size = 0; + { + res.offset = requestedRange->offset; + if (requestedRange->offset < _maxCount) + res.size = std::min(_maxCount - requestedRange->offset, requestedRange->size); + else + res.size = 0; + } - return range; + return res; } std::size_t DatabaseCollectorBase::getMaxCount() const diff --git a/src/lms/ui/explore/DatabaseCollectorBase.hpp b/src/lms/ui/explore/DatabaseCollectorBase.hpp index 2d654779..52f3a458 100644 --- a/src/lms/ui/explore/DatabaseCollectorBase.hpp +++ b/src/lms/ui/explore/DatabaseCollectorBase.hpp @@ -56,9 +56,9 @@ namespace UserInterface void setSearch(std::string_view search); protected: - Range getActualRange(Range range) const; + Range getActualRange(std::optional range) const; std::size_t getMaxCount() const; - Filters& getFilters() { return _filters; } + Filters& getFilters() { return _filters; } const std::vector& getSearchKeywords() const { return _searchKeywords; } private: diff --git a/src/lms/ui/explore/Filters.cpp b/src/lms/ui/explore/Filters.cpp index a3a08487..e6b45457 100644 --- a/src/lms/ui/explore/Filters.cpp +++ b/src/lms/ui/explore/Filters.cpp @@ -84,7 +84,7 @@ Filters::showDialog() { auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - const auto clusterTypesIds {ClusterType::findUsed(LmsApp->getDbSession(), Range {})}; + const auto clusterTypesIds {ClusterType::findUsed(LmsApp->getDbSession())}; for (const ClusterTypeId clusterTypeId : clusterTypesIds.results) { const auto clusterType {ClusterType::find(LmsApp->getDbSession(), clusterTypeId)}; diff --git a/src/lms/ui/explore/PlayQueueController.cpp b/src/lms/ui/explore/PlayQueueController.cpp index 0329b880..74fc6911 100644 --- a/src/lms/ui/explore/PlayQueueController.cpp +++ b/src/lms/ui/explore/PlayQueueController.cpp @@ -45,7 +45,7 @@ namespace UserInterface params.setArtist(artistId); params.setSortMethod(Database::TrackSortMethod::DateDescAndRelease); params.setClusters(clusters); - params.setRange({ 0, maxTrackCount - res.size() }); + params.setRange(Database::Range{ 0, maxTrackCount - res.size() }); const auto tracks{ Database::Track::findIds(session, params) }; @@ -74,7 +74,7 @@ namespace UserInterface params.setRelease(releaseId); params.setSortMethod(Database::TrackSortMethod::Release); params.setClusters(clusters); - params.setRange({ 0, maxTrackCount - res.size() }); + params.setRange(Database::Range{ 0, maxTrackCount - res.size() }); const auto tracks{ Database::Track::findIds(session, params) }; @@ -98,7 +98,7 @@ namespace UserInterface Database::Track::FindParameters params; params.setTrackList(trackListId); params.setClusters(clusters); - params.setRange({ 0, maxTrackCount }); + params.setRange(Database::Range{ 0, maxTrackCount }); params.setSortMethod(TrackSortMethod::TrackList); params.setDistinct(false); diff --git a/src/lms/ui/explore/ReleaseCollector.cpp b/src/lms/ui/explore/ReleaseCollector.cpp index 4a421d4c..c3ae1e15 100644 --- a/src/lms/ui/explore/ReleaseCollector.cpp +++ b/src/lms/ui/explore/ReleaseCollector.cpp @@ -32,15 +32,14 @@ namespace UserInterface { using namespace Database; - RangeResults ReleaseCollector::get(Database::Range range) + RangeResults ReleaseCollector::get(std::optional requestedRange) { Feedback::IFeedbackService& feedbackService{ *Service::get() }; Scrobbling::IScrobblingService& scrobblingService{ *Service::get() }; + const Range range{ getActualRange(requestedRange) }; + RangeResults releases; - range = getActualRange(range); - if (range.size == 0) - return releases; switch (getMode()) { @@ -49,8 +48,14 @@ namespace UserInterface break; case Mode::Starred: - releases = feedbackService.getStarredReleases(LmsApp->getUserId(), getFilters().getClusterIds(), range); + { + Feedback::IFeedbackService::FindParameters params; + params.setUser(LmsApp->getUserId()); + params.setClusters(getFilters().getClusterIds()); + params.setRange(range); + releases = feedbackService.findStarredReleases(params); break; + } case ReleaseCollector::Mode::RecentlyPlayed: releases = scrobblingService.getRecentReleases(LmsApp->getUserId(), getFilters().getClusterIds(), range); @@ -118,7 +123,7 @@ namespace UserInterface Release::FindParameters params; params.setClusters(getFilters().getClusterIds()); params.setSortMethod(ReleaseSortMethod::Random); - params.setRange({ 0, getMaxCount() }); + params.setRange(Range{ 0, getMaxCount() }); { auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; diff --git a/src/lms/ui/explore/ReleaseCollector.hpp b/src/lms/ui/explore/ReleaseCollector.hpp index 924aabe6..1a10c205 100644 --- a/src/lms/ui/explore/ReleaseCollector.hpp +++ b/src/lms/ui/explore/ReleaseCollector.hpp @@ -19,7 +19,7 @@ #pragma once -#include +#include #include "DatabaseCollectorBase.hpp" @@ -39,7 +39,7 @@ namespace UserInterface public: using DatabaseCollectorBase::DatabaseCollectorBase; - Database::RangeResults get(Database::Range range); + Database::RangeResults get(std::optional range = std::nullopt); void reset() { _randomReleases.reset(); } private: diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 40754fc2..042e9c3c 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -373,100 +373,100 @@ namespace UserInterface params.setSortMethod(Database::TrackSortMethod::Release); params.setClusters(_filters.getClusterIds()); - for (const Database::Track::pointer& track : Database::Track::find(LmsApp->getDbSession(), params).results) - { - const Database::TrackId trackId{ track->getId() }; - const auto discNumber{ track->getDiscNumber() }; - - Wt::WContainerWidget* container; - if (isReleaseMultiDisc && discNumber) - container = getOrAddDiscContainer(*discNumber, track->getDiscSubtitle()); - else - container = getOrAddNoDiscContainer(); - - Template* entry{ container->addNew