UI: added away to filter all the views by music library

This commit is contained in:
emeric
2024-04-28 23:37:59 +02:00
parent 83c71ee90c
commit 391d859272
35 changed files with 558 additions and 305 deletions
+23 -4
View File
@@ -43,17 +43,19 @@ namespace lms::db
query.groupBy("c.id");
if (params.track.isValid() || params.release.isValid())
{
query.join("track_cluster t_c ON t_c.cluster_id = c.id");
query.join("track t ON t.id = t_c.track_id");
}
if (!params.clusterTypeName.empty())
query.join("cluster_type c_t ON c_t.id = c.cluster_type_id");
if (params.track.isValid())
query.where("t.id = ?").bind(params.track);
query.where("t_c.track_id = ?").bind(params.track);
if (params.release.isValid())
{
query.join("track t ON t.id = t_c.track_id");
query.where("t.release_id = ?").bind(params.release);
}
assert(!params.clusterType.isValid() || params.clusterTypeName.empty());
if (params.clusterType.isValid())
@@ -61,6 +63,17 @@ namespace lms::db
else if (!params.clusterTypeName.empty())
query.where("c_t.name = ?").bind(params.clusterTypeName);
switch (params.sortMethod)
{
case ClusterSortMethod::None:
break;
case ClusterSortMethod::Name:
query.orderBy("c.name COLLATE NOCASE");
break;
}
query.groupBy("c.id");
return query;
}
@@ -208,6 +221,12 @@ namespace lms::db
return utils::execRangeQuery<ClusterTypeId>(query, range);
}
void ClusterType::find(Session& session, const std::function<void(const pointer&)>& func)
{
auto query{ session.getDboSession()->find<ClusterType>() };
return utils::forEachQueryResult(query, func);
}
ClusterType::pointer ClusterType::find(Session& session, std::string_view name)
{
session.checkReadTransaction();
+9 -2
View File
@@ -34,7 +34,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 57 };
static constexpr Version LMS_DATABASE_VERSION{ 58 };
}
VersionInfo::VersionInfo()
@@ -455,11 +455,17 @@ SELECT
void migrateFromV56(Session& session)
{
// Make sure we remove all the previoulsy created index, the createIndexesIfNeeded will recreate them all
std::vector<std::string> indexeNames{ utils::fetchQueryResults(session.getDboSession()->query<std::string>(R"(SELECT name FROM sqlite_master WHERE type = 'index' AND name LIKE '%_idx')")) };
std::vector<std::string> indexeNames{ utils::fetchQueryResults(session.getDboSession()->query<std::string>(R"(SELECT name FROM sqlite_master WHERE type = 'index' AND name LIKE '%_idx')")) };
for (const auto& indexName : indexeNames)
session.getDboSession()->execute("DROP INDEX " + indexName);
}
void migrateFromV57(Session& session)
{
// useless index
session.getDboSession()->execute("DROP INDEX cluster_name_idx");
}
bool doDbMigration(Session& session)
{
static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -494,6 +500,7 @@ SELECT
{54, migrateFromV54},
{55, migrateFromV55},
{56, migrateFromV56},
{57, migrateFromV57},
};
bool migrationPerformed{};
-1
View File
@@ -172,7 +172,6 @@ namespace lms::db
_session.execute("CREATE INDEX IF NOT EXISTS auth_token_value_idx ON auth_token(value)");
_session.execute("CREATE INDEX IF NOT EXISTS cluster_cluster_type_idx ON cluster(cluster_type_id)");
_session.execute("CREATE INDEX IF NOT EXISTS cluster_name_idx ON cluster(name)");
_session.execute("CREATE INDEX IF NOT EXISTS cluster_type_name_idx ON cluster_type(name)");
_session.execute("CREATE INDEX IF NOT EXISTS listen_backend_idx ON listen(backend)");
+81 -45
View File
@@ -35,6 +35,79 @@
namespace lms::db
{
namespace
{
template <typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const TrackList::FindParameters& params)
{
auto query{ session.getDboSession()->query<ResultType>("SELECT DISTINCT " + std::string{ itemToSelect } + " FROM tracklist t_l") };
if (!params.clusters.empty() || params.mediaLibrary.isValid())
query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id");
if (params.mediaLibrary.isValid())
query.join("track t ON t.id = t_l_e.track_id");
if (params.mediaLibrary.isValid())
query.where("t.media_library_id = ?").bind(params.mediaLibrary);
if (params.user.isValid())
query.where("t_l.user_id = ?").bind(params.user);
if (params.type)
query.where("t_l.type = ?").bind(*params.type);
if (!params.clusters.empty())
{
std::ostringstream oss;
oss << "t_l_e.track_id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (const ClusterId clusterId : params.clusters)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
query.where(oss.str());
}
switch (params.sortMethod)
{
case TrackListSortMethod::None:
break;
case TrackListSortMethod::Name:
query.orderBy("t_l.name COLLATE NOCASE");
break;
case TrackListSortMethod::LastModifiedDesc:
query.orderBy("t_l.last_modified_date_time DESC");
break;
}
return query;
}
template <typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, const TrackList::FindParameters& params)
{
std::string_view itemToSelect;
if constexpr (std::is_same_v<ResultType, TrackListId>)
itemToSelect = "t_l.id";
else if constexpr (std::is_same_v<ResultType, Wt::Dbo::ptr<TrackList>>)
itemToSelect = "t_l";
else
static_assert("Unhandled type");
return createQuery<ResultType>(session, itemToSelect, params);
}
}
TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
: _name{ name }
, _type{ type }
@@ -58,7 +131,6 @@ namespace lms::db
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM tracklist"));
}
TrackList::pointer TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId)
{
session.checkReadTransaction();
@@ -73,53 +145,17 @@ namespace lms::db
RangeResults<TrackListId> TrackList::find(Session& session, const FindParameters& params)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<TrackListId>("SELECT DISTINCT t_l.id FROM tracklist t_l") };
if (params.user.isValid())
query.where("t_l.user_id = ?").bind(params.user);
if (params.type)
query.where("t_l.type = ?").bind(*params.type);
if (!params.clusters.empty())
{
query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id");
query.join("track t ON t.id = t_l_e.track_id");
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (const ClusterId clusterId : params.clusters)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
query.where(oss.str());
}
switch (params.sortMethod)
{
case TrackListSortMethod::None:
break;
case TrackListSortMethod::Name:
query.orderBy("t_l.name COLLATE NOCASE");
break;
case TrackListSortMethod::LastModifiedDesc:
query.orderBy("t_l.last_modified_date_time DESC");
break;
}
auto query{ createQuery<TrackListId>(session, params) };
return utils::execRangeQuery<TrackListId>(query, params.range);
}
void TrackList::find(Session& session, const FindParameters& params, const std::function<void(const TrackList::pointer&)>& func)
{
session.checkReadTransaction();
auto query{ createQuery<Wt::Dbo::ptr<TrackList>>(session, params) };
utils::forEachQueryRangeResult(query, params.range, func);
}
TrackList::pointer TrackList::find(Session& session, TrackListId id)
{
session.checkReadTransaction();
@@ -20,6 +20,7 @@
#pragma once
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
@@ -67,7 +68,7 @@ namespace lms::db
ReleaseId release; // artists involved in this release
MediaLibraryId mediaLibrary; // artists that belong to this library
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setClusters(std::span<const ClusterId> _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); 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& setSortMethod(ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
@@ -46,12 +46,14 @@ namespace lms::db
struct FindParameters
{
std::optional<Range> range;
ClusterSortMethod sortMethod;
ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type
std::string clusterTypeName; // if non empty, clusters that belong to this cluster type
TrackId track; // if set, clusters involved in this track
ReleaseId release; // if set, clusters involved in this release
FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
FindParameters& setSortMethod(ClusterSortMethod _method) { sortMethod = _method; return *this; }
FindParameters& setClusterType(ClusterTypeId _clusterType) { clusterType = _clusterType; return *this; }
FindParameters& setClusterTypeName(std::string_view _name) { clusterTypeName = _name; return *this; }
FindParameters& setTrack(TrackId _track) { track = _track; return *this; }
@@ -119,6 +121,7 @@ namespace lms::db
// Getters
static std::size_t getCount(Session& session);
static RangeResults<ClusterTypeId> findIds(Session& session, std::optional<Range> range = std::nullopt);
static void find(Session& session, const std::function<void(const pointer&)>& func);
static pointer find(Session& session, std::string_view name);
static pointer find(Session& session, ClusterTypeId id);
static RangeResults<ClusterTypeId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
@@ -21,6 +21,7 @@
#include <filesystem>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
@@ -96,7 +97,7 @@ namespace lms::db
std::string releaseType; // If set, albums that has this release type
MediaLibraryId mediaLibrary; // If set, releases that has at least a track in this library
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setClusters(std::span<const ClusterId> _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; }
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
FindParameters& setSortMethod(ReleaseSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
+2 -1
View File
@@ -23,6 +23,7 @@
#include <filesystem>
#include <ostream>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <utility>
@@ -80,7 +81,7 @@ namespace lms::db
std::optional<int> discNumber; // matching this disc number
MediaLibraryId mediaLibrary; // If set, tracks in this library
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setClusters(std::span<const ClusterId> _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); 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& setSortMethod(TrackSortMethod _method) { sortMethod = _method; return *this; }
@@ -20,6 +20,7 @@
#pragma once
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
@@ -28,6 +29,7 @@
#include <Wt/WDateTime.h>
#include "database/ClusterId.hpp"
#include "database/MediaLibraryId.hpp"
#include "database/Object.hpp"
#include "database/TrackId.hpp"
#include "database/TrackListId.hpp"
@@ -57,18 +59,21 @@ namespace lms::db
std::optional<Range> range;
std::optional<TrackListType> type;
UserId user; // only tracklists owned by this user
MediaLibraryId mediaLibrary; // only tracklists that have songs in this media library
TrackListSortMethod sortMethod{ TrackListSortMethod::None };
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setClusters(std::span<const ClusterId> _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; }
FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
FindParameters& setType(TrackListType _type) { type = _type; return *this; }
FindParameters& setUser(UserId _user) { user = _user; return *this; }
FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; }
FindParameters& setSortMethod(TrackListSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
};
static std::size_t getCount(Session& session);
static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId);
static pointer find(Session& session, TrackListId tracklistId);
static RangeResults<TrackListId> find(Session& session, const FindParameters& params);
static void find(Session& session, const FindParameters& params, const std::function<void(const TrackList::pointer&)>& func);
// Accessors
std::string_view getName() const { return _name; }
@@ -116,6 +116,12 @@ namespace lms::db
StarredDateDesc,
};
enum class ClusterSortMethod
{
None,
Name,
};
enum class ReleaseSortMethod
{
None,
+45 -16
View File
@@ -137,11 +137,11 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters {}.setClusters({cluster1.getId()})) };
auto tracks{ Track::findIds(session, Track::FindParameters {}.setClusters(std::initializer_list<ClusterId>{cluster1.getId()})) };
ASSERT_EQ(tracks.results.size(), 1);
EXPECT_EQ(tracks.results.front(), track.getId());
tracks = Track::findIds(session, Track::FindParameters{}.setClusters({ cluster2.getId() }));
tracks = Track::findIds(session, Track::FindParameters{}.setClusters(std::initializer_list<ClusterId>{ cluster2.getId() }));
EXPECT_EQ(tracks.results.size(), 0);
}
@@ -237,6 +237,35 @@ namespace lms::db::tests
}
}
TEST_F(DatabaseFixture, ClusterType)
{
{
auto transaction{ session.createReadTransaction() };
bool visited{};
ClusterType::find(session, [&](const ClusterType::pointer&)
{
visited = true;
});
EXPECT_FALSE(visited);
}
ScopedClusterType clusterType1{ session, "MyClusterType1" };
ScopedClusterType clusterType2{ session, "MyClusterType2" };
{
auto transaction{ session.createReadTransaction() };
std::vector<ClusterTypeId> visitedClusterTypes;
ClusterType::find(session, [&](const ClusterType::pointer& clusterType)
{
visitedClusterTypes.push_back(clusterType->getId());
});
ASSERT_EQ(visitedClusterTypes.size(), 2);
EXPECT_EQ(visitedClusterTypes[0], clusterType1->getId());
EXPECT_EQ(visitedClusterTypes[1], clusterType2->getId());
}
}
TEST_F(DatabaseFixture, ClusterType_singleTrack)
{
@@ -285,7 +314,7 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
ASSERT_EQ(Cluster::findOrphanIds(session).results.size(), 2);
EXPECT_EQ(Release::find(session, Release::FindParameters{}.setClusters({ unusedCluster.getId() })).results.size(), 0);
EXPECT_EQ(Release::find(session, Release::FindParameters{}.setClusters(std::initializer_list<ClusterId>{ unusedCluster.getId() })).results.size(), 0);
EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1);
EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0);
EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0);
@@ -321,7 +350,7 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({cluster.getId()})) };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters(std::initializer_list<ClusterId>{cluster.getId()})) };
ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId());
}
@@ -329,7 +358,7 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({unusedCluster.getId()})) };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters(std::initializer_list<ClusterId>{unusedCluster.getId()})) };
EXPECT_EQ(releases.results.size(), 0);
}
@@ -375,12 +404,12 @@ namespace lms::db::tests
{
auto transaction{ session.createWriteTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster1.getId()})) };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(std::initializer_list<ClusterId>{cluster1.getId()})) };
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId());
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })).results.size(), 0);
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.size(), 0);
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list<ClusterId>{ cluster2.getId() })).results.size(), 0);
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list<ClusterId>{ cluster3.getId() })).results.size(), 0);
cluster2.get().modify()->addTrack(track.get());
}
@@ -388,19 +417,19 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster1.getId()})) };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(std::initializer_list<ClusterId>{cluster1.getId()})) };
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId());
artists = Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() }));
artists = Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list<ClusterId>{ cluster2.getId() }));
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId());
artists = Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster1.getId() }));
artists = Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId() }));
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId());
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.size(), 0);
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list<ClusterId>{ cluster3.getId() })).results.size(), 0);
}
}
@@ -429,7 +458,7 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster.getId()})) };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(std::initializer_list<ClusterId>{cluster.getId()})) };
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId());
}
@@ -581,7 +610,7 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster.getId()})) };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(std::initializer_list<ClusterId>{cluster.getId()})) };
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId());
@@ -589,7 +618,7 @@ namespace lms::db::tests
ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId());
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster.getId() }));
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters(std::initializer_list<ClusterId>{ cluster.getId() }));
ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId());
}
@@ -620,7 +649,7 @@ namespace lms::db::tests
ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId());
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster1.getId(), cluster2.getId() }));
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters(std::initializer_list<ClusterId>{ cluster1.getId(), cluster2.getId() }));
ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId());
}
+42 -2
View File
@@ -151,6 +151,46 @@ namespace lms::db::tests
}
}
TEST_F(DatabaseFixture, TrackList_MediaLibrary)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList1{ session, "MytrackList1", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList2{ session, "MytrackList2", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrack track1{ session };
ScopedTrack track2{ session };
ScopedMediaLibrary library{ session };
{
auto transaction{ session.createWriteTransaction() };
session.create<TrackListEntry>(track1.get(), trackList1.get());
session.create<TrackListEntry>(track2.get(), trackList2.get());
track2.get().modify()->setMediaLibrary(library.get());
}
{
auto transaction{ session.createReadTransaction() };
std::vector<TrackListId> visitedTrackLists;
TrackList::find(session, TrackList::FindParameters{}, [&](const TrackList::pointer& trackList)
{
visitedTrackLists.push_back(trackList->getId());
});
ASSERT_EQ(visitedTrackLists.size(), 2);
EXPECT_EQ(visitedTrackLists[0], trackList1->getId());
EXPECT_EQ(visitedTrackLists[1], trackList2->getId());
}
{
auto transaction{ session.createReadTransaction() };
std::vector<TrackListId> visitedTrackLists;
TrackList::find(session, TrackList::FindParameters{}.setMediaLibrary(library->getId()), [&](const TrackList::pointer& trackList)
{
visitedTrackLists.push_back(trackList->getId());
});
ASSERT_EQ(visitedTrackLists.size(), 1);
EXPECT_EQ(visitedTrackLists[0], trackList2->getId());
}
}
TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster)
{
ScopedUser user{ session, "MyUser" };
@@ -163,7 +203,7 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) };
auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters(std::initializer_list<ClusterId>{cluster.getId()})) };
EXPECT_EQ(trackLists.results.size(), 0);
}
@@ -177,7 +217,7 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) };
auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters(std::initializer_list<ClusterId>{cluster.getId()})) };
ASSERT_EQ(trackLists.results.size(), 1);
EXPECT_EQ(trackLists.results.front(), trackList1.getId());
}
@@ -21,6 +21,7 @@
#include <memory>
#include <optional>
#include <span>
#include <boost/asio/io_service.hpp>
#include <Wt/WDateTime.h>
@@ -57,7 +58,7 @@ namespace lms::feedback
db::MediaLibraryId library;
FindParameters& setUser(const db::UserId _user) { user = _user; return *this; }
FindParameters& setClusters(const std::vector<db::ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setClusters(std::span<const db::ClusterId> _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; }
FindParameters& setRange(std::optional<db::Range> _range) { range = _range; return *this; }
FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; }
};
@@ -22,6 +22,7 @@
#include <chrono>
#include <memory>
#include <optional>
#include <span>
#include <boost/asio/io_service.hpp>
#include <Wt/WDateTime.h>
@@ -65,7 +66,7 @@ namespace lms::scrobbling
db::ArtistId artist; // if set, match this artist
FindParameters& setUser(const db::UserId _user) { user = _user; return *this; }
FindParameters& setClusters(const std::vector<db::ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setClusters(std::span<const db::ClusterId> _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; }
FindParameters& setRange(std::optional<db::Range> _range) { range = _range; return *this; }
FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; }
FindParameters& setArtist(db::ArtistId _artist) { artist = _artist; return *this; }
@@ -88,7 +88,7 @@ namespace lms::api::subsonic
if (const Cluster::pointer cluster{ clusterType->getCluster(genre) })
{
Release::FindParameters params;
params.setClusters({ cluster->getId() });
params.setClusters(std::initializer_list<ClusterId>{ cluster->getId() });
params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range);
params.setMediaLibrary(mediaLibraryId);
@@ -280,7 +280,7 @@ namespace lms::api::subsonic
Response::Node& songsByGenreNode{ response.createNode("songsByGenre") };
Track::FindParameters params;
params.setClusters({ cluster->getId() });
params.setClusters(std::initializer_list<ClusterId>{ cluster->getId() });
params.setRange(Range{ offset, count });
params.setMediaLibrary(mediaLibrary);
+5 -9
View File
@@ -87,9 +87,7 @@ namespace lms::ui
return trackListModel->getValue(*row);
}
static
std::shared_ptr<TrackListModel>
createTrackListModel()
static std::shared_ptr<TrackListModel> createTrackListModel()
{
using namespace db;
@@ -102,12 +100,10 @@ namespace lms::ui
params.setUser(LmsApp->getUserId());
params.setSortMethod(TrackListSortMethod::Name);
auto tracklists{ TrackList::find(LmsApp->getDbSession(), params) };
for (const TrackListId trackListId : tracklists.results)
{
const TrackList::pointer trackList{ TrackList::find(LmsApp->getDbSession(), trackListId) };
model->add(Wt::WString::fromUTF8(std::string{ trackList->getName() }), trackListId);
}
TrackList::find(LmsApp->getDbSession(), params, [&](const TrackList::pointer& trackList)
{
model->add(Wt::WString::fromUTF8(std::string{ trackList->getName() }), trackList->getId());
});
return model;
}
+38 -34
View File
@@ -80,7 +80,18 @@ namespace lms::ui::utils
return cover;
}
std::unique_ptr<Wt::WInteractWidget> createCluster(db::ClusterId clusterId, bool canDelete)
std::unique_ptr<Wt::WInteractWidget> createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete)
{
auto res{ std::make_unique<Wt::WText>(Wt::WString{ canDelete ? "<i class=\"fa fa-times-circle\"></i> " : "" } + name, Wt::TextFormat::UnsafeXHTML) };
res->setStyleClass("Lms-badge-cluster badge me-1 " + std::string{ colorStyleClass }); // HACK
res->setToolTip(tooltip, Wt::TextFormat::Plain);
res->setInline(true);
return res;
}
std::unique_ptr<Wt::WInteractWidget> createFilterCluster(db::ClusterId clusterId, bool canDelete)
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
@@ -88,7 +99,7 @@ namespace lms::ui::utils
if (!cluster)
return {};
auto getStyleClass{ [](const db::Cluster::pointer& cluster) -> const char*
auto getStyleClass{ [](const db::Cluster::pointer& cluster) -> std::string_view
{
switch (cluster->getType()->getId().getValue() % 8)
{
@@ -105,14 +116,33 @@ namespace lms::ui::utils
return "bg-primary";
} };
const std::string styleClass{ getStyleClass(cluster) };
auto res{ std::make_unique<Wt::WText>(std::string {} + (canDelete ? "<i class=\"fa fa-times-circle\"></i> " : "") + Wt::WString::fromUTF8(std::string{ cluster->getName() }), Wt::TextFormat::UnsafeXHTML) };
return createFilter(Wt::WString::fromUTF8(std::string{ cluster->getName() }), Wt::WString::fromUTF8(std::string{ cluster->getType()->getName() }), getStyleClass(cluster), canDelete);
}
res->setStyleClass("Lms-badge-cluster badge me-1 " + styleClass); // HACK
res->setToolTip(std::string{ cluster->getType()->getName() }, Wt::TextFormat::Plain);
res->setInline(true);
std::unique_ptr<Wt::WContainerWidget> createFilterClustersForTrack(db::Track::pointer track, Filters& filters)
{
using namespace db;
return res;
std::unique_ptr<Wt::WContainerWidget> clusterContainer{ std::make_unique<Wt::WContainerWidget>() };
// TODO: optimize this
const auto clusterTypes{ ClusterType::findIds(LmsApp->getDbSession()).results };
const auto clusterGroups{ track->getClusterGroups(clusterTypes, 3) };
for (const auto& clusters : clusterGroups)
{
for (const Cluster::pointer& cluster : clusters)
{
const ClusterId clusterId{ cluster->getId() };
Wt::WInteractWidget* entry{ clusterContainer->addWidget(createFilterCluster(clusterId)) };
entry->clicked().connect([&filters, clusterId]
{
filters.add(clusterId);
});
}
}
return clusterContainer;
}
std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<db::ArtistId>& artistIds, std::string_view cssAnchorClass)
@@ -278,30 +308,4 @@ namespace lms::ui::utils
return res;
}
std::unique_ptr<Wt::WContainerWidget> createClustersForTrack(db::Track::pointer track, Filters& filters)
{
using namespace db;
std::unique_ptr<Wt::WContainerWidget> clusterContainer{ std::make_unique<Wt::WContainerWidget>() };
// TODO: optimize this
const auto clusterTypes{ ClusterType::findIds(LmsApp->getDbSession()).results };
const auto clusterGroups{ track->getClusterGroups(clusterTypes, 3) };
for (const auto& clusters : clusterGroups)
{
for (const Cluster::pointer& cluster : clusters)
{
const ClusterId clusterId{ cluster->getId() };
Wt::WInteractWidget* entry{ clusterContainer->addWidget(createCluster(clusterId)) };
entry->clicked().connect([&filters, clusterId]
{
filters.add(clusterId);
});
}
}
return clusterContainer;
}
}
+4 -3
View File
@@ -26,6 +26,7 @@
#include <Wt/WContainerWidget.h>
#include <Wt/WImage.h>
#include <Wt/WInteractWidget.h>
#include <Wt/WString.h>
#include "database/ArtistId.hpp"
#include "database/ClusterId.hpp"
@@ -56,7 +57,9 @@ namespace lms::ui::utils
std::unique_ptr<Wt::WImage> createCover(db::ReleaseId releaseId, CoverResource::Size size);
std::unique_ptr<Wt::WImage> createCover(db::TrackId trackId, CoverResource::Size size);
std::unique_ptr<Wt::WInteractWidget> createCluster(db::ClusterId clusterId, bool canDelete = false);
std::unique_ptr<Wt::WInteractWidget> createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete = false);
std::unique_ptr<Wt::WInteractWidget> createFilterCluster(db::ClusterId clusterId, bool canDelete = false);
std::unique_ptr<Wt::WContainerWidget> createFilterClustersForTrack(db::ObjectPtr<db::Track> track, Filters& filters);
std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<db::ArtistId>& artistIds, std::string_view cssAnchorClass = "link-success");
std::unique_ptr<Wt::WContainerWidget> createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector<db::ArtistId>& artistIds, std::string_view cssAnchorClass = "link-success");
@@ -67,6 +70,4 @@ namespace lms::ui::utils
Wt::WLink createReleaseLink(db::ObjectPtr<db::Release> release);
std::unique_ptr<Wt::WAnchor> createReleaseAnchor(db::ObjectPtr<db::Release> release, bool setText = true);
std::unique_ptr<Wt::WAnchor> createTrackListAnchor(db::ObjectPtr<db::TrackList> trackList, bool setText = true);
std::unique_ptr<Wt::WContainerWidget> createClustersForTrack(db::ObjectPtr<db::Track> track, Filters& filters);
}
+43 -48
View File
@@ -23,60 +23,55 @@
#include <Wt/WStringListModel.h>
namespace lms::ui {
// Helper class
template <typename T>
class ValueStringModel : public Wt::WStringListModel
namespace lms::ui
{
public:
T getValue(std::size_t row) const
{
return Wt::cpp17::any_cast<T>(data(index(static_cast<int>(row), 0), Wt::ItemDataRole::User));
}
// Helper class
template <typename T>
class ValueStringModel : public Wt::WStringListModel
{
public:
T getValue(std::size_t row) const
{
return Wt::cpp17::any_cast<T>(data(index(static_cast<int>(row), 0), Wt::ItemDataRole::User));
}
Wt::WString getString(std::size_t row) const
{
return Wt::cpp17::any_cast<Wt::WString>(data(index(static_cast<int>(row), 0), Wt::ItemDataRole::Display));
}
Wt::WString getString(std::size_t row) const
{
return Wt::cpp17::any_cast<Wt::WString>(data(index(static_cast<int>(row), 0), Wt::ItemDataRole::Display));
}
std::optional<std::size_t>
getRowFromString(const Wt::WString& value)
{
for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i)
{
if (getString(i) == value)
return i;
}
std::optional<std::size_t> getRowFromString(const Wt::WString& value)
{
for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i)
{
if (getString(i) == value)
return i;
}
return std::nullopt;
}
return std::nullopt;
}
std::optional<std::size_t>
getRowFromValue(const T& value)
{
for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i)
{
if (getValue(i) == value)
return i;
}
std::optional<std::size_t> getRowFromValue(const T& value)
{
for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i)
{
if (getValue(i) == value)
return i;
}
return std::nullopt;
}
return std::nullopt;
}
void
add(const Wt::WString& str, const T& value)
{
insertRows(rowCount(), 1);
setData(rowCount() - 1, 0, value, Wt::ItemDataRole::User);
setData(rowCount() - 1, 0, str, Wt::ItemDataRole::Display);
}
void
clear()
{
removeRows(0, rowCount());
}
};
void add(const Wt::WString& str, const T& value)
{
insertRows(rowCount(), 1);
setData(rowCount() - 1, 0, value, Wt::ItemDataRole::User);
setData(rowCount() - 1, 0, str, Wt::ItemDataRole::Display);
}
void clear()
{
removeRows(0, rowCount());
}
};
} // namespace lms::ui
+15 -7
View File
@@ -39,6 +39,7 @@ namespace lms::ui
scrobbling::IScrobblingService& scrobblingService{ *core::Service<scrobbling::IScrobblingService>::get() };
const Range range{ getActualRange(requestedRange) };
const Filters& filters{ getFilters() };
RangeResults<ArtistId> artists;
@@ -52,7 +53,8 @@ namespace lms::ui
{
feedback::IFeedbackService::ArtistFindParameters params;
params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds());
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType);
params.setSortMethod(ArtistSortMethod::StarredDateDesc);
params.setRange(range);
@@ -64,7 +66,8 @@ namespace lms::ui
{
scrobbling::IScrobblingService::ArtistFindParameters params;
params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds());
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType);
params.setRange(range);
@@ -76,7 +79,8 @@ namespace lms::ui
{
scrobbling::IScrobblingService::ArtistFindParameters params;
params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds());
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType);
params.setRange(range);
@@ -87,7 +91,8 @@ namespace lms::ui
case Mode::RecentlyAdded:
{
Artist::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType);
params.setSortMethod(ArtistSortMethod::LastWritten);
params.setRange(range);
@@ -102,7 +107,8 @@ namespace lms::ui
case Mode::Search:
{
Artist::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setKeywords(getSearchKeywords());
params.setLinkType(_linkType);
params.setRange(range);
@@ -117,7 +123,8 @@ namespace lms::ui
case Mode::All:
{
Artist::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType);
params.setSortMethod(ArtistSortMethod::SortName);
params.setRange(range);
@@ -143,7 +150,8 @@ namespace lms::ui
if (!_randomArtists)
{
Artist::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setLinkType(_linkType);
params.setSortMethod(ArtistSortMethod::Random);
params.setRange(Range{ 0, getMaxCount() });
+7 -4
View File
@@ -138,7 +138,7 @@ namespace lms::ui
for (const db::Cluster::pointer& cluster : clusters)
{
const db::ClusterId clusterId = cluster->getId();
Wt::WInteractWidget* entry{ clusterContainers->addWidget(utils::createCluster(clusterId)) };
Wt::WInteractWidget* entry{ clusterContainers->addWidget(utils::createFilterCluster(clusterId)) };
entry->clicked().connect([this, clusterId]
{
_filters.add(clusterId);
@@ -198,7 +198,8 @@ namespace lms::ui
_releaseContainers.clear();
Release::FindParameters params;
params.setClusters(_filters.getClusterIds());
params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setArtist(_artistId, { TrackArtistLinkType::ReleaseArtist }, {});
params.setSortMethod(ReleaseSortMethod::OriginalDateDesc);
@@ -257,7 +258,8 @@ namespace lms::ui
_appearsOnReleaseContainer = {};
Release::FindParameters params;
params.setClusters(_filters.getClusterIds());
params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setArtist(_artistId, types, { TrackArtistLinkType::ReleaseArtist });
params.setSortMethod(ReleaseSortMethod::OriginalDateDesc);
@@ -343,7 +345,8 @@ namespace lms::ui
const Range range{ static_cast<std::size_t>(_trackContainer->getCount()), _tracksBatchSize };
Track::FindParameters params;
params.setClusters(_filters.getClusterIds());
params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setArtist(_artistId);
params.setRange(range);
params.setSortMethod(TrackSortMethod::Name);
+1 -1
View File
@@ -58,7 +58,7 @@ namespace lms::ui
protected:
Range getActualRange(std::optional<Range> range) const;
std::size_t getMaxCount() const;
Filters& getFilters() { return _filters; }
const Filters& getFilters() { return _filters; }
const std::vector<std::string_view>& getSearchKeywords() const { return _searchKeywords; }
private:
+148 -85
View File
@@ -19,21 +19,79 @@
#include "Filters.hpp"
#include <variant>
#include <Wt/WComboBox.h>
#include <Wt/WDialog.h>
#include <Wt/WPushButton.h>
#include <Wt/WTemplate.h>
#include "database/Cluster.hpp"
#include "database/MediaLibrary.hpp"
#include "database/Session.hpp"
#include "common/ValueStringModel.hpp"
#include "LmsApplication.hpp"
#include "Utils.hpp"
#include "ModalManager.hpp"
namespace lms::ui
namespace lms::ui
{
using namespace db;
namespace
{
struct MediaLibraryTag {};
using TypeVariant = std::variant<db::ClusterTypeId, MediaLibraryTag>;
using TypeModel = ValueStringModel<TypeVariant>;
std::unique_ptr<TypeModel> createTypeModel()
{
auto typeModel{ std::make_unique<TypeModel>() };
typeModel->add(Wt::WString::tr("Lms.Explore.media-library"), MediaLibraryTag{});
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
db::ClusterType::find(LmsApp->getDbSession(), [&](const db::ClusterType::pointer& clusterType)
{
typeModel->add(Wt::WString::fromUTF8(std::string{ clusterType->getName() }), clusterType->getId());
});
return typeModel;
}
using ValueVariant = std::variant<db::ClusterId, db::MediaLibraryId>;
using ValueModel = ValueStringModel<ValueVariant>;
std::unique_ptr<ValueModel> createValueModel(TypeVariant type)
{
db::Session& session{ LmsApp->getDbSession() };
auto valueModel{ std::make_unique<ValueModel>() };
auto transaction{ session.createReadTransaction() };
if (std::holds_alternative<MediaLibraryTag>(type))
{
db::MediaLibrary::find(session, [&](const db::MediaLibrary::pointer& library)
{
valueModel->add(Wt::WString::fromUTF8(std::string{ library->getName() }), library->getId());
});
}
else if (const db::ClusterTypeId * clusterTypeId{ std::get_if<db::ClusterTypeId>(&type) })
{
db::Cluster::FindParameters params;
params.setClusterType(*clusterTypeId);
params.setSortMethod(db::ClusterSortMethod::Name);
db::Cluster::find(session, params, [&](const db::Cluster::pointer& cluster)
{
valueModel->add(Wt::WString::fromUTF8(std::string{ cluster->getName() }), cluster->getId());
});
}
return valueModel;
}
}
void Filters::showDialog()
{
@@ -43,32 +101,27 @@ namespace lms::ui
dialog->addFunction("id", &Wt::WTemplate::Functions::id);
Wt::WComboBox* typeCombo{ dialog->bindNew<Wt::WComboBox>("type") };
const std::shared_ptr<TypeModel> typeModel{ createTypeModel() };
typeCombo->setModel(typeModel);
Wt::WComboBox* valueCombo{ dialog->bindNew<Wt::WComboBox>("value") };
Wt::WPushButton* addBtn{ dialog->bindNew<Wt::WPushButton>("add-btn", Wt::WString::tr("Lms.Explore.add-filter")) };
addBtn->clicked().connect([this, typeCombo, valueCombo, dialogPtr]
addBtn->clicked().connect([this, valueCombo, dialogPtr]
{
const std::string type{ typeCombo->valueText().toUTF8() };
const std::string value{ valueCombo->valueText().toUTF8() };
// TODO use a model to store the cluster.id() values
ClusterId clusterId{};
const auto valueModel{ std::static_pointer_cast<ValueModel>(valueCombo->model()) };
const ValueVariant value{ valueModel->getValue(valueCombo->currentIndex()) };
if (const db::MediaLibraryId * mediaLibraryId{ std::get_if<db::MediaLibraryId>(&value) })
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
ClusterType::pointer clusterType{ ClusterType::find(LmsApp->getDbSession(), type) };
if (!clusterType)
return;
Cluster::pointer cluster{ clusterType->getCluster(value) };
if (!cluster)
return;
clusterId = cluster->getId();
set(*mediaLibraryId);
}
else if (const db::ClusterId * clusterId{ std::get_if<db::ClusterId>(&value) })
{
add(*clusterId);
}
add(clusterId);
// TODO
LmsApp->getModalManager().dispose(dialogPtr);
});
@@ -78,79 +131,20 @@ namespace lms::ui
LmsApp->getModalManager().dispose(dialogPtr);
});
// Populate data
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const auto clusterTypesIds{ ClusterType::findUsed(LmsApp->getDbSession()) };
for (const ClusterTypeId clusterTypeId : clusterTypesIds.results)
typeCombo->activated().connect([valueCombo, typeModel](int row)
{
const auto clusterType{ ClusterType::find(LmsApp->getDbSession(), clusterTypeId) };
typeCombo->addItem(Wt::WString::fromUTF8(std::string{ clusterType->getName() }));
}
if (!clusterTypesIds.results.empty())
{
const auto clusterType{ ClusterType::find(LmsApp->getDbSession(), clusterTypesIds.results.front()) };
for (const Cluster::pointer& cluster : clusterType->getClusters())
{
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end())
valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() }));
}
}
}
typeCombo->changed().connect([this, typeCombo, valueCombo]
{
const std::string name{ typeCombo->valueText().toUTF8() };
const TypeVariant type{ typeModel->getValue(row) };
const std::shared_ptr<ValueModel> valueModel{ createValueModel(type) };
valueCombo->clear();
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
auto clusterType{ ClusterType::find(LmsApp->getDbSession(), name) };
for (const Cluster::pointer& cluster : clusterType->getClusters())
{
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end())
valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() }));
}
valueCombo->setModel(valueModel);
});
typeCombo->activated().emit(0); // force emit to refresh the type combo model
LmsApp->getModalManager().show(std::move(dialog));
}
void Filters::add(ClusterId clusterId)
{
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), clusterId) != std::cend(_clusterIds))
return;
Wt::WInteractWidget* filter{};
{
auto cluster{ utils::createCluster(clusterId, true) };
if (!cluster)
return;
filter = _filters->addWidget(std::move(cluster));
}
_clusterIds.push_back(clusterId);
filter->clicked().connect([this, filter, clusterId]
{
_filters->removeWidget(filter);
_clusterIds.erase(std::remove_if(std::begin(_clusterIds), std::end(_clusterIds), [clusterId](ClusterId id) { return id == clusterId; }), std::end(_clusterIds));
_sigUpdated.emit();
});
LmsApp->notifyMsg(Notification::Type::Info,
Wt::WString::tr("Lms.Explore.filters"),
Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds{ 2 });
_sigUpdated.emit();
}
Filters::Filters()
: Wt::WTemplate{ Wt::WString::tr("Lms.Explore.template.filters") }
{
@@ -162,4 +156,73 @@ namespace lms::ui
_filters = bindNew<Wt::WContainerWidget>("clusters");
}
void Filters::add(db::ClusterId clusterId)
{
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), clusterId) != std::cend(_clusterIds))
return;
Wt::WInteractWidget* filter{};
{
auto cluster{ utils::createFilterCluster(clusterId, true) };
if (!cluster)
return;
filter = _filters->addWidget(std::move(cluster));
}
_clusterIds.push_back(clusterId);
filter->clicked().connect([this, filter, clusterId]
{
_filters->removeWidget(filter);
_clusterIds.erase(std::remove_if(std::begin(_clusterIds), std::end(_clusterIds), [clusterId](db::ClusterId id) { return id == clusterId; }), std::end(_clusterIds));
_sigUpdated.emit();
});
emitFilterAddedNotification();
}
void Filters::set(db::MediaLibraryId mediaLibraryId)
{
if (_mediaLibraryFilter)
{
_filters->removeWidget(_mediaLibraryFilter);
_mediaLibraryFilter = nullptr;
_mediaLibraryId = db::MediaLibraryId{};
}
std::string libraryName;
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const auto library{ db::MediaLibrary::find(LmsApp->getDbSession(), mediaLibraryId) };
if (!library)
return;
libraryName = library->getName();
}
_mediaLibraryId = mediaLibraryId;
_mediaLibraryFilter = _filters->addWidget(utils::createFilter(Wt::WString::fromUTF8(libraryName), Wt::WString::tr("Lms.Explore.media-library"), "bg-primary", true));
_mediaLibraryFilter->clicked().connect(_mediaLibraryFilter, [this]
{
_filters->removeWidget(_mediaLibraryFilter);
_mediaLibraryId = db::MediaLibraryId{};
_mediaLibraryFilter = nullptr;
_sigUpdated.emit();
});
emitFilterAddedNotification();
}
void Filters::emitFilterAddedNotification()
{
LmsApp->notifyMsg(Notification::Type::Info,
Wt::WString::tr("Lms.Explore.filters"),
Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds{ 2 });
_sigUpdated.emit();
}
} // namespace lms::ui
+11 -2
View File
@@ -19,12 +19,14 @@
#pragma once
#include <span>
#include <vector>
#include <Wt/WContainerWidget.h>
#include <Wt/WSignal.h>
#include <Wt/WTemplate.h>
#include "database/ClusterId.hpp"
#include "database/MediaLibraryId.hpp"
#include "Filters.hpp"
@@ -35,17 +37,24 @@ namespace lms::ui
public:
Filters();
const std::vector<db::ClusterId>& getClusterIds() const { return _clusterIds; }
std::span<const db::ClusterId> getClusters() const { return _clusterIds; }
db::MediaLibraryId getMediaLibrary() const { return _mediaLibraryId; }
void add(db::ClusterId clusterId);
Wt::Signal<>& updated() { return _sigUpdated; }
private:
void showDialog();
void set(db::MediaLibraryId mediaLibraryId);
void emitFilterAddedNotification();
Wt::WContainerWidget* _filters;
Wt::WContainerWidget* _filters{};
Wt::Signal<> _sigUpdated;
std::vector<db::ClusterId> _clusterIds;
Wt::WInteractWidget* _mediaLibraryFilter{};
db::MediaLibraryId _mediaLibraryId;
};
} // namespace lms::ui
+17 -14
View File
@@ -28,10 +28,9 @@
namespace lms::ui
{
namespace
{
std::vector<db::TrackId> getArtistsTracks(db::Session& session, const std::vector<db::ArtistId>& artistsId, const std::vector<db::ClusterId>& clusters, std::size_t maxTrackCount)
std::vector<db::TrackId> getArtistsTracks(db::Session& session, const std::vector<db::ArtistId>& artistsId, const Filters& filters, std::size_t maxTrackCount)
{
assert(maxTrackCount);
@@ -44,7 +43,8 @@ namespace lms::ui
db::Track::FindParameters params;
params.setArtist(artistId);
params.setSortMethod(db::TrackSortMethod::DateDescAndRelease);
params.setClusters(clusters);
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setRange(db::Range{ 0, maxTrackCount - res.size() });
const auto tracks{ db::Track::findIds(session, params) };
@@ -59,7 +59,7 @@ namespace lms::ui
return res;
}
std::vector<db::TrackId> getReleasesTracks(db::Session& session, const std::vector<db::ReleaseId>& releasesId, const std::vector<db::ClusterId>& clusters, std::size_t maxTrackCount)
std::vector<db::TrackId> getReleasesTracks(db::Session& session, const std::vector<db::ReleaseId>& releasesId, const Filters& filters, std::size_t maxTrackCount)
{
using namespace db;
assert(maxTrackCount);
@@ -73,7 +73,8 @@ namespace lms::ui
db::Track::FindParameters params;
params.setRelease(releaseId);
params.setSortMethod(db::TrackSortMethod::Release);
params.setClusters(clusters);
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setRange(db::Range{ 0, maxTrackCount - res.size() });
const auto tracks{ db::Track::findIds(session, params) };
@@ -88,7 +89,7 @@ namespace lms::ui
return res;
}
std::vector<db::TrackId> getDiscTracks(db::Session& session, const std::vector<PlayQueueController::Disc>& discs, const std::vector<db::ClusterId>& clusters, std::size_t maxTrackCount)
std::vector<db::TrackId> getDiscTracks(db::Session& session, const std::vector<PlayQueueController::Disc>& discs, const Filters& filters, std::size_t maxTrackCount)
{
using namespace db;
assert(maxTrackCount);
@@ -103,7 +104,8 @@ namespace lms::ui
params.setRelease(disc.releaseId);
params.setSortMethod(db::TrackSortMethod::Release);
params.setDiscNumber(disc.discNumber);
params.setClusters(clusters);
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setRange(db::Range{ 0, maxTrackCount - res.size() });
const auto tracks{ db::Track::findIds(session, params) };
@@ -118,7 +120,7 @@ namespace lms::ui
return res;
}
std::vector<db::TrackId> getTrackListTracks(db::Session& session, db::TrackListId trackListId, const std::vector<db::ClusterId>& clusters, std::size_t maxTrackCount)
std::vector<db::TrackId> getTrackListTracks(db::Session& session, db::TrackListId trackListId, const Filters& filters, std::size_t maxTrackCount)
{
using namespace db;
assert(maxTrackCount);
@@ -127,7 +129,8 @@ namespace lms::ui
db::Track::FindParameters params;
params.setTrackList(trackListId);
params.setClusters(clusters);
params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setRange(db::Range{ 0, maxTrackCount });
params.setSortMethod(TrackSortMethod::TrackList);
@@ -143,13 +146,13 @@ namespace lms::ui
void PlayQueueController::processCommand(Command command, const std::vector<db::ArtistId>& artistIds)
{
const std::vector<db::TrackId> tracks{ getArtistsTracks(LmsApp->getDbSession(), artistIds, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
const std::vector<db::TrackId> tracks{ getArtistsTracks(LmsApp->getDbSession(), artistIds, _filters, _maxTrackCountToEnqueue) };
processCommand(command, tracks);
}
void PlayQueueController::processCommand(Command command, const std::vector<db::ReleaseId>& releaseIds)
{
const std::vector<db::TrackId> tracks{ getReleasesTracks(LmsApp->getDbSession(), releaseIds, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
const std::vector<db::TrackId> tracks{ getReleasesTracks(LmsApp->getDbSession(), releaseIds, _filters, _maxTrackCountToEnqueue) };
processCommand(command, tracks);
}
@@ -175,13 +178,13 @@ namespace lms::ui
void PlayQueueController::processCommand(Command command, db::TrackListId trackListId)
{
const std::vector<db::TrackId> tracks{ getTrackListTracks(LmsApp->getDbSession(), trackListId, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
const std::vector<db::TrackId> tracks{ getTrackListTracks(LmsApp->getDbSession(), trackListId, _filters, _maxTrackCountToEnqueue) };
processCommand(command, tracks);
}
void PlayQueueController::processCommand(Command command, const std::vector<Disc>& discs)
{
const std::vector<db::TrackId> tracks{ getDiscTracks(LmsApp->getDbSession(), discs, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
const std::vector<db::TrackId> tracks{ getDiscTracks(LmsApp->getDbSession(), discs, _filters, _maxTrackCountToEnqueue) };
processCommand(command, tracks);
}
@@ -197,7 +200,7 @@ namespace lms::ui
releaseId = track->getRelease()->getId();
}
const std::vector<db::TrackId> tracks{ getReleasesTracks(LmsApp->getDbSession(), {releaseId}, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
const std::vector<db::TrackId> tracks{ getReleasesTracks(LmsApp->getDbSession(), {releaseId}, _filters, _maxTrackCountToEnqueue) };
auto itTrack{ std::find(std::cbegin(tracks), std::cend(tracks), trackId) };
if (itTrack == std::cend(tracks))
return;
+14 -7
View File
@@ -51,7 +51,8 @@ namespace lms::ui
{
feedback::IFeedbackService::FindParameters params;
params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range);
releases = feedbackService.findStarredReleases(params);
break;
@@ -61,7 +62,8 @@ namespace lms::ui
{
scrobbling::IScrobblingService::FindParameters params;
params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range);
releases = scrobblingService.getRecentReleases(params);
@@ -72,7 +74,8 @@ namespace lms::ui
{
scrobbling::IScrobblingService::FindParameters params;
params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range);
releases = scrobblingService.getTopReleases(params);
@@ -82,7 +85,8 @@ namespace lms::ui
case Mode::RecentlyAdded:
{
Release::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(ReleaseSortMethod::LastWritten);
params.setRange(range);
@@ -96,7 +100,8 @@ namespace lms::ui
case Mode::Search:
{
Release::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setKeywords(getSearchKeywords());
params.setRange(range);
@@ -110,7 +115,8 @@ namespace lms::ui
case Mode::All:
{
Release::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range);
@@ -135,7 +141,8 @@ namespace lms::ui
if (!_randomReleases)
{
Release::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(ReleaseSortMethod::Random);
params.setRange(Range{ 0, getMaxCount() });
+3 -2
View File
@@ -262,7 +262,7 @@ namespace lms::ui
for (const db::Cluster::pointer& cluster : clusters)
{
const ClusterId clusterId{ cluster->getId() };
Wt::WInteractWidget* entry{ clusterContainers->addWidget(utils::createCluster(clusterId)) };
Wt::WInteractWidget* entry{ clusterContainers->addWidget(utils::createFilterCluster(clusterId)) };
entry->clicked().connect([this, clusterId]
{
_filters.add(clusterId);
@@ -394,7 +394,8 @@ namespace lms::ui
db::Track::FindParameters params;
params.setRelease(_releaseId);
params.setSortMethod(db::TrackSortMethod::Release);
params.setClusters(_filters.getClusterIds());
params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
db::Track::find(LmsApp->getDbSession(), params, [&](const db::Track::pointer& track)
{
+14 -7
View File
@@ -53,7 +53,8 @@ namespace lms::ui
case Mode::Starred:
{
feedback::IFeedbackService::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range);
params.setUser(LmsApp->getUserId());
tracks = feedbackService.findStarredTracks(params);
@@ -64,7 +65,8 @@ namespace lms::ui
{
scrobbling::IScrobblingService::FindParameters params;
params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range);
tracks = scrobblingService.getRecentTracks(params);
@@ -75,7 +77,8 @@ namespace lms::ui
{
scrobbling::IScrobblingService::FindParameters params;
params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range);
tracks = scrobblingService.getTopTracks(params);
@@ -85,7 +88,8 @@ namespace lms::ui
case Mode::RecentlyAdded:
{
Track::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(TrackSortMethod::LastWritten);
params.setRange(range);
@@ -99,7 +103,8 @@ namespace lms::ui
case Mode::Search:
{
Track::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setKeywords(getSearchKeywords());
params.setRange(range);
@@ -113,7 +118,8 @@ namespace lms::ui
case Mode::All:
{
Track::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range);
{
@@ -137,7 +143,8 @@ namespace lms::ui
if (!_randomTracks)
{
Track::FindParameters params;
params.setClusters(getFilters().getClusterIds());
params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(TrackSortMethod::Random);
params.setRange(Range{ 0, getMaxCount() });
+1 -1
View File
@@ -138,7 +138,7 @@ namespace lms::ui::TrackListHelpers
trackInfo->bindInt("playcount", core::Service<scrobbling::IScrobblingService>::get()->getCount(LmsApp->getUserId(), track->getId()));
Wt::WContainerWidget* clusterContainer{ trackInfo->bindWidget("clusters", utils::createClustersForTrack(track, filters)) };
Wt::WContainerWidget* clusterContainer{ trackInfo->bindWidget("clusters", utils::createFilterClustersForTrack(track, filters)) };
if (clusterContainer->count() > 0)
trackInfo->setCondition("if-has-clusters", true);
+3 -2
View File
@@ -107,7 +107,7 @@ namespace lms::ui
for (const db::Cluster::pointer& cluster : clusters)
{
const ClusterId clusterId{ cluster->getId() };
Wt::WInteractWidget* entry{ clusterContainers->addWidget(utils::createCluster(clusterId)) };
Wt::WInteractWidget* entry{ clusterContainers->addWidget(utils::createFilterCluster(clusterId)) };
entry->clicked().connect([this, clusterId]
{
_filters.add(clusterId);
@@ -183,7 +183,8 @@ namespace lms::ui
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
db::Track::FindParameters params;
params.setClusters(_filters.getClusterIds());
params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setTrackList(_trackListId);
params.setSortMethod(db::TrackSortMethod::TrackList);
params.setRange(db::Range{ static_cast<std::size_t>(_container->getCount()), _batchSize });
+2 -1
View File
@@ -101,7 +101,8 @@ namespace lms::ui
auto transaction{ session.createReadTransaction() };
TrackList::FindParameters params;
params.setClusters(_filters.getClusterIds());
params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setUser(LmsApp->getUserId());
params.setType(TrackListType::Playlist);
params.setRange(range);