Rework db range primitives + fixed bug when playing all in releases/tracks views + raised limit to 1000 for ranges in subsonic API

This commit is contained in:
emeric
2023-11-10 16:17:18 +01:00
parent bd61093898
commit b7a8bf85b8
61 changed files with 1704 additions and 2603 deletions
@@ -374,8 +374,8 @@ namespace Cover
auto transaction{ session.createSharedTransaction() }; 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()) if (!tracks.results.empty())
{ {
const Track::pointer& track{ tracks.results.front() }; const Track::pointer& track{ tracks.results.front() };
+14 -7
View File
@@ -210,12 +210,11 @@ namespace Database
} }
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, Range range) RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ session.getDboSession().query<ArtistId>("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)") }; auto query{ session.getDboSession().query<ArtistId>("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<ArtistId>(query, range);
return Utils::execQuery(query, range);
} }
RangeResults<ArtistId> Artist::findIds(Session& session, const FindParameters& params) RangeResults<ArtistId> Artist::findIds(Session& session, const FindParameters& params)
@@ -223,7 +222,7 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery<ArtistId>(session, params) }; auto query{ createQuery<ArtistId>(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery<ArtistId>(query, params.range);
} }
RangeResults<Artist::pointer> Artist::find(Session& session, const FindParameters& params) RangeResults<Artist::pointer> Artist::find(Session& session, const FindParameters& params)
@@ -231,10 +230,18 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery<Wt::Dbo::ptr<Artist>>(session, params) }; auto query{ createQuery<Wt::Dbo::ptr<Artist>>(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery<Artist::pointer>(query, params.range);
} }
RangeResults<ArtistId> Artist::findSimilarArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes, Range range) const void Artist::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
{
session.checkSharedLocked();
auto query{ createQuery<Wt::Dbo::ptr<Artist>>(session, params) };
Utils::execQuery(query, params.range, func);
}
RangeResults<ArtistId> Artist::findSimilarArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes, std::optional<Range> range) const
{ {
assert(session()); assert(session());
@@ -278,7 +285,7 @@ namespace Database
for (TrackArtistLinkType type : artistLinkTypes) for (TrackArtistLinkType type : artistLinkTypes)
query.bind(type); query.bind(type);
return Utils::execQuery(query, range); return Utils::execQuery<ArtistId>(query, range);
} }
std::vector<std::vector<Cluster::pointer>> Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const std::vector<std::vector<Cluster::pointer>> Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
+12 -12
View File
@@ -95,7 +95,7 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery<ClusterId>(session, params) }; auto query{ createQuery<ClusterId>(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery<ClusterId>(query, params.range);
} }
RangeResults<Cluster::pointer> Cluster::find(Session& session, const FindParameters& params) RangeResults<Cluster::pointer> Cluster::find(Session& session, const FindParameters& params)
@@ -103,15 +103,15 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery<Wt::Dbo::ptr<Cluster>>(session, params) }; auto query{ createQuery<Wt::Dbo::ptr<Cluster>>(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery<Cluster::pointer>(query, params.range);
} }
RangeResults<ClusterId> Cluster::findOrphans(Session& session, Range range) RangeResults<ClusterId> Cluster::findOrphans(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ session.getDboSession().query<ClusterId>("SELECT DISTINCT c.id FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)") }; auto query{ session.getDboSession().query<ClusterId>("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<ClusterId>(query, range);
} }
Cluster::pointer Cluster::find(Session& session, ClusterId id) Cluster::pointer Cluster::find(Session& session, ClusterId id)
@@ -142,14 +142,14 @@ namespace Database
_tracks.insert(getDboPtr(track)); _tracks.insert(getDboPtr(track));
} }
RangeResults<TrackId> Cluster::getTracks(Range range) const RangeResults<TrackId> Cluster::getTracks(std::optional<Range> range) const
{ {
assert(session()); assert(session());
auto query{ session()->query<TrackId>("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") auto query{ session()->query<TrackId>("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()) }; .where("c.id = ?").bind(getId()) };
return Utils::execQuery(query, range); return Utils::execQuery<TrackId>(query, range);
} }
ClusterType::ClusterType(std::string_view name) ClusterType::ClusterType(std::string_view name)
@@ -170,7 +170,7 @@ namespace Database
} }
RangeResults<ClusterTypeId> ClusterType::findOrphans(Session& session, Range range) RangeResults<ClusterTypeId> ClusterType::findOrphans(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
@@ -179,10 +179,10 @@ namespace Database
" LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id") " LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id")
.where("c.id IS NULL") }; .where("c.id IS NULL") };
return Utils::execQuery(query, range); return Utils::execQuery<ClusterTypeId>(query, range);
} }
RangeResults<ClusterTypeId> ClusterType::findUsed(Session& session, Range range) RangeResults<ClusterTypeId> ClusterType::findUsed(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
@@ -190,7 +190,7 @@ namespace Database
"SELECT DISTINCT c_t.id from cluster_type c_t") "SELECT DISTINCT c_t.id from cluster_type c_t")
.join("cluster c ON c_t.id = c.cluster_type_id") }; .join("cluster c ON c_t.id = c.cluster_type_id") };
return Utils::execQuery(query, range); return Utils::execQuery<ClusterTypeId>(query, range);
} }
ClusterType::pointer ClusterType::find(Session& session, std::string_view name) ClusterType::pointer ClusterType::find(Session& session, std::string_view name)
@@ -207,13 +207,13 @@ namespace Database
return session.getDboSession().find<ClusterType>().where("id = ?").bind(id).resultValue(); return session.getDboSession().find<ClusterType>().where("id = ?").bind(id).resultValue();
} }
RangeResults<ClusterTypeId> ClusterType::find(Session& session, Range range) RangeResults<ClusterTypeId> ClusterType::find(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ session.getDboSession().query<ClusterTypeId>("SELECT id from cluster_type") }; auto query{ session.getDboSession().query<ClusterTypeId>("SELECT id from cluster_type") };
return Utils::execQuery(query, range); return Utils::execQuery<ClusterTypeId>(query, range);
} }
Cluster::pointer ClusterType::getCluster(const std::string& name) const Cluster::pointer ClusterType::getCluster(const std::string& name) const
+13 -13
View File
@@ -172,7 +172,7 @@ namespace Database
if (parameters.syncState) if (parameters.syncState)
query.where("sync_state = ?").bind(*parameters.syncState); query.where("sync_state = ?").bind(*parameters.syncState);
return Utils::execQuery(query, parameters.range); return Utils::execQuery<ListenId>(query, parameters.range);
} }
Listen::pointer Listen::find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime) Listen::pointer Listen::find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime)
@@ -187,7 +187,7 @@ namespace Database
.resultValue(); .resultValue();
} }
RangeResults<ArtistId> Listen::getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range) RangeResults<ArtistId> Listen::getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType) }; auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType) };
@@ -196,57 +196,57 @@ namespace Database
.orderBy("COUNT(a.id) DESC") .orderBy("COUNT(a.id) DESC")
.groupBy("a.id") }; .groupBy("a.id") };
return Utils::execQuery(query, range); return Utils::execQuery<ArtistId>(query, range);
} }
RangeResults<ReleaseId> Listen::getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range) RangeResults<ReleaseId> Listen::getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds) auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds)
.orderBy("COUNT(r.id) DESC") .orderBy("COUNT(r.id) DESC")
.groupBy("r.id") }; .groupBy("r.id") };
return Utils::execQuery(query, range); return Utils::execQuery<ReleaseId>(query, range);
} }
RangeResults<TrackId> Listen::getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range) RangeResults<TrackId> Listen::getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds) auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds)
.orderBy("COUNT(t.id) DESC") .orderBy("COUNT(t.id) DESC")
.groupBy("t.id") }; .groupBy("t.id") };
return Utils::execQuery(query, range); return Utils::execQuery<TrackId>(query, range);
} }
RangeResults<ArtistId> Listen::getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range) RangeResults<ArtistId> Listen::getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType) auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType)
.groupBy("a.id").having("l.date_time = MAX(l.date_time)") .groupBy("a.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC") }; .orderBy("l.date_time DESC") };
return Utils::execQuery(query, range); return Utils::execQuery<ArtistId>(query, range);
} }
RangeResults<ReleaseId> Listen::getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range) RangeResults<ReleaseId> Listen::getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds) auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds)
.groupBy("r.id").having("l.date_time = MAX(l.date_time)") .groupBy("r.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC") }; .orderBy("l.date_time DESC") };
return Utils::execQuery(query, range); return Utils::execQuery<ReleaseId>(query, range);
} }
RangeResults<TrackId> Listen::getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range) RangeResults<TrackId> Listen::getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds) auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds)
.groupBy("t.id").having("l.date_time = MAX(l.date_time)") .groupBy("t.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC") }; .orderBy("l.date_time DESC") };
return Utils::execQuery(query, range); return Utils::execQuery<TrackId>(query, range);
} }
std::size_t Listen::getCount(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId) std::size_t Listen::getCount(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId)
+15 -7
View File
@@ -188,7 +188,7 @@ namespace Database
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Release::FindParameters& params) Wt::Dbo::Query<ResultType> createQuery(Session& session, const Release::FindParameters& params)
{ {
std::string_view itemToSelect; std::string_view itemToSelect;
if constexpr (std::is_same_v<ResultType, ReleaseId>) if constexpr (std::is_same_v<ResultType, ReleaseId>)
itemToSelect = "r.id"; itemToSelect = "r.id";
else if constexpr (std::is_same_v<ResultType, Wt::Dbo::ptr<Release>>) else if constexpr (std::is_same_v<ResultType, Wt::Dbo::ptr<Release>>)
@@ -256,7 +256,7 @@ namespace Database
return session.getDboSession().query<int>("SELECT COUNT(*) FROM release"); return session.getDboSession().query<int>("SELECT COUNT(*) FROM release");
} }
RangeResults<ReleaseId> Release::findIdsOrderedByArtist(Session& session, Range range) RangeResults<ReleaseId> Release::findIdsOrderedByArtist(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
@@ -268,15 +268,15 @@ namespace Database
" INNER JOIN artist a ON t_a_l.artist_id = a.id") " INNER JOIN artist a ON t_a_l.artist_id = a.id")
.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE") }; .orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE") };
return Utils::execQuery(query, range); return Utils::execQuery<ReleaseId>(query, range);
} }
RangeResults<ReleaseId> Release::findOrphanIds(Session& session, Range range) RangeResults<ReleaseId> Release::findOrphanIds(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ session.getDboSession().query<ReleaseId>("select r.id from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL") }; auto query{ session.getDboSession().query<ReleaseId>("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<ReleaseId>(query, range);
} }
RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params) RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params)
@@ -284,7 +284,15 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, params) }; auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery<pointer>(query, params.range);
}
void Release::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
{
session.checkSharedLocked();
auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, params) };
Utils::execQuery<pointer>(query, params.range, func);
} }
RangeResults<ReleaseId> Release::findIds(Session& session, const FindParameters& params) RangeResults<ReleaseId> Release::findIds(Session& session, const FindParameters& params)
@@ -292,7 +300,7 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery<ReleaseId>(session, params) }; auto query{ createQuery<ReleaseId>(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery<ReleaseId>(query, params.range);
} }
std::size_t Release::getDiscCount() const std::size_t Release::getDiscCount() const
@@ -76,7 +76,7 @@ namespace Database
if (params.user.isValid()) if (params.user.isValid())
query.where("s_t.user_id = ?").bind(params.user); query.where("s_t.user_id = ?").bind(params.user);
return Utils::execQuery(query, params.range); return Utils::execQuery<StarredTrackId>(query, params.range);
} }
void StarredTrack::setDateTime(const Wt::WDateTime& dateTime) void StarredTrack::setDateTime(const Wt::WDateTime& dateTime)
+22 -13
View File
@@ -44,7 +44,8 @@ namespace Database
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ session.getDboSession().query<ResultType>(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<ResultType>(selectStatement + " " + std::string{ itemToSelect } + " FROM track t") };
assert(params.keywords.empty() || params.name.empty()); assert(params.keywords.empty() || params.name.empty());
for (std::string_view keyword : params.keywords) for (std::string_view keyword : params.keywords)
@@ -129,7 +130,7 @@ namespace Database
query.where("r.name = ?").bind(params.releaseName); 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 t_l ON t_l_e.tracklist_id = t_l.id");
query.join("tracklist_entry t_l_e ON t.id = t_l_e.track_id"); query.join("tracklist_entry t_l_e ON t.id = t_l_e.track_id");
@@ -249,7 +250,7 @@ namespace Database
return std::vector<Track::pointer>(res.begin(), res.end()); return std::vector<Track::pointer>(res.begin(), res.end());
} }
RangeResults<Track::PathResult> Track::findPaths(Session& session, Range range) RangeResults<Track::PathResult> Track::findPaths(Session& session, std::optional<Range> range)
{ {
using QueryResultType = std::tuple<TrackId, std::string>; using QueryResultType = std::tuple<TrackId, std::string>;
session.checkSharedLocked(); session.checkSharedLocked();
@@ -257,7 +258,7 @@ namespace Database
// TODO Dbo traits on filesystem // TODO Dbo traits on filesystem
auto query{ session.getDboSession().query<QueryResultType>("SELECT id, file_path FROM track") }; auto query{ session.getDboSession().query<QueryResultType>("SELECT id, file_path FROM track") };
RangeResults<QueryResultType> queryResults{ Utils::execQuery(query, range) }; RangeResults<QueryResultType> queryResults{ Utils::execQuery<QueryResultType>(query, range) };
RangeResults<PathResult> res; RangeResults<PathResult> res;
res.range = queryResults.range; 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), std::transform(std::cbegin(queryResults.results), std::cend(queryResults.results), std::back_inserter(res.results),
[](const QueryResultType& queryResult) [](const QueryResultType& queryResult)
{ {
return PathResult{ std::get<0>(queryResult), std::get<1>(queryResult) }; return PathResult{ std::get<TrackId>(queryResult), std::move(std::get<std::string>(queryResult)) };
}); });
return res; return res;
} }
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, Range range) RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ session.getDboSession().query<TrackId>("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)") auto query{ session.getDboSession().query<TrackId>("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") }; .orderBy("track.release_id,track.disc_number,track.track_number,track.mbid") };
return Utils::execQuery(query, range); return Utils::execQuery<TrackId>(query, range);
} }
RangeResults<TrackId> Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, Range range) RangeResults<TrackId> Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional<Range> range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
@@ -291,7 +292,7 @@ namespace Database
.where("LENGTH(t.recording_mbid) > 0") .where("LENGTH(t.recording_mbid) > 0")
.where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)") }; .where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)") };
return Utils::execQuery(query, range); return Utils::execQuery<TrackId>(query, range);
} }
std::vector<Cluster::pointer> Track::getClusters() const std::vector<Cluster::pointer> Track::getClusters() const
@@ -316,7 +317,7 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery<TrackId>(session, parameters) }; auto query{ createQuery<TrackId>(session, parameters) };
return Utils::execQuery(query, parameters.range); return Utils::execQuery<TrackId>(query, parameters.range);
} }
RangeResults<Track::pointer> Track::find(Session& session, const FindParameters& parameters) RangeResults<Track::pointer> Track::find(Session& session, const FindParameters& parameters)
@@ -324,10 +325,18 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, parameters) }; auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, parameters) };
return Utils::execQuery(query, parameters.range); return Utils::execQuery<Track::pointer>(query, parameters.range);
} }
RangeResults<TrackId> Track::findSimilarTrackIds(Session& session, const std::vector<TrackId>& tracks, Range range) void Track::find(Session& session, const FindParameters& params, std::function<void(const Track::pointer&)> func)
{
session.checkSharedLocked();
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, params)};
Utils::execQuery(query, params.range, func);
}
RangeResults<TrackId> Track::findSimilarTrackIds(Session& session, const std::vector<TrackId>& tracks, std::optional<Range> range)
{ {
assert(!tracks.empty()); assert(!tracks.empty());
session.checkSharedLocked(); session.checkSharedLocked();
@@ -354,7 +363,7 @@ namespace Database
for (TrackId trackId : tracks) for (TrackId trackId : tracks)
query.bind(trackId); query.bind(trackId);
return Utils::execQuery(query, range); return Utils::execQuery<TrackId>(query, range);
} }
void Track::clearArtistLinks() void Track::clearArtistLinks()
@@ -84,7 +84,7 @@ namespace Database
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery(session, params) }; auto query{ createQuery(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery<TrackArtistLinkId>(query, params.range);
} }
EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session) EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session)
@@ -27,57 +27,52 @@
namespace Database { namespace Database {
TrackBookmark::TrackBookmark(ObjectPtr<User> user, ObjectPtr<Track> track) TrackBookmark::TrackBookmark(ObjectPtr<User> user, ObjectPtr<Track> track)
: _user {getDboPtr(user)}, : _user{ getDboPtr(user) },
_track {getDboPtr(track)} _track{ getDboPtr(track) }
{ {
} }
TrackBookmark::pointer TrackBookmark::pointer TrackBookmark::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track)
TrackBookmark::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track) {
{ return session.getDboSession().add(std::unique_ptr<TrackBookmark> {new TrackBookmark{ user, track }});
return session.getDboSession().add(std::unique_ptr<TrackBookmark> {new TrackBookmark {user, track}}); }
}
std::size_t std::size_t TrackBookmark::getCount(Session& session)
TrackBookmark::getCount(Session& session) {
{ session.checkSharedLocked();
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track_bookmark"); return session.getDboSession().query<int>("SELECT COUNT(*) FROM track_bookmark");
} }
RangeResults<TrackBookmarkId> RangeResults<TrackBookmarkId> TrackBookmark::find(Session& session, UserId userId, std::optional<Range> range)
TrackBookmark::find(Session& session, UserId userId, Range range) {
{ session.checkSharedLocked();
session.checkSharedLocked();
auto query {session.getDboSession().query<TrackBookmarkId>("SELECT id from track_bookmark") auto query{ session.getDboSession().query<TrackBookmarkId>("SELECT id from track_bookmark")
.where("user_id = ?").bind(userId)}; .where("user_id = ?").bind(userId) };
return Utils::execQuery(query, range); return Utils::execQuery<TrackBookmarkId>(query, range);
} }
TrackBookmark::pointer TrackBookmark::pointer TrackBookmark::find(Session& session, UserId userId, TrackId trackId)
TrackBookmark::find(Session& session, UserId userId, TrackId trackId) {
{ session.checkSharedLocked();
session.checkSharedLocked();
return session.getDboSession().find<TrackBookmark>() return session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(userId) .where("user_id = ?").bind(userId)
.where("track_id = ?").bind(trackId) .where("track_id = ?").bind(trackId)
.resultValue(); .resultValue();
} }
TrackBookmark::pointer TrackBookmark::pointer TrackBookmark::find(Session& session, TrackBookmarkId id)
TrackBookmark::find(Session& session, TrackBookmarkId id) {
{ session.checkSharedLocked();
session.checkSharedLocked();
return session.getDboSession().find<TrackBookmark>() return session.getDboSession().find<TrackBookmark>()
.where("id = ?").bind(id) .where("id = ?").bind(id)
.resultValue(); .resultValue();
} }
} // namespace Database } // namespace Database
@@ -30,99 +30,92 @@
namespace Database { namespace Database {
TrackFeatures::TrackFeatures(ObjectPtr<Track> track, const std::string& jsonEncodedFeatures) TrackFeatures::TrackFeatures(ObjectPtr<Track> track, const std::string& jsonEncodedFeatures)
: _data {jsonEncodedFeatures}, : _data{ jsonEncodedFeatures },
_track {getDboPtr(track)} _track{ getDboPtr(track) }
{ {
} }
TrackFeatures::pointer TrackFeatures::pointer TrackFeatures::create(Session& session, ObjectPtr<Track> track, const std::string& jsonEncodedFeatures)
TrackFeatures::create(Session& session, ObjectPtr<Track> track, const std::string& jsonEncodedFeatures) {
{ return session.getDboSession().add(std::unique_ptr<TrackFeatures> {new TrackFeatures{ track, jsonEncodedFeatures }});
return session.getDboSession().add(std::unique_ptr<TrackFeatures> {new TrackFeatures {track, jsonEncodedFeatures}}); }
}
std::size_t std::size_t TrackFeatures::getCount(Session& session)
TrackFeatures::getCount(Session& session) {
{ session.checkSharedLocked();
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track_features"); return session.getDboSession().query<int>("SELECT COUNT(*) FROM track_features");
} }
TrackFeatures::pointer TrackFeatures::pointer TrackFeatures::find(Session& session, TrackFeaturesId id)
TrackFeatures::find(Session& session, TrackFeaturesId id) {
{ session.checkSharedLocked();
session.checkSharedLocked();
return session.getDboSession().find<TrackFeatures>() return session.getDboSession().find<TrackFeatures>()
.where("id = ?").bind(id) .where("id = ?").bind(id)
.resultValue(); .resultValue();
} }
TrackFeatures::pointer TrackFeatures::pointer TrackFeatures::find(Session& session, TrackId trackId)
TrackFeatures::find(Session& session, TrackId trackId) {
{ session.checkSharedLocked();
session.checkSharedLocked();
return session.getDboSession().find<TrackFeatures>() return session.getDboSession().find<TrackFeatures>()
.where("track_id = ?").bind(trackId) .where("track_id = ?").bind(trackId)
.resultValue(); .resultValue();
} }
RangeResults<TrackFeaturesId> RangeResults<TrackFeaturesId> TrackFeatures::find(Session& session, std::optional<Range> range)
TrackFeatures::find(Session& session, Range range) {
{ session.checkSharedLocked();
session.checkSharedLocked();
auto query {session.getDboSession().query<TrackFeaturesId>("SELECT id from track_features")}; auto query{ session.getDboSession().query<TrackFeaturesId>("SELECT id from track_features") };
return Utils::execQuery(query, range); return Utils::execQuery<TrackFeaturesId>(query, range);
} }
FeatureValues FeatureValues TrackFeatures::getFeatureValues(const FeatureName& featureNode) const
TrackFeatures::getFeatureValues(const FeatureName& featureNode) const {
{ FeatureValuesMap featuresValuesMap{ getFeatureValuesMap({featureNode}) };
FeatureValuesMap featuresValuesMap {getFeatureValuesMap({featureNode})}; return std::move(featuresValuesMap[featureNode]);
return std::move(featuresValuesMap[featureNode]); }
}
FeatureValuesMap FeatureValuesMap TrackFeatures::getFeatureValuesMap(const std::unordered_set<FeatureName>& featureNames) const
TrackFeatures::getFeatureValuesMap(const std::unordered_set<FeatureName>& featureNames) const {
{ FeatureValuesMap res;
FeatureValuesMap res;
try try
{ {
std::istringstream iss {_data}; std::istringstream iss{ _data };
boost::property_tree::ptree root; boost::property_tree::ptree root;
boost::property_tree::read_json(iss, root); boost::property_tree::read_json(iss, root);
for (const FeatureName& featureName : featureNames) for (const FeatureName& featureName : featureNames)
{ {
FeatureValues& featureValues {res[featureName]}; FeatureValues& featureValues{ res[featureName] };
auto node {root.get_child(featureName)}; auto node{ root.get_child(featureName) };
bool hasChildren = false; bool hasChildren = false;
for (const auto& child : node.get_child("")) for (const auto& child : node.get_child(""))
{ {
hasChildren = true; hasChildren = true;
featureValues.push_back(child.second.get_value<double>()); featureValues.push_back(child.second.get_value<double>());
} }
if (!hasChildren) if (!hasChildren)
featureValues.push_back(node.get_value<double>()); featureValues.push_back(node.get_value<double>());
} }
} }
catch (boost::property_tree::ptree_error& error) catch (boost::property_tree::ptree_error& error)
{ {
LMS_LOG(DB, ERROR) << "Track " << _track.id() << ": ptree exception: " << error.what(); LMS_LOG(DB, ERROR) << "Track " << _track.id() << ": ptree exception: " << error.what();
res.clear(); res.clear();
} }
return res; return res;
} }
} // namespace Database } // namespace Database
+293 -659
View File
@@ -33,665 +33,299 @@
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "Utils.hpp" #include "Utils.hpp"
namespace Database { namespace Database
TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> 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::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
} : _name{ name }
, _type{ type }
TrackList::pointer , _isPublic{ isPublic }
TrackList::create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user) , _creationDateTime{ Utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) }
{ , _lastModifiedDateTime{ Utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) }
return session.getDboSession().add(std::unique_ptr<TrackList> {new TrackList {name, type, isPublic, user}}); , _user{ getDboPtr(user) }
} {
assert(user);
std::size_t }
TrackList::getCount(Session& session)
{ TrackList::pointer TrackList::create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
session.checkSharedLocked(); {
return session.getDboSession().add(std::unique_ptr<TrackList> {new TrackList{ name, type, isPublic, user }});
return session.getDboSession().query<int>("SELECT COUNT(*) FROM tracklist"); }
}
std::size_t TrackList::getCount(Session& session)
{
TrackList::pointer session.checkSharedLocked();
TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId)
{ return session.getDboSession().query<int>("SELECT COUNT(*) FROM tracklist");
session.checkSharedLocked(); }
assert(userId.isValid());
return session.getDboSession().find<TrackList>() TrackList::pointer TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId)
.where("name = ?").bind(name) {
.where("type = ?").bind(type) session.checkSharedLocked();
.where("user_id = ?").bind(userId).resultValue(); assert(userId.isValid());
}
return session.getDboSession().find<TrackList>()
RangeResults<TrackListId> .where("name = ?").bind(name)
TrackList::find(Session& session, const FindParameters& params) .where("type = ?").bind(type)
{ .where("user_id = ?").bind(userId).resultValue();
session.checkSharedLocked(); }
auto query {session.getDboSession().query<TrackListId>("SELECT DISTINCT t_l.id FROM tracklist t_l")}; RangeResults<TrackListId> TrackList::find(Session& session, const FindParameters& params)
{
if (params.user.isValid()) session.checkSharedLocked();
query.where("t_l.user_id = ?").bind(params.user);
auto query{ session.getDboSession().query<TrackListId>("SELECT DISTINCT t_l.id FROM tracklist t_l") };
if (params.type)
query.where("t_l.type = ?").bind(*params.type); if (params.user.isValid())
query.where("t_l.user_id = ?").bind(params.user);
if (!params.clusters.empty())
{ if (params.type)
query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id"); query.where("t_l.type = ?").bind(*params.type);
query.join("track t ON t.id = t_l_e.track_id");
if (!params.clusters.empty())
std::ostringstream oss; {
oss << "t.id IN (SELECT DISTINCT t.id FROM track t" query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id");
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id" query.join("track t ON t.id = t_l_e.track_id");
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
std::ostringstream oss;
WhereClause clusterClause; oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
for (const ClusterId clusterId : params.clusters) " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
{ " INNER JOIN cluster c ON c.id = t_c.cluster_id";
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId); WhereClause clusterClause;
} for (const ClusterId clusterId : params.clusters)
{
oss << " " << clusterClause.get(); clusterClause.Or(WhereClause("c.id = ?"));
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")"; query.bind(clusterId);
}
query.where(oss.str());
} oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
switch (params.sortMethod)
{ query.where(oss.str());
case TrackListSortMethod::None: }
break;
case TrackListSortMethod::Name: switch (params.sortMethod)
query.orderBy("t_l.name COLLATE NOCASE"); {
break; case TrackListSortMethod::None:
case TrackListSortMethod::LastModifiedDesc: break;
query.orderBy("t_l.last_modified_date_time DESC"); case TrackListSortMethod::Name:
break; query.orderBy("t_l.name COLLATE NOCASE");
} break;
case TrackListSortMethod::LastModifiedDesc:
return Utils::execQuery(query, params.range); query.orderBy("t_l.last_modified_date_time DESC");
} break;
}
TrackList::pointer
TrackList::find(Session& session, TrackListId id) return Utils::execQuery<TrackListId>(query, params.range);
{ }
session.checkSharedLocked();
TrackList::pointer TrackList::find(Session& session, TrackListId id)
return session.getDboSession().find<TrackList>().where("id = ?").bind(id).resultValue(); {
} session.checkSharedLocked();
bool return session.getDboSession().find<TrackList>().where("id = ?").bind(id).resultValue();
TrackList::isEmpty() const }
{
return _entries.empty(); bool TrackList::isEmpty() const
} {
return _entries.empty();
std::size_t }
TrackList::getCount() const
{ std::size_t TrackList::getCount() const
return _entries.size(); {
} return _entries.size();
}
TrackListEntry::pointer
TrackList::getEntry(std::size_t pos) const TrackListEntry::pointer TrackList::getEntry(std::size_t pos) const
{ {
TrackListEntry::pointer res; TrackListEntry::pointer res;
auto entries = getEntries(Range {pos, 1}); auto entries = getEntries(Range{ pos, 1 });
if (!entries.empty()) if (!entries.empty())
res = entries.front(); res = entries.front();
return res; return res;
} }
std::vector<TrackListEntry::pointer> std::vector<TrackListEntry::pointer> TrackList::getEntries(std::optional<Range> range) const
TrackList::getEntries(std::optional<Range> range) const {
{ assert(session());
assert(session());
auto entries{
auto entries { session()->find<TrackListEntry>()
session()->find<TrackListEntry>() .where("tracklist_id = ?").bind(getId())
.where("tracklist_id = ?").bind(getId()) .orderBy("id")
.orderBy("id") .limit(range ? static_cast<int>(range->size) + 1 : -1)
.limit(range ? static_cast<int>(range->size) + 1 : -1) .offset(range ? static_cast<int>(range->offset) : -1)
.offset(range ? static_cast<int>(range->offset) : -1) .resultList() };
.resultList()};
return std::vector<TrackListEntry::pointer>(entries.begin(), entries.end());
return std::vector<TrackListEntry::pointer>(entries.begin(), entries.end()); }
}
TrackListEntry::pointer TrackList::getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const
TrackListEntry::pointer {
TrackList::getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const assert(session());
{
assert(session()); return session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(getId())
return session()->find<TrackListEntry>() .where("track_id = ?").bind(track->getId())
.where("tracklist_id = ?").bind(getId()) .where("date_time = ?").bind(Utils::normalizeDateTime(dateTime))
.where("track_id = ?").bind(track->getId()) .resultValue();
.where("date_time = ?").bind(Utils::normalizeDateTime(dateTime)) }
.resultValue();
} std::vector<Cluster::pointer> TrackList::getClusters() const
{
static assert(session());
Wt::Dbo::Query<Wt::Dbo::ptr<Artist>>
createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType) auto res{ session()->query<Wt::Dbo::ptr<Cluster>>("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())
auto query {session.query<Wt::Dbo::ptr<Artist>>(queryStr)}; .groupBy("c.id")
query.join("track t ON t.id = t_a_l.track_id"); .orderBy("COUNT(c.id) DESC")
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id"); .resultList() };
query.join("tracklist_entry p_e ON p_e.track_id = t.id");
query.join("tracklist p ON p.id = p_e.tracklist_id"); return std::vector<Cluster::pointer>(res.begin(), res.end());
}
query.where("p.id = ?").bind(tracklistId);
std::vector<std::vector<Cluster::pointer>> TrackList::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
if (linkType) {
query.where("t_a_l.type = ?").bind(*linkType); assert(session());
std::vector<std::vector<Cluster::pointer>> res;
if (!clusterIds.empty())
{ if (clusterTypes.empty())
std::ostringstream oss; return res;
oss << "a.id IN (SELECT DISTINCT a.id FROM artist a"
" INNER JOIN track t ON t.id = t_a_l.track_id" auto query{ session()->query<Wt::Dbo::ptr<Cluster>>("SELECT c from cluster c") };
" 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" query.join("track t ON c.id = t_c.cluster_id")
" INNER JOIN track_cluster t_c ON t_c.track_id = t.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")
WhereClause clusterClause; .join("tracklist_entry t_l_e ON t_l_e.track_id = t.id")
for (auto id : clusterIds) .join("tracklist t_l ON t_l.id = t_l_e.tracklist_id")
{ .where("t_l.id = ?").bind(getId());
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(id); {
} std::ostringstream oss;
oss << "c_type.id IN (";
oss << " " << clusterClause.get(); bool first{ true };
oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; for (auto clusterType : clusterTypes)
{
query.where(oss.str()); if (!first)
} oss << ", ";
oss << "?";
return query; query.bind(clusterType->getId());
} first = false;
}
static oss << ")";
Wt::Dbo::Query<Wt::Dbo::ptr<Release>> query.where(oss.str());
createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds) }
{ query.groupBy("c.id");
auto query {session.query<Wt::Dbo::ptr<Release>>(queryStr)}; query.orderBy("COUNT(c.id) DESC");
query.join("track t ON t.release_id = r.id");
query.join("tracklist_entry p_e ON p_e.track_id = t.id"); auto queryRes{ query.resultList() };
query.join("tracklist p ON p.id = p_e.tracklist_id");
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clustersByType;
query.where("p.id = ?").bind(tracklistId); for (const Wt::Dbo::ptr<Cluster>& cluster : queryRes)
{
if (!clusterIds.empty()) if (clustersByType[cluster->getType()->getId()].size() < size)
{ clustersByType[cluster->getType()->getId()].push_back(cluster);
std::ostringstream oss; }
oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON t.release_id = r.id" for (const auto& [clusterTypeId, clusters] : clustersByType)
" INNER JOIN cluster c ON c.id = t_c.cluster_id" res.push_back(clusters);
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
return res;
WhereClause clusterClause; }
for (ClusterId id : clusterIds)
{ bool TrackList::hasTrack(TrackId trackId) const
clusterClause.Or(WhereClause("c.id = ?")); {
query.bind(id); assert(session());
}
Wt::Dbo::collection<TrackListEntry::pointer> res = session()->query<TrackListEntry::pointer>("SELECT p_e from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id")
oss << " " << clusterClause.get(); .where("p_e.track_id = ?").bind(trackId)
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; .where("p.id = ?").bind(getId());
query.where(oss.str()); return res.size() > 0;
} }
return query; std::vector<Track::pointer> TrackList::getSimilarTracks(std::optional<std::size_t> offset, std::optional<std::size_t> size) const
} {
assert(session());
static
Wt::Dbo::Query<Wt::Dbo::ptr<Track>> auto res{ session()->query<Wt::Dbo::ptr<Track>>(
createTracksQuery(Wt::Dbo::Session& session, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds) "SELECT t FROM track t"
{ " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
auto query {session.query<Wt::Dbo::ptr<Track>>("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")}; " 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 = ?)"
query.where("p.id = ?").bind(tracklistId); " 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 = ?))"
)
if (!clusterIds.empty()) .bind(getId())
{ .bind(getId())
std::ostringstream oss; .groupBy("t.id")
oss << "t.id IN (SELECT DISTINCT t.id FROM track t" .orderBy("COUNT(*) DESC, RANDOM()")
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id" .limit(size ? static_cast<int>(*size) : -1)
" INNER JOIN cluster c ON c.id = t_c.cluster_id"; .offset(offset ? static_cast<int>(*offset) : -1)
.resultList() };
WhereClause clusterClause;
for (auto id : clusterIds) return std::vector<Track::pointer>(res.begin(), res.end());
{ }
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
query.bind(id); std::vector<TrackId> TrackList::getTrackIds() const
} {
assert(session());
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")"; Wt::Dbo::collection<TrackId> res = session()->query<TrackId>("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());
query.where(oss.str());
} return std::vector<TrackId>(res.begin(), res.end());
}
return query;
} std::chrono::milliseconds TrackList::getDuration() const
{
std::vector<Artist::pointer> assert(session());
TrackList::getArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, ArtistSortMethod sortMethod, std::optional<Range> range, bool& moreResults) const
{ using milli = std::chrono::duration<int, std::milli>;
assert(session());
Wt::Dbo::Query<milli> query{ session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id")
auto query {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType) .where("p_e.tracklist_id = ?").bind(getId()) };
.groupBy("a.id").having("p_e.date_time = MAX(p_e.date_time)")
.limit(range ? static_cast<int>(range->size) + 1 : -1) return query.resultValue();
.offset(range ? static_cast<int>(range->offset) : -1)}; }
switch (sortMethod) void TrackList::setLastModifiedDateTime(const Wt::WDateTime& dateTime)
{ {
case ArtistSortMethod::None: _lastModifiedDateTime = Utils::normalizeDateTime(dateTime);
break; }
case ArtistSortMethod::ByName:
query.orderBy("a.name COLLATE NOCASE"); TrackListEntry::TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime)
break; : _dateTime{ Utils::normalizeDateTime(dateTime) }
case ArtistSortMethod::BySortName: , _track{ getDboPtr(track) }
query.orderBy("a.sort_name COLLATE NOCASE"); , _tracklist{ getDboPtr(tracklist) }
break; {
case ArtistSortMethod::Random: assert(track);
query.orderBy("RANDOM()"); assert(tracklist);
break; }
case ArtistSortMethod::LastWritten:
case ArtistSortMethod::StarredDateDesc: TrackListEntry::pointer TrackListEntry::create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime)
assert(false); // Not implemented! {
break; return session.getDboSession().add(std::unique_ptr<TrackListEntry> {new TrackListEntry{ track, tracklist, dateTime }});
} }
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection {query.resultList()}; void TrackListEntry::onPostCreated()
{
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())}; _tracklist.modify()->setLastModifiedDateTime(Utils::normalizeDateTime(Wt::WDateTime::currentDateTime()));
if (range && res.size() == static_cast<std::size_t>(range->size) + 1) }
{
moreResults = true; void TrackListEntry::onPreRemove()
res.pop_back(); {
} _tracklist.modify()->setLastModifiedDateTime(Utils::normalizeDateTime(Wt::WDateTime::currentDateTime()));
else }
moreResults = false;
TrackListEntry::pointer TrackListEntry::getById(Session& session, TrackListEntryId id)
return res; {
} session.checkSharedLocked();
return session.getDboSession().find<TrackListEntry>().where("id = ?").bind(id).resultValue();
std::vector<ObjectPtr<Release>> }
TrackList::getReleases(const std::vector<ClusterId>& clusterIds, std::optional<Range> 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<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<ObjectPtr<Track>>
TrackList::getTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> 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<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Artist::pointer>
TrackList::getArtistsOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> 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<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Release::pointer>
TrackList::getReleasesOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<Range> 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<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Track::pointer>
TrackList::getTracksOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<Range> 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<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Cluster::pointer>
TrackList::getClusters() const
{
assert(session());
auto res {session()->query<Wt::Dbo::ptr<Cluster>>("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<Cluster::pointer>(res.begin(), res.end());
}
std::vector<std::vector<Cluster::pointer>>
TrackList::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
{
assert(session());
std::vector<std::vector<Cluster::pointer>> res;
if (clusterTypes.empty())
return res;
auto query {session()->query<Wt::Dbo::ptr<Cluster>>("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<ClusterTypeId, std::vector<Cluster::pointer>> clustersByType;
for (const Wt::Dbo::ptr<Cluster>& 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<TrackListEntry::pointer> res = session()->query<TrackListEntry::pointer>("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<Track::pointer>
TrackList::getSimilarTracks(std::optional<std::size_t> offset, std::optional<std::size_t> size) const
{
assert(session());
auto res {session()->query<Wt::Dbo::ptr<Track>>(
"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<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)
.resultList()};
return std::vector<Track::pointer>(res.begin(), res.end());
}
std::vector<TrackId>
TrackList::getTrackIds() const
{
assert(session());
Wt::Dbo::collection<TrackId> res = session()->query<TrackId>("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<TrackId>(res.begin(), res.end());
}
std::chrono::milliseconds
TrackList::getDuration() const
{
assert(session());
using milli = std::chrono::duration<int, std::milli>;
Wt::Dbo::Query<milli> query {session()->query<milli>("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<Artist::pointer>
TrackList::getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> 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<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Artist::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Release::pointer>
TrackList::getTopReleases(const std::vector<ClusterId>& clusterIds, std::optional<Range> 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<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Track::pointer>
TrackList::getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> 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<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
TrackListEntry::TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> 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> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime)
{
return session.getDboSession().add(std::unique_ptr<TrackListEntry> {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<TrackListEntry>().where("id = ?").bind(id).resultValue();
}
} // namespace Database } // namespace Database
+1 -1
View File
@@ -58,7 +58,7 @@ namespace Database {
if (params.feedbackBackend) if (params.feedbackBackend)
query.where("feedback_backend = ?").bind(*params.feedbackBackend); query.where("feedback_backend = ?").bind(*params.feedbackBackend);
return Utils::execQuery(query, params.range); return Utils::execQuery<UserId>(query, params.range);
} }
User::pointer User::findDemoUser(Session& session) User::pointer User::findDemoUser(Session& session)
+41 -43
View File
@@ -19,6 +19,7 @@
#pragma once #pragma once
#include <functional>
#include <string> #include <string>
#include <string_view> #include <string_view>
@@ -30,57 +31,54 @@
namespace Database::Utils namespace Database::Utils
{ {
#define ESCAPE_CHAR_STR "\\" #define ESCAPE_CHAR_STR "\\"
static inline constexpr char escapeChar {'\\'}; static inline constexpr char escapeChar{ '\\' };
std::string escapeLikeKeyword(std::string_view keywords); std::string escapeLikeKeyword(std::string_view keywords);
template <typename T> template <typename Query>
RangeResults<T> void applyRange(Query& query, std::optional<Range> range)
execQuery(Wt::Dbo::Query<T>& query, Range range) {
{ if (range)
RangeResults<T> res; {
query.limit(static_cast<int>(range->size));
query.offset(static_cast<int>(range->offset));
}
}
auto collection {query.limit(range.size ? static_cast<int>(range.size) + 1 : -1) template <typename ResultType, typename Query>
.offset(range.offset ? static_cast<int>(range.offset) : -1) RangeResults<ResultType> execQuery(Query& query, std::optional<Range> range)
.resultList()}; {
RangeResults<ResultType> res;
res.results.assign(collection.begin(), collection.end()); if (range)
if (range.size && res.results.size() == static_cast<std::size_t>(range.size) + 1) applyRange(query, Range{ range->offset, range->size + 1 });
{
res.moreResults = true;
res.results.pop_back();
}
else
res.moreResults = false;
res.range.offset = range.offset; auto collection{ query.resultList() };
res.range.size = res.results.size(); res.results.assign(collection.begin(), collection.end());
return res; if (range && res.results.size() == static_cast<std::size_t>(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 <typename T> res.range.offset = range->offset;
RangeResults<typename T::pointer> res.range.size = res.results.size();
execQuery(Wt::Dbo::Query<Wt::Dbo::ptr<T>>& query, Range range)
{
RangeResults<typename T::pointer> res;
auto collection {query.limit(range.size ? static_cast<int>(range.size) + 1 : -1) return res;
.offset(range.offset ? static_cast<int>(range.offset) : -1) }
.resultList()};
res.results.assign(collection.begin(), collection.end()); template <typename ResultType, typename Query>
if (range.size && res.results.size() == static_cast<std::size_t>(range.size) + 1) void execQuery(Query& query, std::optional<Range> range, std::function<void(const ResultType&)> func)
{ {
res.moreResults = true; if (range)
res.results.pop_back(); applyRange(query, range);
}
else
res.moreResults = false;
res.range.offset = range.offset; for (const auto& res : query.resultList())
res.range.size = res.results.size(); func(res);
return res; }
}
Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime);
} // namespace Database::Utils } // namespace Database::Utils
@@ -58,7 +58,7 @@ namespace Database
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field) std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field)
std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
ArtistSortMethod sortMethod{ ArtistSortMethod::None }; ArtistSortMethod sortMethod{ ArtistSortMethod::None };
Range range; std::optional<Range> range;
Wt::WDateTime writtenAfter; Wt::WDateTime writtenAfter;
UserId starringUser; // only artists starred by this user UserId starringUser; // only artists starred by this user
std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend
@@ -69,7 +69,7 @@ namespace Database
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; } FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
FindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; } FindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; }
FindParameters& setSortMethod(ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; 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 = _range; return *this; }
FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; 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& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; }
FindParameters& setTrack(TrackId _track) { track = _track; return *this; } FindParameters& setTrack(TrackId _track) { track = _track; return *this; }
@@ -84,8 +84,9 @@ namespace Database
static pointer find(Session& session, ArtistId id); static pointer find(Session& session, ArtistId id);
static std::vector<pointer> find(Session& session, const std::string& name); // exact match on name field static std::vector<pointer> find(Session& session, const std::string& name); // exact match on name field
static RangeResults<pointer> find(Session& session, const FindParameters& parameters); static RangeResults<pointer> find(Session& session, const FindParameters& parameters);
static void find(Session& session, const FindParameters& parameters, std::function<void(const pointer&)> func);
static RangeResults<ArtistId> findIds(Session& session, const FindParameters& parameters); static RangeResults<ArtistId> findIds(Session& session, const FindParameters& parameters);
static RangeResults<ArtistId> findOrphanIds(Session& session, Range range); // No track related static RangeResults<ArtistId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt); // No track related
static bool exists(Session& session, ArtistId id); static bool exists(Session& session, ArtistId id);
// Accessors // Accessors
@@ -94,7 +95,7 @@ namespace Database
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); } std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
// No artistLinkTypes means get them all // No artistLinkTypes means get them all
RangeResults<ArtistId> findSimilarArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes = {}, Range range = {}) const; RangeResults<ArtistId> findSimilarArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes = {}, std::optional<Range> range = std::nullopt) const;
// Get the cluster of the tracks made by this artist // Get the cluster of the tracks made by this artist
// Each clusters are grouped by cluster type, sorted by the number of occurence // Each clusters are grouped by cluster type, sorted by the number of occurence
@@ -19,6 +19,7 @@
#pragma once #pragma once
#include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
@@ -45,12 +46,12 @@ namespace Database {
public: public:
struct FindParameters struct FindParameters
{ {
Range range; std::optional<Range> range;
ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type
TrackId track; // if set, clusters involved in this track TrackId track; // if set, clusters involved in this track
ReleaseId release; // if set, clusters involved in this release ReleaseId release; // if set, clusters involved in this release
FindParameters& setRange(Range _range) { range = _range; return *this; } FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
FindParameters& setClusterType(ClusterTypeId _clusterType) { clusterType = _clusterType; return *this; } FindParameters& setClusterType(ClusterTypeId _clusterType) { clusterType = _clusterType; return *this; }
FindParameters& setTrack(TrackId _track) { track = _track; return *this; } FindParameters& setTrack(TrackId _track) { track = _track; return *this; }
FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; }
@@ -60,11 +61,11 @@ namespace Database {
// Find utility // Find utility
static std::size_t getCount(Session& session); static std::size_t getCount(Session& session);
static RangeResults<ClusterId> findIds(Session& session, const FindParameters& range); static RangeResults<ClusterId> findIds(Session& session, const FindParameters& params);
static RangeResults<pointer> find(Session& session, const FindParameters& range); static RangeResults<pointer> find(Session& session, const FindParameters& params);
static void find(Session& session, const FindParameters& range, std::function<void(const pointer& cluster)> _func); static void find(Session& session, const FindParameters& params, std::function<void(const pointer& cluster)> _func);
static pointer find(Session& session, ClusterId id); static pointer find(Session& session, ClusterId id);
static RangeResults<ClusterId> findOrphans(Session& session, Range range); static RangeResults<ClusterId> findOrphans(Session& session, std::optional<Range> range = std::nullopt);
// May be very slow // May be very slow
static std::size_t computeTrackCount(Session& session, ClusterId id); static std::size_t computeTrackCount(Session& session, ClusterId id);
@@ -74,7 +75,7 @@ namespace Database {
std::string_view getName() const { return _name; } std::string_view getName() const { return _name; }
ObjectPtr<ClusterType> getType() const { return _clusterType; } ObjectPtr<ClusterType> getType() const { return _clusterType; }
std::size_t getTracksCount() const { return _trackCount; } std::size_t getTracksCount() const { return _trackCount; }
RangeResults<TrackId> getTracks(Range range) const; RangeResults<TrackId> getTracks(std::optional<Range> range = std::nullopt) const;
std::size_t getReleasesCount() const { return _releaseCount; }; std::size_t getReleasesCount() const { return _releaseCount; };
void setReleaseCount(std::size_t releaseCount) { _releaseCount = releaseCount; } void setReleaseCount(std::size_t releaseCount) { _releaseCount = releaseCount; }
@@ -116,11 +117,11 @@ namespace Database {
// Getters // Getters
static std::size_t getCount(Session& session); static std::size_t getCount(Session& session);
static RangeResults<ClusterTypeId> find(Session& session, Range range); static RangeResults<ClusterTypeId> find(Session& session, std::optional<Range> range = std::nullopt);
static pointer find(Session& session, std::string_view name); static pointer find(Session& session, std::string_view name);
static pointer find(Session& session, ClusterTypeId id); static pointer find(Session& session, ClusterTypeId id);
static RangeResults<ClusterTypeId> findOrphans(Session& session, Range range); static RangeResults<ClusterTypeId> findOrphans(Session& session, std::optional<Range> range = std::nullopt);
static RangeResults<ClusterTypeId> findUsed(Session& session, Range range); static RangeResults<ClusterTypeId> findUsed(Session& session, std::optional<Range> range = std::nullopt);
static void remove(Session& session, const std::string& name); static void remove(Session& session, const std::string& name);
@@ -19,6 +19,8 @@
#pragma once #pragma once
#include <optional>
#include <Wt/Dbo/Dbo.h> #include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h> #include <Wt/WDateTime.h>
@@ -45,10 +47,10 @@ namespace Database
struct FindParameters struct FindParameters
{ {
UserId user; UserId user;
std::optional<ScrobblingBackend> backend; std::optional<ScrobblingBackend> backend;
std::optional<SyncState> syncState; std::optional<SyncState> syncState;
Range range; std::optional<Range> range;
FindParameters& setUser(UserId _user) { user = _user; return *this; } FindParameters& setUser(UserId _user) { user = _user; return *this; }
FindParameters& setScrobblingBackend(ScrobblingBackend _backend) { backend = _backend; return *this; } FindParameters& setScrobblingBackend(ScrobblingBackend _backend) { backend = _backend; return *this; }
@@ -63,13 +65,13 @@ namespace Database
static RangeResults<ListenId> find(Session& session, const FindParameters& parameters); static RangeResults<ListenId> find(Session& session, const FindParameters& parameters);
// Stats // Stats
static RangeResults<ArtistId> getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range = {}); static RangeResults<ArtistId> getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range = std::nullopt);
static RangeResults<ReleaseId> getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range = {}); static RangeResults<ReleaseId> getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range = std::nullopt);
static RangeResults<TrackId> getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range = {}); static RangeResults<TrackId> getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range = std::nullopt);
static RangeResults<ArtistId> getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range = {}); static RangeResults<ArtistId> getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range = std::nullopt);
static RangeResults<ReleaseId> getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range = {}); static RangeResults<ReleaseId> getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range = std::nullopt);
static RangeResults<TrackId> getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, Range range = {}); static RangeResults<TrackId> getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> 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, TrackId trackId);
static std::size_t getCount(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId trackId); static std::size_t getCount(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId trackId);
@@ -53,7 +53,7 @@ namespace Database
std::vector<ClusterId> clusters; // if non empty, releases that belong to these clusters std::vector<ClusterId> clusters; // if non empty, releases that belong to these clusters
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords
ReleaseSortMethod sortMethod{ ReleaseSortMethod::None }; ReleaseSortMethod sortMethod{ ReleaseSortMethod::None };
Range range; std::optional<Range> range;
Wt::WDateTime writtenAfter; Wt::WDateTime writtenAfter;
std::optional<DateRange> dateRange; std::optional<DateRange> dateRange;
UserId starringUser; // only releases starred by this user UserId starringUser; // only releases starred by this user
@@ -67,7 +67,7 @@ namespace Database
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; } FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; } FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
FindParameters& setSortMethod(ReleaseSortMethod _sortMethod) { sortMethod = _sortMethod; 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 = _range; return *this; }
FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; }
FindParameters& setDateRange(const std::optional<DateRange>& _dateRange) { dateRange = _dateRange; return *this; } FindParameters& setDateRange(const std::optional<DateRange>& _dateRange) { dateRange = _dateRange; return *this; }
FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; }
@@ -89,9 +89,10 @@ namespace Database
static std::vector<pointer> find(Session& session, const std::string& name); static std::vector<pointer> find(Session& session, const std::string& name);
static pointer find(Session& session, ReleaseId id); static pointer find(Session& session, ReleaseId id);
static RangeResults<pointer> find(Session& session, const FindParameters& parameters); static RangeResults<pointer> find(Session& session, const FindParameters& parameters);
static void find(Session& session, const FindParameters& parameters, std::function<void(const pointer&)> func);
static RangeResults<ReleaseId> findIds(Session& session, const FindParameters& parameters); static RangeResults<ReleaseId> findIds(Session& session, const FindParameters& parameters);
static RangeResults<ReleaseId> findOrphanIds(Session& session, Range range); // not track related static RangeResults<ReleaseId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt); // not track related
static RangeResults<ReleaseId> findIdsOrderedByArtist(Session& session, Range range); static RangeResults<ReleaseId> findIdsOrderedByArtist(Session& session, std::optional<Range> range = std::nullopt);
// Get the cluster of the tracks that belong to this release // 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) // Each clusters are grouped by cluster type, sorted by the number of occurence (max to min)
@@ -45,11 +45,11 @@ namespace Database
std::optional<FeedbackBackend> backend; // for this backend std::optional<FeedbackBackend> backend; // for this backend
std::optional<SyncState> syncState; // and these states std::optional<SyncState> syncState; // and these states
UserId user; // and this user UserId user; // and this user
Range range; std::optional<Range> range;
FindParameters& setFeedbackBackend(FeedbackBackend _backend, SyncState _syncState) { backend = _backend; syncState = _syncState; return *this; } FindParameters& setFeedbackBackend(FeedbackBackend _backend, SyncState _syncState) { backend = _backend; syncState = _syncState; return *this; }
FindParameters& setUser(UserId _user) { user = _user; 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 = _range; return *this; }
}; };
// Search utility // Search utility
@@ -46,207 +46,208 @@
namespace Database { namespace Database {
class Artist; class Artist;
class Cluster; class Cluster;
class ClusterType; class ClusterType;
class Release; class Release;
class Session; class Session;
class TrackArtistLink; class TrackArtistLink;
class TrackStats; class TrackStats;
class User; class User;
class Track final : public Object<Track, TrackId> class Track final : public Object<Track, TrackId>
{ {
public: public:
struct FindParameters struct FindParameters
{ {
std::vector<ClusterId> clusters; // if non empty, tracks that belong to these clusters std::vector<ClusterId> clusters; // if non empty, tracks that belong to these clusters
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords
std::string name; // if non empty, must match this name std::string name; // if non empty, must match this name
TrackSortMethod sortMethod {TrackSortMethod::None}; TrackSortMethod sortMethod{ TrackSortMethod::None };
Range range; std::optional<Range> range;
Wt::WDateTime writtenAfter; Wt::WDateTime writtenAfter;
UserId starringUser; // only tracks starred by this user UserId starringUser; // only tracks starred by this user
std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend
ArtistId artist; // only tracks that involve this artist ArtistId artist; // only tracks that involve this artist
std::string artistName; // only tracks that involve this artist name std::string artistName; // only tracks that involve this artist name
EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
bool nonRelease {}; // only tracks that do not belong to a release bool nonRelease{}; // only tracks that do not belong to a release
ReleaseId release; // matching this release ReleaseId release; // matching this release
std::string releaseName; // matching this release name std::string releaseName; // matching this release name
TrackListId trackList; // matching this trackList TrackListId trackList; // matching this trackList
std::optional<int> trackNumber; // matching this track number std::optional<int> trackNumber; // matching this track number
bool distinct {true}; bool distinct{ true };
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; } FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; } FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
FindParameters& setName(std::string_view _name) { name = _name; return *this; } FindParameters& setName(std::string_view _name) { name = _name; return *this; }
FindParameters& setSortMethod(TrackSortMethod _method) { sortMethod = _method; return *this; } FindParameters& setSortMethod(TrackSortMethod _method) { sortMethod = _method; return *this; }
FindParameters& setRange(Range _range) { range = _range; return *this; } FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; 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& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; }
FindParameters& setArtist(ArtistId _artist, EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } FindParameters& setArtist(ArtistId _artist, EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; }
FindParameters& setArtistName(std::string_view _artistName, EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}) { artistName = _artistName; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } FindParameters& setArtistName(std::string_view _artistName, EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}) { artistName = _artistName; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; }
FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; } FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; }
FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; }
FindParameters& setReleaseName(std::string_view _releaseName) { releaseName = _releaseName; return *this; } FindParameters& setReleaseName(std::string_view _releaseName) { releaseName = _releaseName; return *this; }
FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; } FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; }
FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; } FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; }
FindParameters& setDistinct(bool _distinct) { distinct = _distinct; return *this; } FindParameters& setDistinct(bool _distinct) { distinct = _distinct; return *this; }
}; };
struct PathResult struct PathResult
{ {
TrackId trackId; TrackId trackId;
std::filesystem::path path; std::filesystem::path path;
}; };
Track() = default; Track() = default;
// Find utility functions // Find utility functions
static std::size_t getCount(Session& session); static std::size_t getCount(Session& session);
static pointer findByPath(Session& session, const std::filesystem::path& p); static pointer findByPath(Session& session, const std::filesystem::path& p);
static pointer find(Session& session, TrackId id); static pointer find(Session& session, TrackId id);
static bool exists(Session& session, TrackId id); static bool exists(Session& session, TrackId id);
static std::vector<pointer> findByRecordingMBID(Session& session, const UUID& MBID); static std::vector<pointer> findByRecordingMBID(Session& session, const UUID& MBID);
static std::vector<pointer> findByMBID(Session& session, const UUID& MBID); static std::vector<pointer> findByMBID(Session& session, const UUID& MBID);
static RangeResults<TrackId> findSimilarTrackIds(Session& session, const std::vector<TrackId>& trackIds, Range range); static RangeResults<TrackId> findSimilarTrackIds(Session& session, const std::vector<TrackId>& trackIds, std::optional<Range> range = std::nullopt);
static RangeResults<TrackId> findIds(Session& session, const FindParameters& parameters); static RangeResults<TrackId> findIds(Session& session, const FindParameters& parameters);
static RangeResults<pointer> find(Session& session, const FindParameters& parameters); static RangeResults<pointer> find(Session& session, const FindParameters& parameters);
static RangeResults<PathResult> findPaths(Session& session, Range range); static void find(Session& session, const FindParameters& parameters, std::function<void(const Track::pointer&)> func);
static RangeResults<TrackId> findIdsTrackMBIDDuplicates(Session& session, Range range); static RangeResults<PathResult> findPaths(Session& session, std::optional<Range> range = std::nullopt);
static RangeResults<TrackId> findIdsWithRecordingMBIDAndMissingFeatures(Session& session, Range range); static RangeResults<TrackId> findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range = std::nullopt);
static RangeResults<TrackId> findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional<Range> range = std::nullopt);
// Accessors // Accessors
void setScanVersion(std::size_t version) { _scanVersion = version; } void setScanVersion(std::size_t version) { _scanVersion = version; }
void setTrackNumber(std::optional<int> num) { _trackNumber = num; } void setTrackNumber(std::optional<int> num) { _trackNumber = num; }
void setDiscNumber(std::optional<int> num) { _discNumber = num; } void setDiscNumber(std::optional<int> num) { _discNumber = num; }
void setTotalTrack(std::optional<int> totalTrack) { _totalTrack = totalTrack; } void setTotalTrack(std::optional<int> totalTrack) { _totalTrack = totalTrack; }
void setDiscSubtitle(const std::string& name) { _discSubtitle = name; } void setDiscSubtitle(const std::string& name) { _discSubtitle = name; }
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); } void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
void setPath(const std::filesystem::path& filePath) { _filePath = filePath; } void setPath(const std::filesystem::path& filePath) { _filePath = filePath; }
void setDuration(std::chrono::milliseconds duration) { _duration = duration; } void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; } void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
void setAddedTime(Wt::WDateTime time) { _fileAdded = time; } void setAddedTime(Wt::WDateTime time) { _fileAdded = time; }
void setDate(const Wt::WDate& date) { _date = date; } void setDate(const Wt::WDate& date) { _date = date; }
void setOriginalDate(const Wt::WDate& date) { _originalDate = date; } void setOriginalDate(const Wt::WDate& date) { _originalDate = date; }
void setHasCover(bool hasCover) { _hasCover = hasCover; } void setHasCover(bool hasCover) { _hasCover = hasCover; }
void setTrackMBID(const std::optional<UUID>& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; } void setTrackMBID(const std::optional<UUID>& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; }
void setRecordingMBID(const std::optional<UUID>& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; } void setRecordingMBID(const std::optional<UUID>& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; }
void setCopyright(const std::string& copyright) { _copyright = std::string(copyright, 0, _maxCopyrightLength); } 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 setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; } void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; }
void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc! void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc!
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; } void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
void clearArtistLinks(); void clearArtistLinks();
void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink); void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink);
void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); } void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); }
void setClusters(const std::vector<ObjectPtr<Cluster>>& clusters ); void setClusters(const std::vector<ObjectPtr<Cluster>>& clusters);
std::size_t getScanVersion() const { return _scanVersion; } std::size_t getScanVersion() const { return _scanVersion; }
std::optional<std::size_t> getTrackNumber() const { return _trackNumber; } std::optional<std::size_t> getTrackNumber() const { return _trackNumber; }
std::optional<std::size_t> getTotalTrack() const { return _totalTrack; } std::optional<std::size_t> getTotalTrack() const { return _totalTrack; }
std::optional<std::size_t> getDiscNumber() const { return _discNumber; } std::optional<std::size_t> getDiscNumber() const { return _discNumber; }
const std::string& getDiscSubtitle() const { return _discSubtitle; } const std::string& getDiscSubtitle() const { return _discSubtitle; }
std::string getName() const { return _name; } std::string getName() const { return _name; }
std::filesystem::path getPath() const { return _filePath; } std::filesystem::path getPath() const { return _filePath; }
std::chrono::milliseconds getDuration() const { return _duration; } std::chrono::milliseconds getDuration() const { return _duration; }
const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; } const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; }
std::optional<int> getYear() const; std::optional<int> getYear() const;
std::optional<int> getOriginalYear() const; std::optional<int> getOriginalYear() const;
Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; } Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; }
Wt::WDateTime getAddedTime() const { return _fileAdded; } Wt::WDateTime getAddedTime() const { return _fileAdded; }
bool hasCover() const { return _hasCover; } bool hasCover() const { return _hasCover; }
std::optional<UUID> getTrackMBID() const { return UUID::fromString(_trackMBID); } std::optional<UUID> getTrackMBID() const { return UUID::fromString(_trackMBID); }
std::optional<UUID> getRecordingMBID() const { return UUID::fromString(_recordingMBID); } std::optional<UUID> getRecordingMBID() const { return UUID::fromString(_recordingMBID); }
std::optional<std::string> getCopyright() const; std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const; std::optional<std::string> getCopyrightURL() const;
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; } std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; } std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
std::string_view getArtistDisplayName() const { return _artistDisplayName; } std::string_view getArtistDisplayName() const { return _artistDisplayName; }
// no artistLinkTypes means get all // no artistLinkTypes means get all
std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const; std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const;
ObjectPtr<Release> getRelease() const { return _release; } ObjectPtr<Release> getRelease() const { return _release; }
std::vector<ObjectPtr<Cluster>> getClusters() const; std::vector<ObjectPtr<Cluster>> getClusters() const;
std::vector<ClusterId> getClusterIds() const; std::vector<ClusterId> getClusterIds() const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const; std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
template<class Action> template<class Action>
void persist(Action& a) void persist(Action& a)
{ {
Wt::Dbo::field(a, _scanVersion, "scan_version"); Wt::Dbo::field(a, _scanVersion, "scan_version");
Wt::Dbo::field(a, _trackNumber, "track_number"); Wt::Dbo::field(a, _trackNumber, "track_number");
Wt::Dbo::field(a, _discNumber, "disc_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, _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, _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, _name, "name");
Wt::Dbo::field(a, _duration, "duration"); Wt::Dbo::field(a, _duration, "duration");
Wt::Dbo::field(a, _date, "date"); Wt::Dbo::field(a, _date, "date");
Wt::Dbo::field(a, _originalDate, "original_date"); Wt::Dbo::field(a, _originalDate, "original_date");
Wt::Dbo::field(a, _filePath, "file_path"); Wt::Dbo::field(a, _filePath, "file_path");
Wt::Dbo::field(a, _fileLastWrite, "file_last_write"); Wt::Dbo::field(a, _fileLastWrite, "file_last_write");
Wt::Dbo::field(a, _fileAdded, "file_added"); Wt::Dbo::field(a, _fileAdded, "file_added");
Wt::Dbo::field(a, _hasCover, "has_cover"); Wt::Dbo::field(a, _hasCover, "has_cover");
Wt::Dbo::field(a, _trackMBID, "mbid"); Wt::Dbo::field(a, _trackMBID, "mbid");
Wt::Dbo::field(a, _recordingMBID, "recording_mbid"); Wt::Dbo::field(a, _recordingMBID, "recording_mbid");
Wt::Dbo::field(a, _copyright, "copyright"); Wt::Dbo::field(a, _copyright, "copyright");
Wt::Dbo::field(a, _copyrightURL, "copyright_url"); Wt::Dbo::field(a, _copyrightURL, "copyright_url");
Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain"); 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, _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::field(a, _artistDisplayName, "artist_display_name");
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track"); Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
} }
private: private:
friend class ::Database::Session; friend class ::Database::Session;
Track(const std::filesystem::path& p); Track(const std::filesystem::path& p);
static pointer create(Session& session, 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 _maxNameLength{ 128 };
static constexpr std::size_t _maxCopyrightLength {128}; static constexpr std::size_t _maxCopyrightLength{ 128 };
static constexpr std::size_t _maxCopyrightURLLength {128}; static constexpr std::size_t _maxCopyrightURLLength{ 128 };
int _scanVersion {}; int _scanVersion{};
std::optional<int> _trackNumber {}; std::optional<int> _trackNumber{};
std::optional<int> _discNumber {}; std::optional<int> _discNumber{};
std::optional<int> _totalTrack {}; std::optional<int> _totalTrack{};
std::string _discSubtitle; std::string _discSubtitle;
std::string _name; std::string _name;
std::chrono::duration<int, std::milli> _duration {}; std::chrono::duration<int, std::milli> _duration{};
Wt::WDate _date; Wt::WDate _date;
Wt::WDate _originalDate; Wt::WDate _originalDate;
std::string _filePath; std::string _filePath;
Wt::WDateTime _fileLastWrite; Wt::WDateTime _fileLastWrite;
Wt::WDateTime _fileAdded; Wt::WDateTime _fileAdded;
bool _hasCover {}; bool _hasCover{};
std::string _trackMBID; std::string _trackMBID;
std::string _recordingMBID; std::string _recordingMBID;
std::string _copyright; std::string _copyright;
std::string _copyrightURL; std::string _copyrightURL;
std::optional<float> _trackReplayGain; std::optional<float> _trackReplayGain;
std::optional<float> _releaseReplayGain; std::optional<float> _releaseReplayGain;
std::string _artistDisplayName; std::string _artistDisplayName;
Wt::Dbo::ptr<Release> _release; Wt::Dbo::ptr<Release> _release;
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks; Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> _clusters; Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> _clusters;
}; };
namespace Debug namespace Debug
{ {
struct TrackInfo struct TrackInfo
{ {
Session& session; Session& session;
TrackId trackId; TrackId trackId;
}; };
std::ostream& operator<<(std::ostream& os, const TrackInfo& trackInfo); std::ostream& operator<<(std::ostream& os, const TrackInfo& trackInfo);
} }
} // namespace database } // namespace database
@@ -19,6 +19,7 @@
#pragma once #pragma once
#include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
@@ -45,13 +46,13 @@ namespace Database
public: public:
struct FindParameters struct FindParameters
{ {
Range range; std::optional<Range> range;
std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type std::optional<TrackArtistLinkType> 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 ArtistId artist; // if set, links involved with this artist
ReleaseId release; // if set, artists involved in this release ReleaseId release; // if set, artists involved in this release
TrackId track; // if set, artists involved in this track TrackId track; // if set, artists involved in this track
FindParameters& setRange(Range _range) { range = _range; return *this; } FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
FindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; } FindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; }
FindParameters& setArtist(ArtistId _artist) { artist = _artist; return *this; } FindParameters& setArtist(ArtistId _artist) { artist = _artist; return *this; }
FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; }
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <optional>
#include <Wt/Dbo/Dbo.h> #include <Wt/Dbo/Dbo.h>
@@ -45,7 +46,7 @@ class TrackBookmark final : public Object<TrackBookmark, TrackBookmarkId>
// Find utility functions // Find utility functions
static std::size_t getCount(Session& session); static std::size_t getCount(Session& session);
static pointer find(Session& session, TrackBookmarkId id); static pointer find(Session& session, TrackBookmarkId id);
static RangeResults<TrackBookmarkId> find(Session& session, UserId userId, Range range); static RangeResults<TrackBookmarkId> find(Session& session, UserId userId, std::optional<Range> range = std::nullopt);
static pointer find(Session& session, UserId userId, TrackId trackId); static pointer find(Session& session, UserId userId, TrackId trackId);
// Setters // Setters
@@ -19,6 +19,7 @@
#pragma once #pragma once
#include <optional>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
@@ -51,7 +52,7 @@ class TrackFeatures final : public Object<TrackFeatures, TrackFeaturesId>
static std::size_t getCount(Session& session); static std::size_t getCount(Session& session);
static pointer find(Session& session, TrackFeaturesId id); static pointer find(Session& session, TrackFeaturesId id);
static pointer find(Session& session, TrackId trackId); static pointer find(Session& session, TrackId trackId);
static RangeResults<TrackFeaturesId> find(Session& session, Range range); static RangeResults<TrackFeaturesId> find(Session& session, std::optional<Range> range = std::nullopt);
FeatureValues getFeatureValues(const FeatureName& feature) const; FeatureValues getFeatureValues(const FeatureName& feature) const;
FeatureValuesMap getFeatureValuesMap(const std::unordered_set<FeatureName>& featureNames) const; FeatureValuesMap getFeatureValuesMap(const std::unordered_set<FeatureName>& featureNames) const;
@@ -36,151 +36,141 @@
namespace Database { namespace Database {
class Artist; class Artist;
class Cluster; class Cluster;
class ClusterType; class ClusterType;
class Release; class Release;
class Session; class Session;
class Track; class Track;
class TrackListEntry; class TrackListEntry;
class User; class User;
class TrackList final : public Object<TrackList, TrackListId> class TrackList final : public Object<TrackList, TrackListId>
{ {
public: public:
TrackList() = default; TrackList() = default;
// Stats utility // Search utility
std::vector<ObjectPtr<Artist>> getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const; struct FindParameters
std::vector<ObjectPtr<Release>> getTopReleases(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const; {
std::vector<ObjectPtr<Track>> getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const; std::vector<ClusterId> clusters; // if non empty, tracklists that have tracks that belong to these clusters
std::optional<Range> range;
std::optional<TrackListType> type;
UserId user; // only tracklists owned by this user
TrackListSortMethod sortMethod{ TrackListSortMethod::None };
// Search utility FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
struct FindParameters FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
{ FindParameters& setType(TrackListType _type) { type = _type; return *this; }
std::vector<ClusterId> clusters; // if non empty, tracklists that have tracks that belong to these clusters FindParameters& setUser(UserId _user) { user = _user; return *this; }
Range range; FindParameters& setSortMethod(TrackListSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
std::optional<TrackListType> type; };
UserId user; // only tracklists owned by this user static std::size_t getCount(Session& session);
TrackListSortMethod sortMethod {TrackListSortMethod::None}; static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId);
static pointer find(Session& session, TrackListId tracklistId);
static RangeResults<TrackListId> find(Session& session, const FindParameters& params);
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; } // Accessors
FindParameters& setRange(Range _range) { range = _range; return *this; } std::string_view getName() const { return _name; }
FindParameters& setType(TrackListType _type) { type = _type; return *this; } bool isPublic() const { return _isPublic; }
FindParameters& setUser(UserId _user) { user = _user; return *this; } TrackListType getType() const { return _type; }
FindParameters& setSortMethod(TrackListSortMethod _sortMethod) {sortMethod = _sortMethod; return *this; } ObjectPtr<User> getUser() const { return _user; }
};
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<TrackListId> find(Session& session, const FindParameters& params);
// Accessors // Modifiers
std::string_view getName() const { return _name; } void setName(const std::string& name) { _name = name; }
bool isPublic() const { return _isPublic; } void setIsPublic(bool isPublic) { _isPublic = isPublic; }
TrackListType getType() const { return _type; } void clear() { _entries.clear(); }
ObjectPtr<User> getUser() const { return _user; }
// Modifiers // Get tracks, ordered by position
void setName(const std::string& name) { _name = name; } bool isEmpty() const;
void setIsPublic(bool isPublic) { _isPublic = isPublic; } std::size_t getCount() const;
void clear() { _entries.clear(); } ObjectPtr<TrackListEntry> getEntry(std::size_t pos) const;
std::vector<ObjectPtr<TrackListEntry>> getEntries(std::optional<Range> range = {}) const;
ObjectPtr<TrackListEntry> getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const;
// Get tracks, ordered by position RangeResults<ObjectPtr<Artist>> getArtists(const std::vector<ClusterId>& clusters, std::optional<TrackArtistLinkType> linkType, ArtistSortMethod sortMethod, std::optional<Range> range, bool& moreResults) const;
bool isEmpty() const; RangeResults<ObjectPtr<Release>> getReleases(const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults) const;
std::size_t getCount() const; RangeResults<ObjectPtr<Track>> getTracks(const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults) const;
ObjectPtr<TrackListEntry> getEntry(std::size_t pos) const;
std::vector<ObjectPtr<TrackListEntry>> getEntries(std::optional<Range> range = {}) const;
ObjectPtr<TrackListEntry> getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const;
std::vector<ObjectPtr<Artist>> getArtists(const std::vector<ClusterId>& clusters, std::optional<TrackArtistLinkType> linkType, ArtistSortMethod sortMethod, std::optional<Range> range, bool& moreResults) const; std::vector<TrackId> getTrackIds() const;
std::vector<ObjectPtr<Release>> getReleases(const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults) const; std::chrono::milliseconds getDuration() const;
std::vector<ObjectPtr<Track>> getTracks(const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults) const;
// Sorted by date time void setLastModifiedDateTime(const Wt::WDateTime& dateTime);
std::vector<ObjectPtr<Artist>> getArtistsOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Release>> getReleasesOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Track>> getTracksOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<TrackId> getTrackIds() const; // Get clusters, order by occurence
std::chrono::milliseconds getDuration() const; std::vector<ObjectPtr<Cluster>> getClusters() const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
void setLastModifiedDateTime(const Wt::WDateTime& dateTime); bool hasTrack(TrackId trackId) const;
// Get clusters, order by occurence // Ordered from most clusters in common
std::vector<ObjectPtr<Cluster>> getClusters() const; std::vector<ObjectPtr<Track>> getSimilarTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
bool hasTrack(TrackId trackId) const; template<class Action>
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 Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade);
std::vector<ObjectPtr<Track>> getSimilarTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const; Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "tracklist");
}
template<class Action> private:
void persist(Action& a) friend class Session;
{ TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
Wt::Dbo::field(a, _name, "name"); static pointer create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
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");
Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); std::string _name;
Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "tracklist"); TrackListType _type{ TrackListType::Playlist };
} bool _isPublic{ false };
Wt::WDateTime _creationDateTime;
Wt::WDateTime _lastModifiedDateTime;
private: Wt::Dbo::ptr<User> _user;
friend class Session; Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> _entries;
TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user); };
static pointer create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
std::string _name; class TrackListEntry final : public Object<TrackListEntry, TrackListEntryId>
TrackListType _type {TrackListType::Playlist}; {
bool _isPublic {false}; public:
Wt::WDateTime _creationDateTime; TrackListEntry() = default;
Wt::WDateTime _lastModifiedDateTime;
Wt::Dbo::ptr<User> _user; bool hasOnPostCreated() const override { return true; }
Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> _entries; void onPostCreated() override;
};
class TrackListEntry final : public Object<TrackListEntry, TrackListEntryId> bool hasOnPreRemove() const override { return true; }
{ void onPreRemove() override;
public:
TrackListEntry() = default;
bool hasOnPostCreated() const override { return true; } // find utility
void onPostCreated() override; static pointer getById(Session& session, TrackListEntryId id);
bool hasOnPreRemove() const override { return true; } // Accessors
void onPreRemove() override; ObjectPtr<Track> getTrack() const { return _track; }
const Wt::WDateTime& getDateTime() const { return _dateTime; }
// find utility template<class Action>
static pointer getById(Session& session, TrackListEntryId id); void persist(Action& a)
{
Wt::Dbo::field(a, _dateTime, "date_time");
// Accessors Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
ObjectPtr<Track> getTrack() const { return _track; } Wt::Dbo::belongsTo(a, _tracklist, "tracklist", Wt::Dbo::OnDeleteCascade);
const Wt::WDateTime& getDateTime() const { return _dateTime; } }
template<class Action> private:
void persist(Action& a) friend class Session;
{ TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime);
Wt::Dbo::field(a, _dateTime, "date_time"); TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist);
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime = {});
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); Wt::WDateTime _dateTime; // optional date time
Wt::Dbo::belongsTo(a, _tracklist, "tracklist", Wt::Dbo::OnDeleteCascade); Wt::Dbo::ptr<Track> _track;
} Wt::Dbo::ptr<TrackList> _tracklist;
};
private:
friend class Session;
TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime);
TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist);
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime = {});
Wt::WDateTime _dateTime; // optional date time
Wt::Dbo::ptr<Track> _track;
Wt::Dbo::ptr<TrackList> _tracklist;
};
} // namespace Database } // namespace Database
@@ -29,7 +29,7 @@ namespace Database
// Caution: do not change enum values if they are set! // Caution: do not change enum values if they are set!
// Request: // Request:
// size = 0 => no size limit! // size = 0 => means we don't want data
// Response (via RangeResults) // Response (via RangeResults)
// size => results size // size => results size
struct Range struct Range
@@ -37,8 +37,7 @@ namespace Database
std::size_t offset{}; std::size_t offset{};
std::size_t size{}; std::size_t size{};
// TODO remove this bool operator==(const Range& rhs) const { return offset == rhs.offset && size == rhs.size; }
operator bool() const { return size != 0; }
}; };
// Func must return true to continue iterating // Func must return true to continue iterating
@@ -49,11 +49,11 @@ namespace Database {
{ {
std::optional<ScrobblingBackend> scrobblingBackend; std::optional<ScrobblingBackend> scrobblingBackend;
std::optional<FeedbackBackend> feedbackBackend; std::optional<FeedbackBackend> feedbackBackend;
Range range; std::optional<Range> range;
FindParameters& setFeedbackBackend(FeedbackBackend _feedbackBackend) { feedbackBackend = _feedbackBackend; return *this; } FindParameters& setFeedbackBackend(FeedbackBackend _feedbackBackend) { feedbackBackend = _feedbackBackend; return *this; }
FindParameters& setScrobblingBackend(ScrobblingBackend _scrobblingBackend) { scrobblingBackend = _scrobblingBackend; 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 = _range; return *this; }
}; };
static inline constexpr std::size_t MinNameLength{ 3 }; static inline constexpr std::size_t MinNameLength{ 3 };
+15 -3
View File
@@ -51,7 +51,7 @@ TEST_F(DatabaseFixture, Artist)
ASSERT_EQ(artists.results.size(), 1); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); EXPECT_EQ(artists.results.front(), artist.getId());
artists = Artist::findOrphanIds(session, Range{}); artists = Artist::findOrphanIds(session);
ASSERT_EQ(artists.results.size(), 1); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); EXPECT_EQ(artists.results.front(), artist.getId());
} }
@@ -64,6 +64,18 @@ TEST_F(DatabaseFixture, Artist)
ASSERT_EQ(artists.results.size(), 1); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front()->getId(), artist.getId()); 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) TEST_F(DatabaseFixture, Artist_singleTrack)
@@ -80,7 +92,7 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
{ {
auto transaction{ session.createSharedTransaction() }; 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() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
} }
{ {
+31 -380
View File
@@ -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); ASSERT_EQ(clusters.results.size(), 1);
EXPECT_EQ(clusters.results.front(), cluster.getId()); EXPECT_EQ(clusters.results.front(), cluster.getId());
} }
auto clusterTypes{ ClusterType::find(session, Range {}) }; auto clusterTypes{ ClusterType::find(session) };
ASSERT_EQ(clusterTypes.results.size(), 1); ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
clusterTypes = ClusterType::findUsed(session, Range{}); clusterTypes = ClusterType::findUsed(session);
ASSERT_EQ(clusterTypes.results.size(), 1); ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
clusterTypes = ClusterType::findOrphans(session, Range{}); clusterTypes = ClusterType::findOrphans(session);
EXPECT_TRUE(clusterTypes.results.empty()); EXPECT_TRUE(clusterTypes.results.empty());
} }
} }
@@ -76,11 +76,11 @@ TEST_F(DatabaseFixture, Cluster)
{ {
auto transaction{ session.createUniqueTransaction() }; auto transaction{ session.createUniqueTransaction() };
auto clusterTypes{ ClusterType::findOrphans(session, Range {}) }; auto clusterTypes{ ClusterType::findOrphans(session) };
ASSERT_EQ(clusterTypes.results.size(), 1); ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); 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() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
auto clusterTypes{ ClusterType::findOrphans(session, Range {}) }; auto clusterTypes{ ClusterType::findOrphans(session) };
ASSERT_EQ(clusterTypes.results.size(), 1); ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
} }
@@ -102,7 +102,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
auto clusters{ Cluster::findOrphans(session, Range {}) }; auto clusters{ Cluster::findOrphans(session) };
EXPECT_EQ(clusters.results.size(), 2); EXPECT_EQ(clusters.results.size(), 2);
EXPECT_TRUE(track->getClusters().empty()); EXPECT_TRUE(track->getClusters().empty());
EXPECT_TRUE(track->getClusterIds().empty()); EXPECT_TRUE(track->getClusterIds().empty());
@@ -127,11 +127,11 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
auto clusters{ Cluster::findOrphans(session, Range {}) }; auto clusters{ Cluster::findOrphans(session) };
ASSERT_EQ(clusters.results.size(), 1); ASSERT_EQ(clusters.results.size(), 1);
EXPECT_EQ(clusters.results.front(), cluster2.getId()); 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() }; 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()); 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(); }) }; auto it{ std::find_if(std::cbegin(tracks), std::cend(tracks), [&](const ScopedTrack& track) { return trackId == track.getId(); }) };
EXPECT_TRUE(it != std::cend(tracks)); 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<TrackListEntry>(trackA.get(), trackList.get());
session.create<TrackListEntry>(trackB.get(), trackList.get());
session.create<TrackListEntry>(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<TrackListEntry>(trackC.get(), trackList.get());
session.create<TrackListEntry>(trackC.get(), trackList.get());
session.create<TrackListEntry>(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) TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
{ {
ScopedTrack track{ session, "MyTrackFile" }; ScopedTrack track{ session, "MyTrackFile" };
@@ -340,7 +244,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
} }
ScopedClusterType clusterType{ session, "MyClusterType" }; ScopedClusterType clusterType{ session, "MyClusterType" };
@@ -349,7 +253,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
{ {
auto transaction{ session.createSharedTransaction() }; 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_TRUE(Release::find(session, Release::FindParameters{}.setClusters({ unusedCluster.getId() })).results.empty());
EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1); EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1);
EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0); EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0);
@@ -367,7 +271,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
{ {
auto clusters{ Cluster::findOrphans(session, Range {}) }; auto clusters{ Cluster::findOrphans(session) };
ASSERT_EQ(clusters.results.size(), 1); ASSERT_EQ(clusters.results.size(), 1);
EXPECT_EQ(clusters.results.front(), unusedCluster.getId()); EXPECT_EQ(clusters.results.front(), unusedCluster.getId());
} }
@@ -425,10 +329,10 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(ClusterType::findOrphans(session).results.empty());
EXPECT_EQ(Cluster::findOrphans(session, Range{}).results.size(), 2); EXPECT_EQ(Cluster::findOrphans(session).results.size(), 2);
EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
} }
{ {
@@ -486,9 +390,9 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
} }
{ {
@@ -526,8 +430,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
} }
{ {
@@ -562,7 +466,7 @@ TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity)
{ {
auto transaction{ session.createSharedTransaction() }; 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); EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1);
for (const TrackId similarTrackId : similarTracks.results) 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); EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1);
for (const TrackId similarTrackId : similarTracks.results) 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)); 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() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(ClusterType::findOrphans(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
EXPECT_TRUE(Release::findOrphanIds(session, Range{}).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<TrackListEntry>(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<TrackListEntry>(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<TrackListEntry>(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) TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters)
{ {
ScopedArtist artist1{ session, "MyArtist1" }; ScopedArtist artist1{ session, "MyArtist1" };
+10 -14
View File
@@ -42,8 +42,7 @@ TmpDatabase::TmpDatabase()
{ {
} }
Database::Db& Database::Db& TmpDatabase::getDb()
TmpDatabase::getDb()
{ {
return _db; return _db;
} }
@@ -53,8 +52,7 @@ DatabaseFixture::~DatabaseFixture()
testDatabaseEmpty(); testDatabaseEmpty();
} }
void void DatabaseFixture::SetUpTestCase()
DatabaseFixture::SetUpTestCase()
{ {
_tmpDb = std::make_unique<TmpDatabase>(); _tmpDb = std::make_unique<TmpDatabase>();
{ {
@@ -65,8 +63,8 @@ DatabaseFixture::SetUpTestCase()
// remove default created entries // remove default created entries
{ {
auto transaction{ s.createUniqueTransaction() }; 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) }; auto clusterType{ Database::ClusterType::find(s, clusterTypeId) };
clusterType.remove(); clusterType.remove();
@@ -75,14 +73,12 @@ DatabaseFixture::SetUpTestCase()
} }
} }
void void DatabaseFixture::TearDownTestCase()
DatabaseFixture::TearDownTestCase()
{ {
_tmpDb.reset(); _tmpDb.reset();
} }
void void DatabaseFixture::testDatabaseEmpty()
DatabaseFixture::testDatabaseEmpty()
{ {
using namespace Database; using namespace Database;
@@ -153,10 +149,10 @@ TEST_F(DatabaseFixture, Common_subRangeForeach)
{ {
std::vector<Range> subRanges; std::vector<Range> subRanges;
foreachSubRange(test.range, test.subRangeSize, [&](Range subRange) foreachSubRange(test.range, test.subRangeSize, [&](Range subRange)
{ {
subRanges.push_back(subRange); subRanges.push_back(subRange);
return true; return true;
}); });
EXPECT_EQ(subRanges, test.expectedSubRanges) << ", test index = " << std::distance(std::cbegin(testCases), &test); EXPECT_EQ(subRanges, test.expectedSubRanges) << ", test index = " << std::distance(std::cbegin(testCases), &test);
} }
@@ -42,8 +42,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
} }
{ {
+13 -3
View File
@@ -40,7 +40,7 @@ TEST_F(DatabaseFixture, Release)
EXPECT_TRUE(Release::exists(session, release.getId())); 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); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
} }
@@ -57,6 +57,16 @@ TEST_F(DatabaseFixture, Release)
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front()->getId(), release.getId()); 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() }; 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())) }; const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) };
ASSERT_EQ(tracks.results.size(), 1); 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())) }; const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) };
EXPECT_TRUE(tracks.results.empty()); EXPECT_TRUE(tracks.results.empty());
auto releases{ Release::findOrphanIds(session, Range {}) }; auto releases{ Release::findOrphanIds(session) };
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
} }
+16
View File
@@ -31,6 +31,12 @@ TEST_F(DatabaseFixture, Track)
EXPECT_EQ(Track::findIds(session, Track::FindParameters{}).results.size(), 0); EXPECT_EQ(Track::findIds(session, Track::FindParameters{}).results.size(), 0);
EXPECT_EQ(Track::getCount(session), 0); EXPECT_EQ(Track::getCount(session), 0);
EXPECT_FALSE(Track::exists(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" }; ScopedTrack track{ session, "MyTrackFile" };
@@ -44,6 +50,16 @@ TEST_F(DatabaseFixture, Track)
auto myTrack{ Track::find(session, track.getId()) }; auto myTrack{ Track::find(session, track.getId()) };
ASSERT_TRUE(myTrack); ASSERT_TRUE(myTrack);
EXPECT_EQ(myTrack->getId(), track.getId()); 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);
}
} }
} }
@@ -49,7 +49,7 @@ TEST_F(DatabaseFixture, TrackBookmark)
EXPECT_EQ(TrackBookmark::getCount(session), 1); 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); ASSERT_EQ(bookmarks.results.size(), 1);
EXPECT_EQ(bookmarks.results.front(), bookmark.getId()); EXPECT_EQ(bookmarks.results.front(), bookmark.getId());
} }
@@ -41,7 +41,7 @@ TEST_F(DatabaseFixture, TrackFeatures)
auto transaction {session.createUniqueTransaction()}; auto transaction {session.createUniqueTransaction()};
EXPECT_EQ(TrackFeatures::getCount(session), 1); EXPECT_EQ(TrackFeatures::getCount(session), 1);
auto allTrackFeatures {TrackFeatures::find(session, Range {})}; auto allTrackFeatures {TrackFeatures::find(session)};
ASSERT_EQ(allTrackFeatures.results.size(), 1); ASSERT_EQ(allTrackFeatures.results.size(), 1);
EXPECT_EQ(allTrackFeatures.results.front(), trackFeatures.getId()); EXPECT_EQ(allTrackFeatures.results.front(), trackFeatures.getId());
} }
+140 -437
View File
@@ -25,497 +25,200 @@ using namespace Database;
TEST_F(DatabaseFixture, SingleTrackList) TEST_F(DatabaseFixture, SingleTrackList)
{ {
ScopedUser user {session, "MyUser"}; ScopedUser user{ session, "MyUser" };
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
EXPECT_EQ(TrackList::getCount(session), 0); 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()}; auto transaction{ session.createSharedTransaction() };
EXPECT_EQ(TrackList::getCount(session), 1); EXPECT_EQ(TrackList::getCount(session), 1);
} }
} }
TEST_F(DatabaseFixture, SingleTrackListSingleTrack) TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
{ {
ScopedUser user {session, "MyUser"}; ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()}; ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()}; ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrack track {session, "MyTrack"}; ScopedTrack track{ session, "MyTrack" };
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
auto tracks {Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId()))}; auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) };
EXPECT_EQ(tracks.results.size(), 0); EXPECT_EQ(tracks.results.size(), 0);
tracks = Track::findIds(session, Track::FindParameters {}.setTrackList(trackList2.getId())); tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId()));
EXPECT_EQ(tracks.results.size(), 0); EXPECT_EQ(tracks.results.size(), 0);
} }
{ {
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
session.create<TrackListEntry>(track.get(), trackList1.get()); session.create<TrackListEntry>(track.get(), trackList1.get());
} }
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
auto tracks {Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId()))}; auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) };
ASSERT_EQ(tracks.results.size(), 1); ASSERT_EQ(tracks.results.size(), 1);
EXPECT_EQ(tracks.results.front(), track.getId()); EXPECT_EQ(tracks.results.front(), track.getId());
tracks = Track::findIds(session, Track::FindParameters {}.setTrackList(trackList2.getId())); tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId()));
EXPECT_EQ(tracks.results.size(), 0); EXPECT_EQ(tracks.results.size(), 0);
} }
} }
TEST_F(DatabaseFixture, TrackList_SortMethod) TEST_F(DatabaseFixture, TrackList_SortMethod)
{ {
ScopedUser user {session, "MyUser"}; ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()}; ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()}; ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrack track {session, "MyTrack"}; ScopedTrack track{ session, "MyTrack" };
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
const auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::Name))}; const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::Name)) };
ASSERT_EQ(trackLists.results.size(), 2); ASSERT_EQ(trackLists.results.size(), 2);
EXPECT_EQ(trackLists.results[0], trackList1.getId()); EXPECT_EQ(trackLists.results[0], trackList1.getId());
EXPECT_EQ(trackLists.results[1], trackList2.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}}); trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} });
trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime {Wt::WDate {1900,1,2}}); 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))}; const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) };
ASSERT_EQ(trackLists.results.size(), 2); ASSERT_EQ(trackLists.results.size(), 2);
EXPECT_EQ(trackLists.results[0], trackList2.getId()); EXPECT_EQ(trackLists.results[0], trackList2.getId());
EXPECT_EQ(trackLists.results[1], trackList1.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}}); trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} });
trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime {Wt::WDate {1900,1,1}}); 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))}; const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) };
ASSERT_EQ(trackLists.results.size(), 2); ASSERT_EQ(trackLists.results.size(), 2);
EXPECT_EQ(trackLists.results[0], trackList1.getId()); EXPECT_EQ(trackLists.results[0], trackList1.getId());
EXPECT_EQ(trackLists.results[1], trackList2.getId()); EXPECT_EQ(trackLists.results[1], trackList2.getId());
} }
} }
TEST_F(DatabaseFixture, SingleTrackListMultipleTrack) TEST_F(DatabaseFixture, SingleTrackListMultipleTrack)
{ {
ScopedUser user {session, "MyUser"}; ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()}; ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() };
std::list<ScopedTrack> tracks; std::list<ScopedTrack> tracks;
for (std::size_t i {}; i < 10; ++i) for (std::size_t i{}; i < 10; ++i)
{ {
tracks.emplace_back(session, "MyTrack" + std::to_string(i)); tracks.emplace_back(session, "MyTrack" + std::to_string(i));
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
session.create<TrackListEntry>(tracks.back().get(), trackList.get()); session.create<TrackListEntry>(tracks.back().get(), trackList.get());
} }
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
ASSERT_EQ(trackList->getCount(), tracks.size()); ASSERT_EQ(trackList->getCount(), tracks.size());
const auto trackIds {trackList->getTrackIds()}; const auto trackIds{ trackList->getTrackIds() };
ASSERT_EQ(trackIds.size(), tracks.size()); ASSERT_EQ(trackIds.size(), tracks.size());
// Same order // Same order
std::size_t i {}; std::size_t i{};
for (const ScopedTrack& track : tracks) for (const ScopedTrack& track : tracks)
EXPECT_EQ(track.getId(), trackIds[i++]); 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<TrackListEntry>(track1.get(), trackList.get(), now);
session.create<TrackListEntry>(track2.get(), trackList.get(), now.addSecs(-1));
session.create<TrackListEntry>(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<TrackListEntry>(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<TrackListEntry>(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<TrackListEntry>(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<TrackListEntry>(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<TrackListEntry>(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<TrackListEntry>(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<TrackListEntry>(track1.get(), trackList.get());
session.create<TrackListEntry>(track2.get(), trackList.get());
session.create<TrackListEntry>(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());
}
} }
TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster) TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster)
{ {
ScopedUser user {session, "MyUser"}; ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()}; ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()}; ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() };
ScopedClusterType clusterType {session, "MyClusterType"}; ScopedClusterType clusterType{ session, "MyClusterType" };
ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
ScopedTrack track {session, "MyTrack"}; ScopedTrack track{ session, "MyTrack" };
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()}))}; auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) };
EXPECT_EQ(trackLists.results.size(), 0); EXPECT_EQ(trackLists.results.size(), 0);
} }
{ {
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
session.create<TrackListEntry>(track.get(), trackList1.get()); session.create<TrackListEntry>(track.get(), trackList1.get());
cluster.get().modify()->addTrack(track.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()}))}; auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) };
ASSERT_EQ(trackLists.results.size(), 1); ASSERT_EQ(trackLists.results.size(), 1);
EXPECT_EQ(trackLists.results.front(), trackList1.getId()); EXPECT_EQ(trackLists.results.front(), trackList1.getId());
} }
} }
TEST_F(DatabaseFixture, SingleTrackList_getEntries) TEST_F(DatabaseFixture, SingleTrackList_getEntries)
{ {
ScopedUser user {session, "MyUser"}; ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()}; ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrack track1 {session, "MyTrack"}; ScopedTrack track1{ session, "MyTrack" };
ScopedTrack track2 {session, "MyTrack"}; ScopedTrack track2{ session, "MyTrack" };
{ {
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
session.create<TrackListEntry>(track1.get(), trackList.get()); session.create<TrackListEntry>(track1.get(), trackList.get());
} }
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
auto entries {trackList.get()->getEntries()}; auto entries{ trackList.get()->getEntries() };
ASSERT_EQ(entries.size(), 1); ASSERT_EQ(entries.size(), 1);
EXPECT_EQ(entries.front()->getTrack()->getId(), track1.getId()); EXPECT_EQ(entries.front()->getTrack()->getId(), track1.getId());
} }
{ {
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
session.create<TrackListEntry>(track2.get(), trackList.get()); session.create<TrackListEntry>(track2.get(), trackList.get());
} }
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
auto entries {trackList.get()->getEntries()}; auto entries{ trackList.get()->getEntries() };
ASSERT_EQ(entries.size(), 2); ASSERT_EQ(entries.size(), 2);
EXPECT_EQ(entries[0]->getTrack()->getId(), track1.getId()); EXPECT_EQ(entries[0]->getTrack()->getId(), track1.getId());
EXPECT_EQ(entries[1]->getTrack()->getId(), track2.getId()); EXPECT_EQ(entries[1]->getTrack()->getId(), track2.getId());
} }
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
auto entries {trackList.get()->getEntries(Range {1, 1})}; auto entries{ trackList.get()->getEntries(Range {1, 1}) };
ASSERT_EQ(entries.size(), 1); ASSERT_EQ(entries.size(), 1);
EXPECT_EQ(entries[0]->getTrack()->getId(), track2.getId()); EXPECT_EQ(entries[0]->getTrack()->getId(), track2.getId());
} }
} }
@@ -87,23 +87,23 @@ namespace Feedback
return getStarredDateTime<Artist, ArtistId, StarredArtist>(userId, artistId); return getStarredDateTime<Artist, ArtistId, StarredArtist>(userId, artistId);
} }
FeedbackService::ArtistContainer FeedbackService::getStarredArtists(UserId userId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, ArtistSortMethod sortMethod, Range range) FeedbackService::ArtistContainer FeedbackService::findStarredArtists(const ArtistFindParameters& params)
{ {
auto backend{ getUserFeedbackBackend(userId) }; auto backend{ getUserFeedbackBackend(params.user) };
if (!backend) if (!backend)
return {}; return {};
Artist::FindParameters params; Artist::FindParameters searchParams;
params.setStarringUser(userId, *backend); searchParams.setStarringUser(params.user, *backend);
params.setClusters(clusterIds); searchParams.setClusters(params.clusters);
params.setLinkType(linkType); searchParams.setLinkType(params.linkType);
params.setSortMethod(sortMethod); searchParams.setSortMethod(params.sortMethod);
params.setRange(range); searchParams.setRange(params.range);
Session& session{ _db.getTLSSession() }; Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
return Artist::findIds(session, params); return Artist::findIds(session, searchParams);
} }
void FeedbackService::star(UserId userId, ReleaseId releaseId) void FeedbackService::star(UserId userId, ReleaseId releaseId)
@@ -126,22 +126,22 @@ namespace Feedback
return getStarredDateTime<Release, ReleaseId, StarredRelease>(userId, releaseId); return getStarredDateTime<Release, ReleaseId, StarredRelease>(userId, releaseId);
} }
FeedbackService::ReleaseContainer FeedbackService::getStarredReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range) FeedbackService::ReleaseContainer FeedbackService::findStarredReleases(const FindParameters& params)
{ {
auto backend{ getUserFeedbackBackend(userId) }; auto backend{ getUserFeedbackBackend(params.user) };
if (!backend) if (!backend)
return {}; return {};
Release::FindParameters params; Release::FindParameters searchParams;
params.setStarringUser(userId, *backend); searchParams.setStarringUser(params.user, *backend);
params.setClusters(clusterIds); searchParams.setClusters(params.clusters);
params.setSortMethod(ReleaseSortMethod::StarredDateDesc); searchParams.setSortMethod(ReleaseSortMethod::StarredDateDesc);
params.setRange(range); searchParams.setRange(params.range);
Session& session{ _db.getTLSSession() }; Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
return Release::findIds(session, params); return Release::findIds(session, searchParams);
} }
void FeedbackService::star(UserId userId, TrackId trackId) void FeedbackService::star(UserId userId, TrackId trackId)
@@ -164,22 +164,22 @@ namespace Feedback
return getStarredDateTime<Track, TrackId, StarredTrack>(userId, trackId); return getStarredDateTime<Track, TrackId, StarredTrack>(userId, trackId);
} }
FeedbackService::TrackContainer FeedbackService::getStarredTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range) FeedbackService::TrackContainer FeedbackService::findStarredTracks(const FindParameters& params)
{ {
auto backend{ getUserFeedbackBackend(userId) }; auto backend{ getUserFeedbackBackend(params.user) };
if (!backend) if (!backend)
return {}; return {};
Track::FindParameters params; Track::FindParameters searchParams;
params.setStarringUser(userId, *backend); searchParams.setStarringUser(params.user, *backend);
params.setClusters(clusterIds); searchParams.setClusters(params.clusters);
params.setSortMethod(TrackSortMethod::StarredDateDesc); searchParams.setSortMethod(TrackSortMethod::StarredDateDesc);
params.setRange(range); searchParams.setRange(params.range);
Session& session{ _db.getTLSSession() }; Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
return Track::findIds(session, params); return Track::findIds(session, searchParams);
} }
} // ns Feedback } // ns Feedback
@@ -46,19 +46,19 @@ namespace Feedback
void unstar(Database::UserId userId, Database::ArtistId artistId) override; void unstar(Database::UserId userId, Database::ArtistId artistId) override;
bool isStarred(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; Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) override;
ArtistContainer getStarredArtists(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, std::optional<Database::TrackArtistLinkType> linkType, Database::ArtistSortMethod sortMethod, Database::Range range) override; ArtistContainer findStarredArtists(const ArtistFindParameters& params) override;
void star(Database::UserId userId, Database::ReleaseId releaseId) override; void star(Database::UserId userId, Database::ReleaseId releaseId) override;
void unstar(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; bool isStarred(Database::UserId userId, Database::ReleaseId releasedId) override;
Wt::WDateTime getStarredDateTime(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<Database::ClusterId>& clusterIds, Database::Range range) override; ReleaseContainer findStarredReleases(const FindParameters& params) override;
void star(Database::UserId userId, Database::TrackId trackId) override; void star(Database::UserId userId, Database::TrackId trackId) override;
void unstar(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; bool isStarred(Database::UserId userId, Database::TrackId trackId) override;
Wt::WDateTime getStarredDateTime(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<Database::ClusterId>& clusterIds, Database::Range range) override; TrackContainer findStarredTracks(const FindParameters& params) override;
std::optional<Database::FeedbackBackend> getUserFeedbackBackend(Database::UserId userId); std::optional<Database::FeedbackBackend> getUserFeedbackBackend(Database::UserId userId);
@@ -48,23 +48,46 @@ namespace Feedback
using ReleaseContainer = Database::RangeResults<Database::ReleaseId>; using ReleaseContainer = Database::RangeResults<Database::ReleaseId>;
using TrackContainer = Database::RangeResults<Database::TrackId>; using TrackContainer = Database::RangeResults<Database::TrackId>;
virtual void star(Database::UserId userId, Database::ArtistId artistId) = 0; struct FindParameters
virtual void unstar(Database::UserId userId, Database::ArtistId artistId) = 0; {
virtual bool isStarred(Database::UserId userId, Database::ArtistId artistId) = 0; Database::UserId user;
virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) = 0; std::vector<Database::ClusterId> clusters; // if non empty, at least one artist that belongs to these clusters
virtual ArtistContainer getStarredArtists(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, std::optional<Database::TrackArtistLinkType> linkType, Database::ArtistSortMethod sortMethod, Database::Range range) = 0; std::optional<Database::Range> range;
FindParameters& setUser(const Database::UserId _user) { user = _user; return *this; }
FindParameters& setClusters(const std::vector<Database::ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setRange(std::optional<Database::Range> _range) { range = _range; return *this; }
};
// Artists
struct ArtistFindParameters : public FindParameters
{
std::optional<Database::TrackArtistLinkType> 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<Database::TrackArtistLinkType> _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 star(Database::UserId userId, Database::ReleaseId releaseId) = 0;
virtual void unstar(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 bool isStarred(Database::UserId userId, Database::ReleaseId artistId) = 0;
virtual Wt::WDateTime getStarredDateTime(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<Database::ClusterId>& 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 star(Database::UserId userId, Database::TrackId trackId) = 0;
virtual void unstar(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 bool isStarred(Database::UserId userId, Database::TrackId artistId) = 0;
virtual Wt::WDateTime getStarredDateTime(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<Database::ClusterId>& clusterIds, Database::Range range) = 0; virtual TrackContainer findStarredTracks(const FindParameters& params) = 0;
}; };
std::unique_ptr<IFeedbackService> createFeedbackService(boost::asio::io_service& ioService, Database::Db& db); std::unique_ptr<IFeedbackService> createFeedbackService(boost::asio::io_service& ioService, Database::Db& db);
@@ -120,7 +120,7 @@ FeaturesEngine::loadFromTraining(const TrainSettings& trainSettings, const Progr
auto transaction {session.createSharedTransaction()}; auto transaction {session.createSharedTransaction()};
LMS_LOG(RECOMMENDATION, DEBUG) << "Getting Track features..."; 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)"; LMS_LOG(RECOMMENDATION, DEBUG) << "Getting Track features DONE (found " << trackFeaturesIds.results.size() << " track features)";
} }
@@ -37,7 +37,7 @@ namespace Scanner
Session& session {_db.getTLSSession()}; Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()}; auto transaction {session.createSharedTransaction()};
const RangeResults<TrackId> tracks = Track::findIdsTrackMBIDDuplicates(session, Range {}); const RangeResults<TrackId> tracks = Track::findIdsTrackMBIDDuplicates(session);
for (const TrackId trackId : tracks.results) for (const TrackId trackId : tracks.results)
{ {
const Track::pointer track {Track::find(session, trackId)}; const Track::pointer track {Track::find(session, trackId)};
@@ -30,174 +30,169 @@
namespace Scanner namespace Scanner
{ {
void void ScanStepRemoveOrphanDbFiles::process(ScanContext& context)
ScanStepRemoveOrphanDbFiles::process(ScanContext& context) {
{ removeOrphanTracks(context);
removeOrphanTracks(context); removeOrphanClusters();
removeOrphanClusters(); removeOrphanArtists();
removeOrphanArtists(); removeOrphanReleases();
removeOrphanReleases(); }
}
void ScanStepRemoveOrphanDbFiles::removeOrphanTracks(ScanContext& context) void ScanStepRemoveOrphanDbFiles::removeOrphanTracks(ScanContext& context)
{ {
using namespace Database; using namespace Database;
if (_abortScan) if (_abortScan)
return; return;
static constexpr std::size_t batchSize {50}; static constexpr std::size_t batchSize{ 50 };
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks to be removed..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks to be removed...";
std::size_t trackCount {}; std::size_t trackCount{};
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
trackCount = Track::getCount(session); trackCount = Track::getCount(session);
} }
LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks to be checked..."; LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks to be checked...";
context.currentStepStats.totalElems = trackCount; context.currentStepStats.totalElems = trackCount;
RangeResults<Track::PathResult> trackPaths; RangeResults<Track::PathResult> trackPaths;
std::vector<TrackId> tracksToRemove; std::vector<TrackId> tracksToRemove;
// TODO handle only files in context.directory // TODO handle only files in context.directory
for (std::size_t i {trackCount < batchSize ? 0 : trackCount - batchSize}; ; i -= (i > batchSize ? batchSize : i)) for (std::size_t i{ trackCount < batchSize ? 0 : trackCount - batchSize }; ; i -= (i > batchSize ? batchSize : i))
{ {
tracksToRemove.clear(); tracksToRemove.clear();
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
trackPaths = Track::findPaths(session, Range {i, batchSize}); trackPaths = Track::findPaths(session, Range{ i, batchSize });
} }
for (const Track::PathResult& trackPath : trackPaths.results) for (const Track::PathResult& trackPath : trackPaths.results)
{ {
if (_abortScan) if (_abortScan)
return; return;
if (!checkFile(trackPath.path)) if (!checkFile(trackPath.path))
tracksToRemove.push_back(trackPath.trackId); tracksToRemove.push_back(trackPath.trackId);
context.currentStepStats.processedElems++; context.currentStepStats.processedElems++;
} }
if (!tracksToRemove.empty()) if (!tracksToRemove.empty())
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
for (const TrackId trackId : tracksToRemove) for (const TrackId trackId : tracksToRemove)
{ {
Track::pointer track {Track::find(session, trackId)}; Track::pointer track{ Track::find(session, trackId) };
if (track) if (track)
{ {
track.remove(); track.remove();
context.stats.deletions++; context.stats.deletions++;
} }
} }
} }
_progressCallback(context.currentStepStats); _progressCallback(context.currentStepStats);
if (i == 0) if (i == 0)
break; break;
} }
LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks checked!"; LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks checked!";
} }
void void ScanStepRemoveOrphanDbFiles::removeOrphanClusters()
ScanStepRemoveOrphanDbFiles::removeOrphanClusters() {
{ using namespace Database;
using namespace Database;
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters...";
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
// Now process orphan Cluster (no track) // Now process orphan Cluster (no track)
auto clusterIds {Cluster::findOrphans(session, Range {})}; auto clusterIds{ Cluster::findOrphans(session) };
for (ClusterId clusterId : clusterIds.results) for (ClusterId clusterId : clusterIds.results)
{ {
Cluster::pointer cluster {Cluster::find(session, clusterId)}; Cluster::pointer cluster{ Cluster::find(session, clusterId) };
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'";
cluster.remove(); cluster.remove();
} }
} }
void void ScanStepRemoveOrphanDbFiles::removeOrphanArtists()
ScanStepRemoveOrphanDbFiles::removeOrphanArtists() {
{ using namespace Database;
using namespace Database;
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan artists..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan artists...";
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
auto artistIds {Artist::findOrphanIds(session, Range {})}; auto artistIds{ Artist::findOrphanIds(session) };
for (const ArtistId artistId : artistIds.results) for (const ArtistId artistId : artistIds.results)
{ {
Artist::pointer artist {Artist::find(session, artistId)}; Artist::pointer artist{ Artist::find(session, artistId) };
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
artist.remove(); artist.remove();
} }
} }
void void ScanStepRemoveOrphanDbFiles::removeOrphanReleases()
ScanStepRemoveOrphanDbFiles::removeOrphanReleases() {
{ using namespace Database;
using namespace Database;
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases...";
// TODO, by batch // TODO, by batch
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
auto releases {Release::findOrphanIds(session, Range {})}; auto releases{ Release::findOrphanIds(session) };
for (const ReleaseId releaseId : releases.results) for (const ReleaseId releaseId : releases.results)
{ {
Release::pointer release {Release::find(session, releaseId)}; Release::pointer release{ Release::find(session, releaseId) };
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'";
release.remove(); release.remove();
} }
} }
bool bool ScanStepRemoveOrphanDbFiles::checkFile(const std::filesystem::path& p)
ScanStepRemoveOrphanDbFiles::checkFile(const std::filesystem::path& p) {
{ try
try {
{ // For each track, make sure the the file still exists
// For each track, make sure the the file still exists // and still belongs to a media directory
// and still belongs to a media directory if (!std::filesystem::exists(p)
if (!std::filesystem::exists( p ) || !std::filesystem::is_regular_file(p))
|| !std::filesystem::is_regular_file( p ) ) {
{ LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': missing";
LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': missing"; return false;
return false; }
}
if (!PathUtils::isPathInRootPath(p, _settings.mediaDirectory, &excludeDirFileName)) if (!PathUtils::isPathInRootPath(p, _settings.mediaDirectory, &excludeDirFileName))
{ {
LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': out of media directory"; LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': out of media directory";
return false; return false;
} }
if (!PathUtils::hasFileAnyExtension(p, _settings.supportedExtensions)) if (!PathUtils::hasFileAnyExtension(p, _settings.supportedExtensions))
{ {
LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': file format no longer handled"; LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': file format no longer handled";
return false; return false;
} }
return true; return true;
} }
catch (std::filesystem::filesystem_error& e) catch (std::filesystem::filesystem_error& e)
{ {
LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p.string() << "': " << e.what(); LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p.string() << "': " << e.what();
return false; return false;
} }
} }
} }
@@ -29,6 +29,8 @@
namespace API::Subsonic 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 enum class ResponseFormat
{ {
@@ -47,8 +47,8 @@ namespace API::Subsonic
// Optional params // Optional params
const std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(10) }; const std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(10) };
const std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) }; const std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) };
if (size > 500) if (size > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "size", 500 }; throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
const Range range{ offset, size }; 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) // (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; Release::FindParameters params;
params.setSortMethod(ReleaseSortMethod::Random); params.setSortMethod(ReleaseSortMethod::Random);
params.setRange({ 0, size }); params.setRange(Range{ 0, size });
releases = Release::findIds(context.dbSession, params); releases = Release::findIds(context.dbSession, params);
} }
@@ -132,10 +132,15 @@ namespace API::Subsonic
} }
else if (type == "starred") 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 else
{
throw NotImplementedGenericError{}; throw NotImplementedGenericError{};
}
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& albumListNode{ response.createNode(id3 ? Response::Node::Key{ "albumList2" } : Response::Node::Key{ "albumList" }) }; 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<Feedback::IFeedbackService>::get() }; Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::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) }) Feedback::IFeedbackService::ArtistFindParameters artistFindParams;
starredNode.addArrayChild("artist", createArtistNode(context, artist, user, id3)); 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) }) if (auto release{ Release::find(context.dbSession, releaseId) })
starredNode.addArrayChild("album", createAlbumNode(context, release, user, id3)); 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) }) if (auto track{ Track::find(context.dbSession, trackId) })
starredNode.addArrayChild("song", createSongNode(context, track, user)); starredNode.addArrayChild("song", createSongNode(context, track, user));
@@ -198,8 +211,8 @@ namespace API::Subsonic
{ {
// Optional params // Optional params
std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(50) }; std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(50) };
if (size > 500) if (size > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{"size", 500}; throw ParameterValueTooHighGenericError{ "size", defaultMaxCountSize };
auto transaction{ context.dbSession.createSharedTransaction() }; auto transaction{ context.dbSession.createSharedTransaction() };
@@ -207,13 +220,17 @@ namespace API::Subsonic
if (!user) if (!user)
throw UserNotAuthorizedError{}; throw UserNotAuthorizedError{};
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setSortMethod(TrackSortMethod::Random).setRange({0, size})) };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& randomSongsNode{ response.createNode("randomSongs") }; 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; return response;
} }
@@ -225,8 +242,8 @@ namespace API::Subsonic
// Optional params // Optional params
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(10) }; std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(10) };
if (count > 500) if (count > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{"count", 500}; throw ParameterValueTooHighGenericError{"count", defaultMaxCountSize};
std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) }; std::size_t offset{ getParameterAs<std::size_t>(context.parameters, "offset").value_or(0) };
@@ -249,11 +266,12 @@ namespace API::Subsonic
Track::FindParameters params; Track::FindParameters params;
params.setClusters({ cluster->getId() }); params.setClusters({ cluster->getId() });
params.setRange({ offset, count }); params.setRange(Range{ offset, count });
const auto tracks{ Track::find(context.dbSession, params) }; Track::find(context.dbSession, params, [&](const Track::pointer& track)
for (const Track::pointer& track : tracks.results) {
songsByGenreNode.addArrayChild("song", createSongNode(context, track, user)); songsByGenreNode.addArrayChild("song", createSongNode(context, track, user));
});
return response; return response;
} }
@@ -40,7 +40,7 @@ namespace API::Subsonic
if (!user) if (!user)
throw UserNotAuthorizedError{}; 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 response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& bookmarksNode{ response.createNode("bookmarks") }; Response::Node& bookmarksNode{ response.createNode("bookmarks") };
+18 -12
View File
@@ -124,7 +124,7 @@ namespace API::Subsonic
// first pass: dispatch the artists by first letter // first pass: dispatch the artists by first letter
LMS_LOG(API_SUBSONIC, DEBUG) << "GetArtists: fetching all artists..."; LMS_LOG(API_SUBSONIC, DEBUG) << "GetArtists: fetching all artists...";
std::map<char, std::vector<ArtistId>> artistsSortedByFirstChar; std::map<char, std::vector<ArtistId>> artistsSortedByFirstChar;
std::size_t currentArtistOffset{0}; std::size_t currentArtistOffset{ 0 };
constexpr std::size_t batchSize{ 100 }; constexpr std::size_t batchSize{ 100 };
bool hasMoreArtists{ true }; bool hasMoreArtists{ true };
while (hasMoreArtists) while (hasMoreArtists)
@@ -190,7 +190,7 @@ namespace API::Subsonic
{ {
Track::FindParameters params; Track::FindParameters params;
params.setArtist(id); params.setArtist(id);
params.setRange({ 0, meanTrackCountPerArtist }); params.setRange(Range{ 0, meanTrackCountPerArtist });
params.setSortMethod(TrackSortMethod::Random); params.setSortMethod(TrackSortMethod::Random);
const auto artistTracks{ Track::findIds(context.dbSession, params) }; const auto artistTracks{ Track::findIds(context.dbSession, params) };
@@ -221,7 +221,7 @@ namespace API::Subsonic
{ {
Track::FindParameters params; Track::FindParameters params;
params.setRelease(id); params.setRelease(id);
params.setRange({ 0, meanTrackCountPerRelease }); params.setRange(Range{ 0, meanTrackCountPerRelease });
params.setSortMethod(TrackSortMethod::Random); params.setSortMethod(TrackSortMethod::Random);
const auto releaseTracks{ Track::findIds(context.dbSession, params) }; const auto releaseTracks{ Track::findIds(context.dbSession, params) };
@@ -242,6 +242,8 @@ namespace API::Subsonic
{ {
// Optional params // Optional params
std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(50) }; std::size_t count{ getParameterAs<std::size_t>(context.parameters, "count").value_or(50) };
if (count > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize };
std::vector<TrackId> tracks; std::vector<TrackId> tracks;
@@ -316,9 +318,11 @@ namespace API::Subsonic
directoryNode.setAttribute("id", idToString(RootId{})); directoryNode.setAttribute("id", idToString(RootId{}));
directoryNode.setAttribute("name", "Music"); directoryNode.setAttribute("name", "Music");
const auto rootArtistIds{ Artist::find(context.dbSession, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName)) }; // TODO: this does not scale when a lot of artists are present
for (const Artist::pointer& artist : rootArtistIds.results) Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::BySortName), [&](const Artist::pointer& artist)
directoryNode.addArrayChild("child", createArtistNode(context, artist, user, false /* no id3 */)); {
directoryNode.addArrayChild("child", createArtistNode(context, artist, user, false /* no id3 */));
});
} }
else if (artistId) else if (artistId)
{ {
@@ -330,9 +334,10 @@ namespace API::Subsonic
directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(artist->getName())); directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(artist->getName()));
const auto artistReleases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(*artistId)) }; Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release)
for (const Release::pointer& release : artistReleases.results) {
directoryNode.addArrayChild("child", createAlbumNode(context, release, user, false /* no id3 */)); directoryNode.addArrayChild("child", createAlbumNode(context, release, user, false /* no id3 */));
});
} }
else if (releaseId) else if (releaseId)
{ {
@@ -344,9 +349,10 @@ namespace API::Subsonic
directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(release->getName())); directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(release->getName()));
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release)) }; Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track)
for (const Track::pointer& track : tracks.results) {
directoryNode.addArrayChild("child", createSongNode(context, track, user)); directoryNode.addArrayChild("child", createSongNode(context, track, user));
});
} }
else else
throw BadParameterGenericError{ "id" }; throw BadParameterGenericError{ "id" };
@@ -57,12 +57,12 @@ namespace API::Subsonic
std::size_t songCount{ getParameterAs<std::size_t>(context.parameters, "songCount").value_or(20) }; std::size_t songCount{ getParameterAs<std::size_t>(context.parameters, "songCount").value_or(20) };
std::size_t songOffset{ getParameterAs<std::size_t>(context.parameters, "songOffset").value_or(0) }; std::size_t songOffset{ getParameterAs<std::size_t>(context.parameters, "songOffset").value_or(0) };
if (artistCount > 500) if (artistCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "artistCount", 500 }; throw ParameterValueTooHighGenericError{ "artistCount", defaultMaxCountSize };
else if (albumCount > 500) else if (albumCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "albumCount", 500 }; throw ParameterValueTooHighGenericError{ "albumCount", defaultMaxCountSize };
else if (songCount > 500) else if (songCount > defaultMaxCountSize)
throw ParameterValueTooHighGenericError{ "songCount", 500 }; throw ParameterValueTooHighGenericError{ "songCount", defaultMaxCountSize };
Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& searchResult2Node{ response.createNode(id3 ? "searchResult3" : "searchResult2") }; Response::Node& searchResult2Node{ response.createNode(id3 ? "searchResult3" : "searchResult2") };
@@ -77,30 +77,36 @@ namespace API::Subsonic
{ {
Artist::FindParameters params; Artist::FindParameters params;
params.setKeywords(keywords); params.setKeywords(keywords);
params.setRange({ artistOffset, artistCount }); params.setRange(Range{ artistOffset, artistCount });
for (const Artist::pointer& artist : Artist::find(context.dbSession, params).results) Artist::find(context.dbSession, params, [&](const Artist::pointer& artist)
searchResult2Node.addArrayChild("artist", createArtistNode(context, artist, user, id3)); {
searchResult2Node.addArrayChild("artist", createArtistNode(context, artist, user, id3));
});
} }
if (albumCount > 0) if (albumCount > 0)
{ {
Release::FindParameters params; Release::FindParameters params;
params.setKeywords(keywords); params.setKeywords(keywords);
params.setRange({ albumOffset, albumCount }); params.setRange(Range{ albumOffset, albumCount });
for (const Release::pointer& release : Release::find(context.dbSession, params).results) Release::find(context.dbSession, params, [&](const Release::pointer& release)
searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3)); {
searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3));
});
} }
if (songCount > 0) if (songCount > 0)
{ {
Track::FindParameters params; Track::FindParameters params;
params.setKeywords(keywords); params.setKeywords(keywords);
params.setRange({ songOffset, songCount }); params.setRange(Range{ songOffset, songCount });
for (const Track::pointer& track : Track::find(context.dbSession, params).results) Track::find(context.dbSession, params, [&](const Track::pointer& track)
searchResult2Node.addArrayChild("song", createSongNode(context, track, user)); {
searchResult2Node.addArrayChild("song", createSongNode(context, track, user));
});
} }
return response; return response;
+4 -3
View File
@@ -799,8 +799,9 @@ namespace UserInterface
params.setDistinct(false); params.setDistinct(false);
params.setSortMethod(TrackSortMethod::TrackList); params.setSortMethod(TrackSortMethod::TrackList);
const auto tracks{ Track::find(session, params) }; Track::find(session, params, [&](const Track::pointer& track)
for (const Track::pointer& track : tracks.results) {
session.create<TrackListEntry>(track, trackList); session.create<TrackListEntry>(track, trackList);
});
} }
} // namespace UserInterface } // namespace UserInterface
+12 -6
View File
@@ -33,16 +33,14 @@ namespace UserInterface
{ {
using namespace Database; using namespace Database;
RangeResults<ArtistId> ArtistCollector::get(Database::Range range) RangeResults<ArtistId> ArtistCollector::get(std::optional<Database::Range> requestedRange)
{ {
Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() }; Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() };
Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() }; Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() };
RangeResults<ArtistId> artists; const Range range{ getActualRange(requestedRange) };
range = getActualRange(range); RangeResults<ArtistId> artists;
if (range.size == 0)
return artists;
switch (getMode()) switch (getMode())
{ {
@@ -51,8 +49,16 @@ namespace UserInterface
break; break;
case Mode::Starred: 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; break;
}
case Mode::RecentlyPlayed: case Mode::RecentlyPlayed:
artists = scrobblingService.getRecentArtists(LmsApp->getUserId(), getFilters().getClusterIds(), _linkType, range); artists = scrobblingService.getRecentArtists(LmsApp->getUserId(), getFilters().getClusterIds(), _linkType, range);
+1 -1
View File
@@ -37,7 +37,7 @@ namespace UserInterface
public: public:
using DatabaseCollectorBase::DatabaseCollectorBase; using DatabaseCollectorBase::DatabaseCollectorBase;
Database::RangeResults<Database::ArtistId> get(Database::Range range); Database::RangeResults<Database::ArtistId> get(std::optional<Database::Range> range = std::nullopt);
void reset() { _randomArtists.reset(); } void reset() { _randomArtists.reset(); }
void setArtistLinkType(std::optional<Database::TrackArtistLinkType> linkType) { _linkType = linkType; } void setArtistLinkType(std::optional<Database::TrackArtistLinkType> linkType) { _linkType = linkType; }
+15 -6
View File
@@ -30,16 +30,25 @@ namespace UserInterface
{ {
} }
DatabaseCollectorBase::Range DatabaseCollectorBase::getActualRange(Range range) const DatabaseCollectorBase::Range DatabaseCollectorBase::getActualRange(std::optional<Database::Range> requestedRange) const
{ {
assert(range.size > 0); Database::Range res;
if (range.offset < _maxCount) if (!requestedRange)
range.size = std::min(_maxCount - range.offset, range.size); {
res.offset = 0;
res.size = _maxCount;
}
else 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 std::size_t DatabaseCollectorBase::getMaxCount() const
+2 -2
View File
@@ -56,9 +56,9 @@ namespace UserInterface
void setSearch(std::string_view search); void setSearch(std::string_view search);
protected: protected:
Range getActualRange(Range range) const; Range getActualRange(std::optional<Range> range) const;
std::size_t getMaxCount() const; std::size_t getMaxCount() const;
Filters& getFilters() { return _filters; } Filters& getFilters() { return _filters; }
const std::vector<std::string_view>& getSearchKeywords() const { return _searchKeywords; } const std::vector<std::string_view>& getSearchKeywords() const { return _searchKeywords; }
private: private:
+1 -1
View File
@@ -84,7 +84,7 @@ Filters::showDialog()
{ {
auto transaction {LmsApp->getDbSession().createSharedTransaction()}; 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) for (const ClusterTypeId clusterTypeId : clusterTypesIds.results)
{ {
const auto clusterType {ClusterType::find(LmsApp->getDbSession(), clusterTypeId)}; const auto clusterType {ClusterType::find(LmsApp->getDbSession(), clusterTypeId)};
+3 -3
View File
@@ -45,7 +45,7 @@ namespace UserInterface
params.setArtist(artistId); params.setArtist(artistId);
params.setSortMethod(Database::TrackSortMethod::DateDescAndRelease); params.setSortMethod(Database::TrackSortMethod::DateDescAndRelease);
params.setClusters(clusters); 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) }; const auto tracks{ Database::Track::findIds(session, params) };
@@ -74,7 +74,7 @@ namespace UserInterface
params.setRelease(releaseId); params.setRelease(releaseId);
params.setSortMethod(Database::TrackSortMethod::Release); params.setSortMethod(Database::TrackSortMethod::Release);
params.setClusters(clusters); 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) }; const auto tracks{ Database::Track::findIds(session, params) };
@@ -98,7 +98,7 @@ namespace UserInterface
Database::Track::FindParameters params; Database::Track::FindParameters params;
params.setTrackList(trackListId); params.setTrackList(trackListId);
params.setClusters(clusters); params.setClusters(clusters);
params.setRange({ 0, maxTrackCount }); params.setRange(Database::Range{ 0, maxTrackCount });
params.setSortMethod(TrackSortMethod::TrackList); params.setSortMethod(TrackSortMethod::TrackList);
params.setDistinct(false); params.setDistinct(false);
+11 -6
View File
@@ -32,15 +32,14 @@ namespace UserInterface
{ {
using namespace Database; using namespace Database;
RangeResults<ReleaseId> ReleaseCollector::get(Database::Range range) RangeResults<ReleaseId> ReleaseCollector::get(std::optional<Database::Range> requestedRange)
{ {
Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() }; Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() };
Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() }; Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() };
const Range range{ getActualRange(requestedRange) };
RangeResults<ReleaseId> releases; RangeResults<ReleaseId> releases;
range = getActualRange(range);
if (range.size == 0)
return releases;
switch (getMode()) switch (getMode())
{ {
@@ -49,8 +48,14 @@ namespace UserInterface
break; break;
case Mode::Starred: 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; break;
}
case ReleaseCollector::Mode::RecentlyPlayed: case ReleaseCollector::Mode::RecentlyPlayed:
releases = scrobblingService.getRecentReleases(LmsApp->getUserId(), getFilters().getClusterIds(), range); releases = scrobblingService.getRecentReleases(LmsApp->getUserId(), getFilters().getClusterIds(), range);
@@ -118,7 +123,7 @@ namespace UserInterface
Release::FindParameters params; Release::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusterIds());
params.setSortMethod(ReleaseSortMethod::Random); params.setSortMethod(ReleaseSortMethod::Random);
params.setRange({ 0, getMaxCount() }); params.setRange(Range{ 0, getMaxCount() });
{ {
auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
+2 -2
View File
@@ -19,7 +19,7 @@
#pragma once #pragma once
#include <vector> #include <optional>
#include "DatabaseCollectorBase.hpp" #include "DatabaseCollectorBase.hpp"
@@ -39,7 +39,7 @@ namespace UserInterface
public: public:
using DatabaseCollectorBase::DatabaseCollectorBase; using DatabaseCollectorBase::DatabaseCollectorBase;
Database::RangeResults<Database::ReleaseId> get(Database::Range range); Database::RangeResults<Database::ReleaseId> get(std::optional<Database::Range> range = std::nullopt);
void reset() { _randomReleases.reset(); } void reset() { _randomReleases.reset(); }
private: private:
+83 -83
View File
@@ -373,100 +373,100 @@ namespace UserInterface
params.setSortMethod(Database::TrackSortMethod::Release); params.setSortMethod(Database::TrackSortMethod::Release);
params.setClusters(_filters.getClusterIds()); params.setClusters(_filters.getClusterIds());
for (const Database::Track::pointer& track : Database::Track::find(LmsApp->getDbSession(), params).results) Database::Track::find(LmsApp->getDbSession(), params, [&](const Database::Track::pointer& track)
{
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<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry")) };
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
const auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) };
if (variousArtists && !artists.empty())
{ {
entry->setCondition("if-has-artists", true); const Database::TrackId trackId{ track->getId() };
entry->bindWidget("artists", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists)); const auto discNumber{ track->getDiscNumber() };
entry->bindWidget("artists-md", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
}
auto trackNumber{ track->getTrackNumber() }; Wt::WContainerWidget* container;
if (trackNumber) if (isReleaseMultiDisc && discNumber)
{ container = getOrAddDiscContainer(*discNumber, track->getDiscSubtitle());
entry->setCondition("if-has-track-number", true); else
entry->bindInt("track-number", *trackNumber); container = getOrAddNoDiscContainer();
}
Wt::WPushButton* playBtn{ entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) }; Template* entry{ container->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry")) };
playBtn->clicked().connect([=]
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
const auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) };
if (variousArtists && !artists.empty())
{ {
_playQueueController.playTrackInRelease(trackId); entry->setCondition("if-has-artists", true);
}); entry->bindWidget("artists", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
entry->bindWidget("artists-md", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
}
{ auto trackNumber{ track->getTrackNumber() };
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML); if (trackNumber)
entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play")) {
->clicked().connect([=] entry->setCondition("if-has-track-number", true);
{ entry->bindInt("track-number", *trackNumber);
_playQueueController.playTrackInRelease(trackId); }
});
entry->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"))
->clicked().connect([=]
{
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { trackId });
});
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
->clicked().connect([=]
{
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { trackId });
});
auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); } }; Wt::WPushButton* playBtn{ entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) };
playBtn->clicked().connect([=]
Wt::WPushButton* starBtn{ entry->bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) };
starBtn->clicked().connect([=]
{ {
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() }; _playQueueController.playTrackInRelease(trackId);
if (isStarred())
{
Service<Feedback::IFeedbackService>::get()->unstar(LmsApp->getUserId(), trackId);
starBtn->setText(Wt::WString::tr("Lms.Explore.star"));
}
else
{
Service<Feedback::IFeedbackService>::get()->star(LmsApp->getUserId(), trackId);
starBtn->setText(Wt::WString::tr("Lms.Explore.unstar"));
}
}); });
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
->setLink(Wt::WLink{ std::make_unique<DownloadTrackResource>(trackId) });
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info"))
->clicked().connect([=] { TrackListHelpers::showTrackInfoModal(trackId, _filters); });
}
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=](TrackId loadedTrackId)
{ {
entry->toggleStyleClass("Lms-entry-playing", loadedTrackId == trackId); entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
}); entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
->clicked().connect([=]
{
_playQueueController.playTrackInRelease(trackId);
});
entry->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"))
->clicked().connect([=]
{
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { trackId });
});
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
->clicked().connect([=]
{
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { trackId });
});
if (auto trackIdLoaded{ LmsApp->getMediaPlayer().getTrackLoaded() }) auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); } };
{
entry->toggleStyleClass("Lms-entry-playing", *trackIdLoaded == trackId); Wt::WPushButton* starBtn{ entry->bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) };
} starBtn->clicked().connect([=]
else {
entry->removeStyleClass("Lms-entry-playing"); auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
}
if (isStarred())
{
Service<Feedback::IFeedbackService>::get()->unstar(LmsApp->getUserId(), trackId);
starBtn->setText(Wt::WString::tr("Lms.Explore.star"));
}
else
{
Service<Feedback::IFeedbackService>::get()->star(LmsApp->getUserId(), trackId);
starBtn->setText(Wt::WString::tr("Lms.Explore.unstar"));
}
});
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
->setLink(Wt::WLink{ std::make_unique<DownloadTrackResource>(trackId) });
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info"))
->clicked().connect([=] { TrackListHelpers::showTrackInfoModal(trackId, _filters); });
}
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=](TrackId loadedTrackId)
{
entry->toggleStyleClass("Lms-entry-playing", loadedTrackId == trackId);
});
if (auto trackIdLoaded{ LmsApp->getMediaPlayer().getTrackLoaded() })
{
entry->toggleStyleClass("Lms-entry-playing", *trackIdLoaded == trackId);
}
else
entry->removeStyleClass("Lms-entry-playing");
});
} }
void Release::refreshReleaseArtists(const Database::Release::pointer& release) void Release::refreshReleaseArtists(const Database::Release::pointer& release)
+89 -93
View File
@@ -33,113 +33,109 @@
using namespace Database; using namespace Database;
namespace UserInterface { namespace UserInterface
Releases::Releases(Filters& filters, PlayQueueController& playQueueController)
: Template {Wt::WString::tr("Lms.Explore.Releases.template")}
, _playQueueController {playQueueController}
, _releaseCollector {filters, _defaultMode, _maxCount}
{ {
addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("id", &Wt::WTemplate::Functions::id);
auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, ReleaseCollector::Mode mode) Releases::Releases(Filters& filters, PlayQueueController& playQueueController)
{ : Template{ Wt::WString::tr("Lms.Explore.Releases.template") }
auto *menuItem {bindNew<Wt::WPushButton>(var, title)}; , _playQueueController{ playQueueController }
menuItem->clicked().connect([=] , _releaseCollector{ filters, _defaultMode, _maxCount }
{ {
refreshView(mode); addFunction("tr", &Wt::WTemplate::Functions::tr);
_currentActiveItem->removeStyleClass("active"); addFunction("id", &Wt::WTemplate::Functions::id);
menuItem->addStyleClass("active");
_currentActiveItem = menuItem;
});
if (mode == _defaultMode) auto bindMenuItem{ [this](const std::string& var, const Wt::WString& title, ReleaseCollector::Mode mode)
{ {
_currentActiveItem = menuItem; auto* menuItem {bindNew<Wt::WPushButton>(var, title)};
_currentActiveItem->addStyleClass("active"); menuItem->clicked().connect([=]
} {
}}; refreshView(mode);
_currentActiveItem->removeStyleClass("active");
menuItem->addStyleClass("active");
_currentActiveItem = menuItem;
});
bindMenuItem("random", Wt::WString::tr("Lms.Explore.random"), ReleaseCollector::Mode::Random); if (mode == _defaultMode)
bindMenuItem("starred", Wt::WString::tr("Lms.Explore.starred"), ReleaseCollector::Mode::Starred); {
bindMenuItem("recently-played", Wt::WString::tr("Lms.Explore.recently-played"), ReleaseCollector::Mode::RecentlyPlayed); _currentActiveItem = menuItem;
bindMenuItem("most-played", Wt::WString::tr("Lms.Explore.most-played"), ReleaseCollector::Mode::MostPlayed); _currentActiveItem->addStyleClass("active");
bindMenuItem("recently-added", Wt::WString::tr("Lms.Explore.recently-added"), ReleaseCollector::Mode::RecentlyAdded); }
bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), ReleaseCollector::Mode::All); } };
Wt::WPushButton* playBtn {bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)}; bindMenuItem("random", Wt::WString::tr("Lms.Explore.random"), ReleaseCollector::Mode::Random);
playBtn->clicked().connect([this] bindMenuItem("starred", Wt::WString::tr("Lms.Explore.starred"), ReleaseCollector::Mode::Starred);
{ bindMenuItem("recently-played", Wt::WString::tr("Lms.Explore.recently-played"), ReleaseCollector::Mode::RecentlyPlayed);
_playQueueController.processCommand(PlayQueueController::Command::Play, getAllReleases()); bindMenuItem("most-played", Wt::WString::tr("Lms.Explore.most-played"), ReleaseCollector::Mode::MostPlayed);
}); bindMenuItem("recently-added", Wt::WString::tr("Lms.Explore.recently-added"), ReleaseCollector::Mode::RecentlyAdded);
bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), ReleaseCollector::Mode::All);
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) Wt::WPushButton* playBtn{ bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) };
->clicked().connect([=] playBtn->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllReleases()); _playQueueController.processCommand(PlayQueueController::Command::Play, getAllReleases());
}); });
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllReleases());
});
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllReleases());
});
_container = bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
_container->onRequestElements.connect([this] ->clicked().connect([=]
{ {
addSome(); _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllReleases());
}); });
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllReleases());
});
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllReleases());
});
filters.updated().connect([this] _container = bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
{ _container->onRequestElements.connect([this]
refreshView(); {
}); addSome();
});
refreshView(_releaseCollector.getMode()); filters.updated().connect([this]
} {
refreshView();
});
void refreshView(_releaseCollector.getMode());
Releases::refreshView() }
{
_container->reset();
_releaseCollector.reset();
}
void void Releases::refreshView()
Releases::refreshView(ReleaseCollector::Mode mode) {
{ _container->reset();
_releaseCollector.setMode(mode); _releaseCollector.reset();
refreshView(); }
}
void void Releases::refreshView(ReleaseCollector::Mode mode)
Releases::addSome() {
{ _releaseCollector.setMode(mode);
const auto releaseIds {_releaseCollector.get(Range {static_cast<std::size_t>(_container->getCount()), _batchSize})}; refreshView();
}
{ void Releases::addSome()
auto transaction {LmsApp->getDbSession().createSharedTransaction()}; {
const auto releaseIds{ _releaseCollector.get(Range {static_cast<std::size_t>(_container->getCount()), _batchSize}) };
for (const ReleaseId releaseId : releaseIds.results) {
{ auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
if (const Release::pointer release {Release::find(LmsApp->getDbSession(), releaseId)})
_container->add(ReleaseListHelpers::createEntry(release));
}
}
}
std::vector<ReleaseId> for (const ReleaseId releaseId : releaseIds.results)
Releases::getAllReleases() {
{ if (const Release::pointer release{ Release::find(LmsApp->getDbSession(), releaseId) })
RangeResults<ReleaseId> releaseIds {_releaseCollector.get(Range {})}; _container->add(ReleaseListHelpers::createEntry(release));
return std::move(releaseIds.results); }
} }
}
std::vector<ReleaseId> Releases::getAllReleases()
{
RangeResults<ReleaseId> releaseIds{ _releaseCollector.get() };
return std::move(releaseIds.results);
}
} // namespace UserInterface } // namespace UserInterface
+6 -3
View File
@@ -155,7 +155,8 @@ namespace UserInterface
for (const ArtistId artistId : artistIds.results) for (const ArtistId artistId : artistIds.results)
{ {
const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) }; const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) };
_artists->add(ArtistListHelpers::createEntry(artist)); if (artist)
_artists->add(ArtistListHelpers::createEntry(artist));
} }
} }
} }
@@ -172,7 +173,8 @@ namespace UserInterface
for (const ReleaseId releaseId : releaseIds.results) for (const ReleaseId releaseId : releaseIds.results)
{ {
const Release::pointer release{ Release::find(LmsApp->getDbSession(), releaseId) }; const Release::pointer release{ Release::find(LmsApp->getDbSession(), releaseId) };
_releases->add(ReleaseListHelpers::createEntry(release)); if (release)
_releases->add(ReleaseListHelpers::createEntry(release));
} }
} }
} }
@@ -190,7 +192,8 @@ namespace UserInterface
for (const TrackId trackId : trackIds.results) for (const TrackId trackId : trackIds.results)
{ {
const Track::pointer track{ Track::find(LmsApp->getDbSession(), trackId) }; const Track::pointer track{ Track::find(LmsApp->getDbSession(), trackId) };
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters)); if (track)
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
} }
} }
} }
+11 -6
View File
@@ -35,15 +35,14 @@ namespace UserInterface
{ {
using namespace Database; using namespace Database;
RangeResults<TrackId> TrackCollector::get(Range range) RangeResults<TrackId> TrackCollector::get(std::optional<Range> requestedRange)
{ {
Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() }; Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() };
Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() }; Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() };
const Range range{ getActualRange(requestedRange) };
RangeResults<TrackId> tracks; RangeResults<TrackId> tracks;
range = getActualRange(range);
if (range.size == 0)
return tracks;
switch (getMode()) switch (getMode())
{ {
@@ -52,8 +51,14 @@ namespace UserInterface
break; break;
case Mode::Starred: case Mode::Starred:
tracks = feedbackService.getStarredTracks(LmsApp->getUserId(), getFilters().getClusterIds(), range); {
Feedback::IFeedbackService::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setRange(range);
params.setUser(LmsApp->getUserId());
tracks = feedbackService.findStarredTracks(params);
break; break;
}
case TrackCollector::Mode::RecentlyPlayed: case TrackCollector::Mode::RecentlyPlayed:
tracks = scrobblingService.getRecentTracks(LmsApp->getUserId(), getFilters().getClusterIds(), range); tracks = scrobblingService.getRecentTracks(LmsApp->getUserId(), getFilters().getClusterIds(), range);
@@ -120,7 +125,7 @@ namespace UserInterface
Track::FindParameters params; Track::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusterIds());
params.setSortMethod(TrackSortMethod::Random); params.setSortMethod(TrackSortMethod::Random);
params.setRange({ 0, getMaxCount() }); params.setRange(Range{ 0, getMaxCount() });
{ {
auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
+3 -1
View File
@@ -19,6 +19,8 @@
#pragma once #pragma once
#include <optional>
#include "services/database/Object.hpp" #include "services/database/Object.hpp"
#include "services/database/TrackId.hpp" #include "services/database/TrackId.hpp"
#include "DatabaseCollectorBase.hpp" #include "DatabaseCollectorBase.hpp"
@@ -35,7 +37,7 @@ namespace UserInterface
public: public:
using DatabaseCollectorBase::DatabaseCollectorBase; using DatabaseCollectorBase::DatabaseCollectorBase;
Database::RangeResults<Database::TrackId> get(Database::Range range); Database::RangeResults<Database::TrackId> get(std::optional<Database::Range> range = std::nullopt);
void reset() { _randomTracks.reset(); } void reset() { _randomTracks.reset(); }
private: private:
+5 -3
View File
@@ -186,11 +186,13 @@ namespace UserInterface
params.setClusters(_filters.getClusterIds()); params.setClusters(_filters.getClusterIds());
params.setTrackList(_trackListId); params.setTrackList(_trackListId);
params.setSortMethod(Database::TrackSortMethod::TrackList); params.setSortMethod(Database::TrackSortMethod::TrackList);
params.setRange({ static_cast<std::size_t>(_container->getCount()), _batchSize }); params.setRange(Database::Range{ static_cast<std::size_t>(_container->getCount()), _batchSize });
params.setDistinct(false); params.setDistinct(false);
for (const Track::pointer& track : Database::Track::find(LmsApp->getDbSession(), params).results) Database::Track::find(LmsApp->getDbSession(), params, [this](const Track::pointer& track)
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters)); {
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
});
} }
} // namespace UserInterface } // namespace UserInterface
+87 -95
View File
@@ -33,114 +33,106 @@
using namespace Database; using namespace Database;
namespace UserInterface { namespace UserInterface
Tracks::Tracks(Filters& filters, PlayQueueController& playQueueController)
: Template {Wt::WString::tr("Lms.Explore.Tracks.template")}
, _filters {filters}
, _playQueueController {playQueueController}
, _trackCollector {filters, _defaultMode, _maxCount}
{ {
addFunction("tr", &Wt::WTemplate::Functions::tr); Tracks::Tracks(Filters& filters, PlayQueueController& playQueueController)
addFunction("id", &Wt::WTemplate::Functions::id); : Template{ Wt::WString::tr("Lms.Explore.Tracks.template") }
, _filters{ filters }
, _playQueueController{ playQueueController }
, _trackCollector{ filters, _defaultMode, _maxCount }
{
addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("id", &Wt::WTemplate::Functions::id);
auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, TrackCollector::Mode mode) auto bindMenuItem{ [this](const std::string& var, const Wt::WString& title, TrackCollector::Mode mode)
{ {
auto *menuItem {bindNew<Wt::WPushButton>(var, title)}; auto* menuItem {bindNew<Wt::WPushButton>(var, title)};
menuItem->clicked().connect([=] menuItem->clicked().connect([=]
{ {
refreshView(mode); refreshView(mode);
_currentActiveItem->removeStyleClass("active"); _currentActiveItem->removeStyleClass("active");
menuItem->addStyleClass("active"); menuItem->addStyleClass("active");
_currentActiveItem = menuItem; _currentActiveItem = menuItem;
}); });
if (mode == _defaultMode) if (mode == _defaultMode)
{ {
_currentActiveItem = menuItem; _currentActiveItem = menuItem;
_currentActiveItem->addStyleClass("active"); _currentActiveItem->addStyleClass("active");
} }
}}; } };
bindMenuItem("random", Wt::WString::tr("Lms.Explore.random"), TrackCollector::Mode::Random); bindMenuItem("random", Wt::WString::tr("Lms.Explore.random"), TrackCollector::Mode::Random);
bindMenuItem("starred", Wt::WString::tr("Lms.Explore.starred"), TrackCollector::Mode::Starred); bindMenuItem("starred", Wt::WString::tr("Lms.Explore.starred"), TrackCollector::Mode::Starred);
bindMenuItem("recently-played", Wt::WString::tr("Lms.Explore.recently-played"), TrackCollector::Mode::RecentlyPlayed); bindMenuItem("recently-played", Wt::WString::tr("Lms.Explore.recently-played"), TrackCollector::Mode::RecentlyPlayed);
bindMenuItem("most-played", Wt::WString::tr("Lms.Explore.most-played"), TrackCollector::Mode::MostPlayed); bindMenuItem("most-played", Wt::WString::tr("Lms.Explore.most-played"), TrackCollector::Mode::MostPlayed);
bindMenuItem("recently-added", Wt::WString::tr("Lms.Explore.recently-added"), TrackCollector::Mode::RecentlyAdded); bindMenuItem("recently-added", Wt::WString::tr("Lms.Explore.recently-added"), TrackCollector::Mode::RecentlyAdded);
bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), TrackCollector::Mode::All); bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), TrackCollector::Mode::All);
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
->clicked().connect([=] ->clicked().connect([=]
{ {
_playQueueController.processCommand(PlayQueueController::Command::Play, getAllTracks()); _playQueueController.processCommand(PlayQueueController::Command::Play, getAllTracks());
}); });
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([=]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllTracks()); _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllTracks());
}); });
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([=]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllTracks()); _playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllTracks());
}); });
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([=]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllTracks()); _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllTracks());
}); });
_container = bindNew<InfiniteScrollingContainer>("tracks", Wt::WString::tr("Lms.Explore.Tracks.template.entry-container")); _container = bindNew<InfiniteScrollingContainer>("tracks", Wt::WString::tr("Lms.Explore.Tracks.template.entry-container"));
_container->onRequestElements.connect([this] _container->onRequestElements.connect([this]
{ {
addSome(); addSome();
}); });
filters.updated().connect([this] filters.updated().connect([this]
{ {
refreshView(); refreshView();
}); });
refreshView(_trackCollector.getMode()); refreshView(_trackCollector.getMode());
} }
void void Tracks::refreshView()
Tracks::refreshView() {
{ _container->reset();
_container->reset(); _trackCollector.reset();
_trackCollector.reset(); }
}
void void Tracks::refreshView(TrackCollector::Mode mode)
Tracks::refreshView(TrackCollector::Mode mode) {
{ _trackCollector.setMode(mode);
_trackCollector.setMode(mode); refreshView();
refreshView(); }
}
void void Tracks::addSome()
Tracks::addSome() {
{ auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const auto trackIds {_trackCollector.get(Range {static_cast<std::size_t>(_container->getCount()), _batchSize})}; const auto trackIds{ _trackCollector.get(Range {static_cast<std::size_t>(_container->getCount()), _batchSize}) };
for (const TrackId trackId : trackIds.results)
{
if (const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)})
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
}
}
std::vector<Database::TrackId>
Tracks::getAllTracks()
{
RangeResults<TrackId> trackIds {_trackCollector.get(Range {})};
return std::move(trackIds.results);
}
for (const TrackId trackId : trackIds.results)
{
if (const Track::pointer track{ Track::find(LmsApp->getDbSession(), trackId) })
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
}
}
std::vector<Database::TrackId> Tracks::getAllTracks()
{
RangeResults<TrackId> trackIds{ _trackCollector.get() };
return std::move(trackIds.results);
}
} // namespace UserInterface } // namespace UserInterface