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
+1
View File
@@ -158,6 +158,7 @@
<message id="Lms.Explore.duration">Duration</message> <message id="Lms.Explore.duration">Duration</message>
<message id="Lms.Explore.filter-added">Filter added</message> <message id="Lms.Explore.filter-added">Filter added</message>
<message id="Lms.Explore.filters">Filters</message> <message id="Lms.Explore.filters">Filters</message>
<message id="Lms.Explore.media-library">Music Library</message>
<message id="Lms.Explore.most-played">Most played</message> <message id="Lms.Explore.most-played">Most played</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message> <message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message> <message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
+1
View File
@@ -158,6 +158,7 @@
<message id="Lms.Explore.duration">Durée</message> <message id="Lms.Explore.duration">Durée</message>
<message id="Lms.Explore.filter-added">Filtre ajouté</message> <message id="Lms.Explore.filter-added">Filtre ajouté</message>
<message id="Lms.Explore.filters">Filtres</message> <message id="Lms.Explore.filters">Filtres</message>
<message id="Lms.Explore.media-library">Bibliothèque musicale</message>
<message id="Lms.Explore.most-played">Plus joués</message> <message id="Lms.Explore.most-played">Plus joués</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message> <message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message> <message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
+2
View File
@@ -110,6 +110,7 @@
<message id="Lms.Admin.ScannerController.status-in-progress">Scansione: passo {1}/{2}</message> <message id="Lms.Admin.ScannerController.status-in-progress">Scansione: passo {1}/{2}</message>
<message id="Lms.Admin.ScannerController.step-checking-for-duplicate-files">Controllo duplicati... {1} files</message> <message id="Lms.Admin.ScannerController.step-checking-for-duplicate-files">Controllo duplicati... {1} files</message>
<message id="Lms.Admin.ScannerController.step-checking-for-missing-files">Controllo file... {1}%</message> <message id="Lms.Admin.ScannerController.step-checking-for-missing-files">Controllo file... {1}%</message>
<message id="Lms.Admin.ScannerController.step-compact">Compattazione del database...</message>
<message id="Lms.Admin.ScannerController.step-compute-cluster-stats">Calcolo statistiche... {1}%</message> <message id="Lms.Admin.ScannerController.step-compute-cluster-stats">Calcolo statistiche... {1}%</message>
<message id="Lms.Admin.ScannerController.step-discovering-files">File trovati: {1} files</message> <message id="Lms.Admin.ScannerController.step-discovering-files">File trovati: {1} files</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features">Recupero metadati da AcousticBrainz: {1}/{2} tracce ({3}%)...</message> <message id="Lms.Admin.ScannerController.step-fetching-track-features">Recupero metadati da AcousticBrainz: {1}/{2} tracce ({3}%)...</message>
@@ -157,6 +158,7 @@
<message id="Lms.Explore.duration">Durata</message> <message id="Lms.Explore.duration">Durata</message>
<message id="Lms.Explore.filter-added">Filtro aggiunto</message> <message id="Lms.Explore.filter-added">Filtro aggiunto</message>
<message id="Lms.Explore.filters">Filtri</message> <message id="Lms.Explore.filters">Filtri</message>
<message id="Lms.Explore.media-library">Libreria musicale</message>
<message id="Lms.Explore.most-played">Più ascoltati</message> <message id="Lms.Explore.most-played">Più ascoltati</message>
<message id="Lms.Explore.musicbrainz-artist">Artista MusicBrainz</message> <message id="Lms.Explore.musicbrainz-artist">Artista MusicBrainz</message>
<message id="Lms.Explore.musicbrainz-release">Album MusicBrainz</message> <message id="Lms.Explore.musicbrainz-release">Album MusicBrainz</message>
+1
View File
@@ -158,6 +158,7 @@
<message id="Lms.Explore.filter-added">过滤器已添加</message> <message id="Lms.Explore.filter-added">过滤器已添加</message>
<message id="Lms.Explore.filters">过滤器</message> <message id="Lms.Explore.filters">过滤器</message>
<message id="Lms.Explore.most-played">播放最多</message> <message id="Lms.Explore.most-played">播放最多</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz 歌手</message> <message id="Lms.Explore.musicbrainz-artist">MusicBrainz 歌手</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz 专辑</message> <message id="Lms.Explore.musicbrainz-release">MusicBrainz 专辑</message>
+23 -4
View File
@@ -43,17 +43,19 @@ namespace lms::db
query.groupBy("c.id"); query.groupBy("c.id");
if (params.track.isValid() || params.release.isValid()) if (params.track.isValid() || params.release.isValid())
{
query.join("track_cluster t_c ON t_c.cluster_id = c.id"); 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()) if (!params.clusterTypeName.empty())
query.join("cluster_type c_t ON c_t.id = c.cluster_type_id"); query.join("cluster_type c_t ON c_t.id = c.cluster_type_id");
if (params.track.isValid()) if (params.track.isValid())
query.where("t.id = ?").bind(params.track); query.where("t_c.track_id = ?").bind(params.track);
if (params.release.isValid()) if (params.release.isValid())
{
query.join("track t ON t.id = t_c.track_id");
query.where("t.release_id = ?").bind(params.release); query.where("t.release_id = ?").bind(params.release);
}
assert(!params.clusterType.isValid() || params.clusterTypeName.empty()); assert(!params.clusterType.isValid() || params.clusterTypeName.empty());
if (params.clusterType.isValid()) if (params.clusterType.isValid())
@@ -61,6 +63,17 @@ namespace lms::db
else if (!params.clusterTypeName.empty()) else if (!params.clusterTypeName.empty())
query.where("c_t.name = ?").bind(params.clusterTypeName); 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; return query;
} }
@@ -208,6 +221,12 @@ namespace lms::db
return utils::execRangeQuery<ClusterTypeId>(query, range); 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) ClusterType::pointer ClusterType::find(Session& session, std::string_view name)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
+9 -2
View File
@@ -34,7 +34,7 @@ namespace lms::db
{ {
namespace namespace
{ {
static constexpr Version LMS_DATABASE_VERSION{ 57 }; static constexpr Version LMS_DATABASE_VERSION{ 58 };
} }
VersionInfo::VersionInfo() VersionInfo::VersionInfo()
@@ -455,11 +455,17 @@ SELECT
void migrateFromV56(Session& session) void migrateFromV56(Session& session)
{ {
// Make sure we remove all the previoulsy created index, the createIndexesIfNeeded will recreate them all // 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) for (const auto& indexName : indexeNames)
session.getDboSession()->execute("DROP INDEX " + indexName); session.getDboSession()->execute("DROP INDEX " + indexName);
} }
void migrateFromV57(Session& session)
{
// useless index
session.getDboSession()->execute("DROP INDEX cluster_name_idx");
}
bool doDbMigration(Session& session) bool doDbMigration(Session& session)
{ {
static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -494,6 +500,7 @@ SELECT
{54, migrateFromV54}, {54, migrateFromV54},
{55, migrateFromV55}, {55, migrateFromV55},
{56, migrateFromV56}, {56, migrateFromV56},
{57, migrateFromV57},
}; };
bool migrationPerformed{}; 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 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_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 cluster_type_name_idx ON cluster_type(name)");
_session.execute("CREATE INDEX IF NOT EXISTS listen_backend_idx ON listen(backend)"); _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 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) TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
: _name{ name } : _name{ name }
, _type{ type } , _type{ type }
@@ -58,7 +131,6 @@ namespace lms::db
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM tracklist")); 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) TrackList::pointer TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
@@ -73,53 +145,17 @@ namespace lms::db
RangeResults<TrackListId> TrackList::find(Session& session, const FindParameters& params) RangeResults<TrackListId> TrackList::find(Session& session, const FindParameters& params)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ createQuery<TrackListId>(session, params) };
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;
}
return utils::execRangeQuery<TrackListId>(query, params.range); 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) TrackList::pointer TrackList::find(Session& session, TrackListId id)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -67,7 +68,7 @@ namespace lms::db
ReleaseId release; // artists involved in this release ReleaseId release; // artists involved in this release
MediaLibraryId mediaLibrary; // artists that belong to this library 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& 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; }
@@ -46,12 +46,14 @@ namespace lms::db
struct FindParameters struct FindParameters
{ {
std::optional<Range> range; std::optional<Range> range;
ClusterSortMethod sortMethod;
ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type 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 std::string clusterTypeName; // 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(std::optional<Range> _range) { range = _range; return *this; } 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& setClusterType(ClusterTypeId _clusterType) { clusterType = _clusterType; return *this; }
FindParameters& setClusterTypeName(std::string_view _name) { clusterTypeName = _name; return *this; } FindParameters& setClusterTypeName(std::string_view _name) { clusterTypeName = _name; return *this; }
FindParameters& setTrack(TrackId _track) { track = _track; return *this; } FindParameters& setTrack(TrackId _track) { track = _track; return *this; }
@@ -119,6 +121,7 @@ namespace lms::db
// Getters // Getters
static std::size_t getCount(Session& session); static std::size_t getCount(Session& session);
static RangeResults<ClusterTypeId> findIds(Session& session, std::optional<Range> range = std::nullopt); 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, std::string_view name);
static pointer find(Session& session, ClusterTypeId id); static pointer find(Session& session, ClusterTypeId id);
static RangeResults<ClusterTypeId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt); static RangeResults<ClusterTypeId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
@@ -21,6 +21,7 @@
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -96,7 +97,7 @@ namespace lms::db
std::string releaseType; // If set, albums that has this release type 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 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& 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(std::optional<Range> _range) { range = _range; return *this; } FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
+2 -1
View File
@@ -23,6 +23,7 @@
#include <filesystem> #include <filesystem>
#include <ostream> #include <ostream>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
@@ -80,7 +81,7 @@ namespace lms::db
std::optional<int> discNumber; // matching this disc number std::optional<int> discNumber; // matching this disc number
MediaLibraryId mediaLibrary; // If set, tracks in this library 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& 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; }
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -28,6 +29,7 @@
#include <Wt/WDateTime.h> #include <Wt/WDateTime.h>
#include "database/ClusterId.hpp" #include "database/ClusterId.hpp"
#include "database/MediaLibraryId.hpp"
#include "database/Object.hpp" #include "database/Object.hpp"
#include "database/TrackId.hpp" #include "database/TrackId.hpp"
#include "database/TrackListId.hpp" #include "database/TrackListId.hpp"
@@ -57,18 +59,21 @@ namespace lms::db
std::optional<Range> range; std::optional<Range> range;
std::optional<TrackListType> type; std::optional<TrackListType> type;
UserId user; // only tracklists owned by this user UserId user; // only tracklists owned by this user
MediaLibraryId mediaLibrary; // only tracklists that have songs in this media library
TrackListSortMethod sortMethod{ TrackListSortMethod::None }; 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& setRange(std::optional<Range> _range) { range = _range; return *this; }
FindParameters& setType(TrackListType _type) { type = _type; return *this; } FindParameters& setType(TrackListType _type) { type = _type; return *this; }
FindParameters& setUser(UserId _user) { user = _user; 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; } FindParameters& setSortMethod(TrackListSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
}; };
static std::size_t getCount(Session& session); 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, std::string_view name, TrackListType type, UserId userId);
static pointer find(Session& session, TrackListId tracklistId); static pointer find(Session& session, TrackListId tracklistId);
static RangeResults<TrackListId> find(Session& session, const FindParameters& params); 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 // Accessors
std::string_view getName() const { return _name; } std::string_view getName() const { return _name; }
@@ -116,6 +116,12 @@ namespace lms::db
StarredDateDesc, StarredDateDesc,
}; };
enum class ClusterSortMethod
{
None,
Name,
};
enum class ReleaseSortMethod enum class ReleaseSortMethod
{ {
None, None,
+45 -16
View File
@@ -137,11 +137,11 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; 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); 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{}.setClusters({ cluster2.getId() })); tracks = Track::findIds(session, Track::FindParameters{}.setClusters(std::initializer_list<ClusterId>{ cluster2.getId() }));
EXPECT_EQ(tracks.results.size(), 0); 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) TEST_F(DatabaseFixture, ClusterType_singleTrack)
{ {
@@ -285,7 +314,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
ASSERT_EQ(Cluster::findOrphanIds(session).results.size(), 2); 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(Release::find(session, Release::FindParameters{}).results.size(), 1);
EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0); EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0);
EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0);
@@ -321,7 +350,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; 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); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
} }
@@ -329,7 +358,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; 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); EXPECT_EQ(releases.results.size(), 0);
} }
@@ -375,12 +404,12 @@ namespace lms::db::tests
{ {
auto transaction{ session.createWriteTransaction() }; 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); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); 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(std::initializer_list<ClusterId>{ 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>{ cluster3.getId() })).results.size(), 0);
cluster2.get().modify()->addTrack(track.get()); cluster2.get().modify()->addTrack(track.get());
} }
@@ -388,19 +417,19 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; 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); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); 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); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); 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); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); 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 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); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); EXPECT_EQ(artists.results.front(), artist.getId());
} }
@@ -581,7 +610,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; 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); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); EXPECT_EQ(artists.results.front(), artist.getId());
@@ -589,7 +618,7 @@ namespace lms::db::tests
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());
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); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
} }
@@ -620,7 +649,7 @@ namespace lms::db::tests
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());
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); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); 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) TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster)
{ {
ScopedUser user{ session, "MyUser" }; ScopedUser user{ session, "MyUser" };
@@ -163,7 +203,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; 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); EXPECT_EQ(trackLists.results.size(), 0);
} }
@@ -177,7 +217,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; 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); ASSERT_EQ(trackLists.results.size(), 1);
EXPECT_EQ(trackLists.results.front(), trackList1.getId()); EXPECT_EQ(trackLists.results.front(), trackList1.getId());
} }
@@ -21,6 +21,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <boost/asio/io_service.hpp> #include <boost/asio/io_service.hpp>
#include <Wt/WDateTime.h> #include <Wt/WDateTime.h>
@@ -57,7 +58,7 @@ namespace lms::feedback
db::MediaLibraryId library; db::MediaLibraryId library;
FindParameters& setUser(const db::UserId _user) { user = _user; return *this; } 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& setRange(std::optional<db::Range> _range) { range = _range; return *this; }
FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; } FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; }
}; };
@@ -22,6 +22,7 @@
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <boost/asio/io_service.hpp> #include <boost/asio/io_service.hpp>
#include <Wt/WDateTime.h> #include <Wt/WDateTime.h>
@@ -65,7 +66,7 @@ namespace lms::scrobbling
db::ArtistId artist; // if set, match this artist db::ArtistId artist; // if set, match this artist
FindParameters& setUser(const db::UserId _user) { user = _user; return *this; } 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& setRange(std::optional<db::Range> _range) { range = _range; return *this; }
FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; } FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; }
FindParameters& setArtist(db::ArtistId _artist) { artist = _artist; 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) }) if (const Cluster::pointer cluster{ clusterType->getCluster(genre) })
{ {
Release::FindParameters params; Release::FindParameters params;
params.setClusters({ cluster->getId() }); params.setClusters(std::initializer_list<ClusterId>{ cluster->getId() });
params.setSortMethod(ReleaseSortMethod::Name); params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range); params.setRange(range);
params.setMediaLibrary(mediaLibraryId); params.setMediaLibrary(mediaLibraryId);
@@ -280,7 +280,7 @@ namespace lms::api::subsonic
Response::Node& songsByGenreNode{ response.createNode("songsByGenre") }; Response::Node& songsByGenreNode{ response.createNode("songsByGenre") };
Track::FindParameters params; Track::FindParameters params;
params.setClusters({ cluster->getId() }); params.setClusters(std::initializer_list<ClusterId>{ cluster->getId() });
params.setRange(Range{ offset, count }); params.setRange(Range{ offset, count });
params.setMediaLibrary(mediaLibrary); params.setMediaLibrary(mediaLibrary);
+5 -9
View File
@@ -87,9 +87,7 @@ namespace lms::ui
return trackListModel->getValue(*row); return trackListModel->getValue(*row);
} }
static static std::shared_ptr<TrackListModel> createTrackListModel()
std::shared_ptr<TrackListModel>
createTrackListModel()
{ {
using namespace db; using namespace db;
@@ -102,12 +100,10 @@ namespace lms::ui
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setSortMethod(TrackListSortMethod::Name); params.setSortMethod(TrackListSortMethod::Name);
auto tracklists{ TrackList::find(LmsApp->getDbSession(), params) }; TrackList::find(LmsApp->getDbSession(), params, [&](const TrackList::pointer& trackList)
for (const TrackListId trackListId : tracklists.results) {
{ model->add(Wt::WString::fromUTF8(std::string{ trackList->getName() }), trackList->getId());
const TrackList::pointer trackList{ TrackList::find(LmsApp->getDbSession(), trackListId) }; });
model->add(Wt::WString::fromUTF8(std::string{ trackList->getName() }), trackListId);
}
return model; return model;
} }
+38 -34
View File
@@ -80,7 +80,18 @@ namespace lms::ui::utils
return cover; 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() }; auto transaction{ LmsApp->getDbSession().createReadTransaction() };
@@ -88,7 +99,7 @@ namespace lms::ui::utils
if (!cluster) if (!cluster)
return {}; 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) switch (cluster->getType()->getId().getValue() % 8)
{ {
@@ -105,14 +116,33 @@ namespace lms::ui::utils
return "bg-primary"; return "bg-primary";
} }; } };
const std::string styleClass{ getStyleClass(cluster) }; return createFilter(Wt::WString::fromUTF8(std::string{ cluster->getName() }), Wt::WString::fromUTF8(std::string{ cluster->getType()->getName() }), getStyleClass(cluster), canDelete);
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) }; }
res->setStyleClass("Lms-badge-cluster badge me-1 " + styleClass); // HACK std::unique_ptr<Wt::WContainerWidget> createFilterClustersForTrack(db::Track::pointer track, Filters& filters)
res->setToolTip(std::string{ cluster->getType()->getName() }, Wt::TextFormat::Plain); {
res->setInline(true); 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) 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; 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/WContainerWidget.h>
#include <Wt/WImage.h> #include <Wt/WImage.h>
#include <Wt/WInteractWidget.h> #include <Wt/WInteractWidget.h>
#include <Wt/WString.h>
#include "database/ArtistId.hpp" #include "database/ArtistId.hpp"
#include "database/ClusterId.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::ReleaseId releaseId, CoverResource::Size size);
std::unique_ptr<Wt::WImage> createCover(db::TrackId trackId, 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> 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"); 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); 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> 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::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> #include <Wt/WStringListModel.h>
namespace lms::ui { namespace lms::ui
// Helper class
template <typename T>
class ValueStringModel : public Wt::WStringListModel
{ {
public: // Helper class
T getValue(std::size_t row) const template <typename T>
{ class ValueStringModel : public Wt::WStringListModel
return Wt::cpp17::any_cast<T>(data(index(static_cast<int>(row), 0), Wt::ItemDataRole::User)); {
} 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 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)); return Wt::cpp17::any_cast<Wt::WString>(data(index(static_cast<int>(row), 0), Wt::ItemDataRole::Display));
} }
std::optional<std::size_t> std::optional<std::size_t> getRowFromString(const Wt::WString& value)
getRowFromString(const Wt::WString& value) {
{ for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i)
for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i) {
{ if (getString(i) == value)
if (getString(i) == value) return i;
return i; }
}
return std::nullopt; return std::nullopt;
} }
std::optional<std::size_t> std::optional<std::size_t> getRowFromValue(const T& value)
getRowFromValue(const T& value) {
{ for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i)
for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i) {
{ if (getValue(i) == value)
if (getValue(i) == value) return i;
return i; }
}
return std::nullopt; return std::nullopt;
} }
void void add(const Wt::WString& str, const T& value)
add(const Wt::WString& str, const T& value) {
{ insertRows(rowCount(), 1);
insertRows(rowCount(), 1); setData(rowCount() - 1, 0, value, Wt::ItemDataRole::User);
setData(rowCount() - 1, 0, value, Wt::ItemDataRole::User); setData(rowCount() - 1, 0, str, Wt::ItemDataRole::Display);
setData(rowCount() - 1, 0, str, Wt::ItemDataRole::Display); }
}
void
clear()
{
removeRows(0, rowCount());
}
};
void clear()
{
removeRows(0, rowCount());
}
};
} // namespace lms::ui } // namespace lms::ui
+15 -7
View File
@@ -39,6 +39,7 @@ namespace lms::ui
scrobbling::IScrobblingService& scrobblingService{ *core::Service<scrobbling::IScrobblingService>::get() }; scrobbling::IScrobblingService& scrobblingService{ *core::Service<scrobbling::IScrobblingService>::get() };
const Range range{ getActualRange(requestedRange) }; const Range range{ getActualRange(requestedRange) };
const Filters& filters{ getFilters() };
RangeResults<ArtistId> artists; RangeResults<ArtistId> artists;
@@ -52,7 +53,8 @@ namespace lms::ui
{ {
feedback::IFeedbackService::ArtistFindParameters params; feedback::IFeedbackService::ArtistFindParameters params;
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds()); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType); params.setLinkType(_linkType);
params.setSortMethod(ArtistSortMethod::StarredDateDesc); params.setSortMethod(ArtistSortMethod::StarredDateDesc);
params.setRange(range); params.setRange(range);
@@ -64,7 +66,8 @@ namespace lms::ui
{ {
scrobbling::IScrobblingService::ArtistFindParameters params; scrobbling::IScrobblingService::ArtistFindParameters params;
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds()); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType); params.setLinkType(_linkType);
params.setRange(range); params.setRange(range);
@@ -76,7 +79,8 @@ namespace lms::ui
{ {
scrobbling::IScrobblingService::ArtistFindParameters params; scrobbling::IScrobblingService::ArtistFindParameters params;
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds()); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType); params.setLinkType(_linkType);
params.setRange(range); params.setRange(range);
@@ -87,7 +91,8 @@ namespace lms::ui
case Mode::RecentlyAdded: case Mode::RecentlyAdded:
{ {
Artist::FindParameters params; Artist::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType); params.setLinkType(_linkType);
params.setSortMethod(ArtistSortMethod::LastWritten); params.setSortMethod(ArtistSortMethod::LastWritten);
params.setRange(range); params.setRange(range);
@@ -102,7 +107,8 @@ namespace lms::ui
case Mode::Search: case Mode::Search:
{ {
Artist::FindParameters params; Artist::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setKeywords(getSearchKeywords()); params.setKeywords(getSearchKeywords());
params.setLinkType(_linkType); params.setLinkType(_linkType);
params.setRange(range); params.setRange(range);
@@ -117,7 +123,8 @@ namespace lms::ui
case Mode::All: case Mode::All:
{ {
Artist::FindParameters params; Artist::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setLinkType(_linkType); params.setLinkType(_linkType);
params.setSortMethod(ArtistSortMethod::SortName); params.setSortMethod(ArtistSortMethod::SortName);
params.setRange(range); params.setRange(range);
@@ -143,7 +150,8 @@ namespace lms::ui
if (!_randomArtists) if (!_randomArtists)
{ {
Artist::FindParameters params; Artist::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setLinkType(_linkType); params.setLinkType(_linkType);
params.setSortMethod(ArtistSortMethod::Random); params.setSortMethod(ArtistSortMethod::Random);
params.setRange(Range{ 0, getMaxCount() }); params.setRange(Range{ 0, getMaxCount() });
+7 -4
View File
@@ -138,7 +138,7 @@ namespace lms::ui
for (const db::Cluster::pointer& cluster : clusters) for (const db::Cluster::pointer& cluster : clusters)
{ {
const db::ClusterId clusterId = cluster->getId(); 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] entry->clicked().connect([this, clusterId]
{ {
_filters.add(clusterId); _filters.add(clusterId);
@@ -198,7 +198,8 @@ namespace lms::ui
_releaseContainers.clear(); _releaseContainers.clear();
Release::FindParameters params; Release::FindParameters params;
params.setClusters(_filters.getClusterIds()); params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setArtist(_artistId, { TrackArtistLinkType::ReleaseArtist }, {}); params.setArtist(_artistId, { TrackArtistLinkType::ReleaseArtist }, {});
params.setSortMethod(ReleaseSortMethod::OriginalDateDesc); params.setSortMethod(ReleaseSortMethod::OriginalDateDesc);
@@ -257,7 +258,8 @@ namespace lms::ui
_appearsOnReleaseContainer = {}; _appearsOnReleaseContainer = {};
Release::FindParameters params; Release::FindParameters params;
params.setClusters(_filters.getClusterIds()); params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setArtist(_artistId, types, { TrackArtistLinkType::ReleaseArtist }); params.setArtist(_artistId, types, { TrackArtistLinkType::ReleaseArtist });
params.setSortMethod(ReleaseSortMethod::OriginalDateDesc); params.setSortMethod(ReleaseSortMethod::OriginalDateDesc);
@@ -343,7 +345,8 @@ namespace lms::ui
const Range range{ static_cast<std::size_t>(_trackContainer->getCount()), _tracksBatchSize }; const Range range{ static_cast<std::size_t>(_trackContainer->getCount()), _tracksBatchSize };
Track::FindParameters params; Track::FindParameters params;
params.setClusters(_filters.getClusterIds()); params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setArtist(_artistId); params.setArtist(_artistId);
params.setRange(range); params.setRange(range);
params.setSortMethod(TrackSortMethod::Name); params.setSortMethod(TrackSortMethod::Name);
+1 -1
View File
@@ -58,7 +58,7 @@ namespace lms::ui
protected: protected:
Range getActualRange(std::optional<Range> range) const; Range getActualRange(std::optional<Range> range) const;
std::size_t getMaxCount() 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; } const std::vector<std::string_view>& getSearchKeywords() const { return _searchKeywords; }
private: private:
+148 -85
View File
@@ -19,21 +19,79 @@
#include "Filters.hpp" #include "Filters.hpp"
#include <variant>
#include <Wt/WComboBox.h> #include <Wt/WComboBox.h>
#include <Wt/WDialog.h> #include <Wt/WDialog.h>
#include <Wt/WPushButton.h> #include <Wt/WPushButton.h>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
#include "database/MediaLibrary.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "common/ValueStringModel.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "ModalManager.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() void Filters::showDialog()
{ {
@@ -43,32 +101,27 @@ namespace lms::ui
dialog->addFunction("id", &Wt::WTemplate::Functions::id); dialog->addFunction("id", &Wt::WTemplate::Functions::id);
Wt::WComboBox* typeCombo{ dialog->bindNew<Wt::WComboBox>("type") }; 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::WComboBox* valueCombo{ dialog->bindNew<Wt::WComboBox>("value") };
Wt::WPushButton* addBtn{ dialog->bindNew<Wt::WPushButton>("add-btn", Wt::WString::tr("Lms.Explore.add-filter")) }; 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 auto valueModel{ std::static_pointer_cast<ValueModel>(valueCombo->model()) };
const std::string value{ valueCombo->valueText().toUTF8() }; const ValueVariant value{ valueModel->getValue(valueCombo->currentIndex()) };
// TODO use a model to store the cluster.id() values
ClusterId clusterId{};
if (const db::MediaLibraryId * mediaLibraryId{ std::get_if<db::MediaLibraryId>(&value) })
{ {
auto transaction{ LmsApp->getDbSession().createReadTransaction() }; set(*mediaLibraryId);
}
ClusterType::pointer clusterType{ ClusterType::find(LmsApp->getDbSession(), type) }; else if (const db::ClusterId * clusterId{ std::get_if<db::ClusterId>(&value) })
if (!clusterType) {
return; add(*clusterId);
Cluster::pointer cluster{ clusterType->getCluster(value) };
if (!cluster)
return;
clusterId = cluster->getId();
} }
add(clusterId); // TODO
LmsApp->getModalManager().dispose(dialogPtr); LmsApp->getModalManager().dispose(dialogPtr);
}); });
@@ -78,79 +131,20 @@ namespace lms::ui
LmsApp->getModalManager().dispose(dialogPtr); LmsApp->getModalManager().dispose(dialogPtr);
}); });
// Populate data typeCombo->activated().connect([valueCombo, typeModel](int row)
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const auto clusterTypesIds{ ClusterType::findUsed(LmsApp->getDbSession()) };
for (const ClusterTypeId clusterTypeId : clusterTypesIds.results)
{ {
const auto clusterType{ ClusterType::find(LmsApp->getDbSession(), clusterTypeId) }; const TypeVariant type{ typeModel->getValue(row) };
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 std::shared_ptr<ValueModel> valueModel{ createValueModel(type) };
valueCombo->clear(); valueCombo->clear();
valueCombo->setModel(valueModel);
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() }));
}
}); });
typeCombo->activated().emit(0); // force emit to refresh the type combo model
LmsApp->getModalManager().show(std::move(dialog)); 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() Filters::Filters()
: Wt::WTemplate{ Wt::WString::tr("Lms.Explore.template.filters") } : Wt::WTemplate{ Wt::WString::tr("Lms.Explore.template.filters") }
{ {
@@ -162,4 +156,73 @@ namespace lms::ui
_filters = bindNew<Wt::WContainerWidget>("clusters"); _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 } // namespace lms::ui
+11 -2
View File
@@ -19,12 +19,14 @@
#pragma once #pragma once
#include <span>
#include <vector> #include <vector>
#include <Wt/WContainerWidget.h> #include <Wt/WContainerWidget.h>
#include <Wt/WSignal.h> #include <Wt/WSignal.h>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include "database/ClusterId.hpp" #include "database/ClusterId.hpp"
#include "database/MediaLibraryId.hpp"
#include "Filters.hpp" #include "Filters.hpp"
@@ -35,17 +37,24 @@ namespace lms::ui
public: public:
Filters(); 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); void add(db::ClusterId clusterId);
Wt::Signal<>& updated() { return _sigUpdated; } Wt::Signal<>& updated() { return _sigUpdated; }
private: private:
void showDialog(); void showDialog();
void set(db::MediaLibraryId mediaLibraryId);
void emitFilterAddedNotification();
Wt::WContainerWidget* _filters; Wt::WContainerWidget* _filters{};
Wt::Signal<> _sigUpdated; Wt::Signal<> _sigUpdated;
std::vector<db::ClusterId> _clusterIds; std::vector<db::ClusterId> _clusterIds;
Wt::WInteractWidget* _mediaLibraryFilter{};
db::MediaLibraryId _mediaLibraryId;
}; };
} // namespace lms::ui } // namespace lms::ui
+17 -14
View File
@@ -28,10 +28,9 @@
namespace lms::ui namespace lms::ui
{ {
namespace 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); assert(maxTrackCount);
@@ -44,7 +43,8 @@ namespace lms::ui
db::Track::FindParameters params; db::Track::FindParameters params;
params.setArtist(artistId); params.setArtist(artistId);
params.setSortMethod(db::TrackSortMethod::DateDescAndRelease); params.setSortMethod(db::TrackSortMethod::DateDescAndRelease);
params.setClusters(clusters); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setRange(db::Range{ 0, maxTrackCount - res.size() }); params.setRange(db::Range{ 0, maxTrackCount - res.size() });
const auto tracks{ db::Track::findIds(session, params) }; const auto tracks{ db::Track::findIds(session, params) };
@@ -59,7 +59,7 @@ namespace lms::ui
return res; 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; using namespace db;
assert(maxTrackCount); assert(maxTrackCount);
@@ -73,7 +73,8 @@ namespace lms::ui
db::Track::FindParameters params; db::Track::FindParameters params;
params.setRelease(releaseId); params.setRelease(releaseId);
params.setSortMethod(db::TrackSortMethod::Release); params.setSortMethod(db::TrackSortMethod::Release);
params.setClusters(clusters); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setRange(db::Range{ 0, maxTrackCount - res.size() }); params.setRange(db::Range{ 0, maxTrackCount - res.size() });
const auto tracks{ db::Track::findIds(session, params) }; const auto tracks{ db::Track::findIds(session, params) };
@@ -88,7 +89,7 @@ namespace lms::ui
return res; 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; using namespace db;
assert(maxTrackCount); assert(maxTrackCount);
@@ -103,7 +104,8 @@ namespace lms::ui
params.setRelease(disc.releaseId); params.setRelease(disc.releaseId);
params.setSortMethod(db::TrackSortMethod::Release); params.setSortMethod(db::TrackSortMethod::Release);
params.setDiscNumber(disc.discNumber); params.setDiscNumber(disc.discNumber);
params.setClusters(clusters); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setRange(db::Range{ 0, maxTrackCount - res.size() }); params.setRange(db::Range{ 0, maxTrackCount - res.size() });
const auto tracks{ db::Track::findIds(session, params) }; const auto tracks{ db::Track::findIds(session, params) };
@@ -118,7 +120,7 @@ namespace lms::ui
return res; 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; using namespace db;
assert(maxTrackCount); assert(maxTrackCount);
@@ -127,7 +129,8 @@ namespace lms::ui
db::Track::FindParameters params; db::Track::FindParameters params;
params.setTrackList(trackListId); params.setTrackList(trackListId);
params.setClusters(clusters); params.setClusters(filters.getClusters());
params.setMediaLibrary(filters.getMediaLibrary());
params.setRange(db::Range{ 0, maxTrackCount }); params.setRange(db::Range{ 0, maxTrackCount });
params.setSortMethod(TrackSortMethod::TrackList); params.setSortMethod(TrackSortMethod::TrackList);
@@ -143,13 +146,13 @@ namespace lms::ui
void PlayQueueController::processCommand(Command command, const std::vector<db::ArtistId>& artistIds) 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); processCommand(command, tracks);
} }
void PlayQueueController::processCommand(Command command, const std::vector<db::ReleaseId>& releaseIds) 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); processCommand(command, tracks);
} }
@@ -175,13 +178,13 @@ namespace lms::ui
void PlayQueueController::processCommand(Command command, db::TrackListId trackListId) 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); processCommand(command, tracks);
} }
void PlayQueueController::processCommand(Command command, const std::vector<Disc>& discs) 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); processCommand(command, tracks);
} }
@@ -197,7 +200,7 @@ namespace lms::ui
releaseId = track->getRelease()->getId(); 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) }; auto itTrack{ std::find(std::cbegin(tracks), std::cend(tracks), trackId) };
if (itTrack == std::cend(tracks)) if (itTrack == std::cend(tracks))
return; return;
+14 -7
View File
@@ -51,7 +51,8 @@ namespace lms::ui
{ {
feedback::IFeedbackService::FindParameters params; feedback::IFeedbackService::FindParameters params;
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range); params.setRange(range);
releases = feedbackService.findStarredReleases(params); releases = feedbackService.findStarredReleases(params);
break; break;
@@ -61,7 +62,8 @@ namespace lms::ui
{ {
scrobbling::IScrobblingService::FindParameters params; scrobbling::IScrobblingService::FindParameters params;
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range); params.setRange(range);
releases = scrobblingService.getRecentReleases(params); releases = scrobblingService.getRecentReleases(params);
@@ -72,7 +74,8 @@ namespace lms::ui
{ {
scrobbling::IScrobblingService::FindParameters params; scrobbling::IScrobblingService::FindParameters params;
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range); params.setRange(range);
releases = scrobblingService.getTopReleases(params); releases = scrobblingService.getTopReleases(params);
@@ -82,7 +85,8 @@ namespace lms::ui
case Mode::RecentlyAdded: case Mode::RecentlyAdded:
{ {
Release::FindParameters params; Release::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(ReleaseSortMethod::LastWritten); params.setSortMethod(ReleaseSortMethod::LastWritten);
params.setRange(range); params.setRange(range);
@@ -96,7 +100,8 @@ namespace lms::ui
case Mode::Search: case Mode::Search:
{ {
Release::FindParameters params; Release::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setKeywords(getSearchKeywords()); params.setKeywords(getSearchKeywords());
params.setRange(range); params.setRange(range);
@@ -110,7 +115,8 @@ namespace lms::ui
case Mode::All: case Mode::All:
{ {
Release::FindParameters params; Release::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(ReleaseSortMethod::Name); params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range); params.setRange(range);
@@ -135,7 +141,8 @@ namespace lms::ui
if (!_randomReleases) if (!_randomReleases)
{ {
Release::FindParameters params; Release::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(ReleaseSortMethod::Random); params.setSortMethod(ReleaseSortMethod::Random);
params.setRange(Range{ 0, getMaxCount() }); params.setRange(Range{ 0, getMaxCount() });
+3 -2
View File
@@ -262,7 +262,7 @@ namespace lms::ui
for (const db::Cluster::pointer& cluster : clusters) for (const db::Cluster::pointer& cluster : clusters)
{ {
const ClusterId clusterId{ cluster->getId() }; 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] entry->clicked().connect([this, clusterId]
{ {
_filters.add(clusterId); _filters.add(clusterId);
@@ -394,7 +394,8 @@ namespace lms::ui
db::Track::FindParameters params; db::Track::FindParameters params;
params.setRelease(_releaseId); params.setRelease(_releaseId);
params.setSortMethod(db::TrackSortMethod::Release); 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) 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: case Mode::Starred:
{ {
feedback::IFeedbackService::FindParameters params; feedback::IFeedbackService::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range); params.setRange(range);
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
tracks = feedbackService.findStarredTracks(params); tracks = feedbackService.findStarredTracks(params);
@@ -64,7 +65,8 @@ namespace lms::ui
{ {
scrobbling::IScrobblingService::FindParameters params; scrobbling::IScrobblingService::FindParameters params;
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range); params.setRange(range);
tracks = scrobblingService.getRecentTracks(params); tracks = scrobblingService.getRecentTracks(params);
@@ -75,7 +77,8 @@ namespace lms::ui
{ {
scrobbling::IScrobblingService::FindParameters params; scrobbling::IScrobblingService::FindParameters params;
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range); params.setRange(range);
tracks = scrobblingService.getTopTracks(params); tracks = scrobblingService.getTopTracks(params);
@@ -85,7 +88,8 @@ namespace lms::ui
case Mode::RecentlyAdded: case Mode::RecentlyAdded:
{ {
Track::FindParameters params; Track::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(TrackSortMethod::LastWritten); params.setSortMethod(TrackSortMethod::LastWritten);
params.setRange(range); params.setRange(range);
@@ -99,7 +103,8 @@ namespace lms::ui
case Mode::Search: case Mode::Search:
{ {
Track::FindParameters params; Track::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setKeywords(getSearchKeywords()); params.setKeywords(getSearchKeywords());
params.setRange(range); params.setRange(range);
@@ -113,7 +118,8 @@ namespace lms::ui
case Mode::All: case Mode::All:
{ {
Track::FindParameters params; Track::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setRange(range); params.setRange(range);
{ {
@@ -137,7 +143,8 @@ namespace lms::ui
if (!_randomTracks) if (!_randomTracks)
{ {
Track::FindParameters params; Track::FindParameters params;
params.setClusters(getFilters().getClusterIds()); params.setClusters(getFilters().getClusters());
params.setMediaLibrary(getFilters().getMediaLibrary());
params.setSortMethod(TrackSortMethod::Random); params.setSortMethod(TrackSortMethod::Random);
params.setRange(Range{ 0, getMaxCount() }); 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())); 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) if (clusterContainer->count() > 0)
trackInfo->setCondition("if-has-clusters", true); trackInfo->setCondition("if-has-clusters", true);
+3 -2
View File
@@ -107,7 +107,7 @@ namespace lms::ui
for (const db::Cluster::pointer& cluster : clusters) for (const db::Cluster::pointer& cluster : clusters)
{ {
const ClusterId clusterId{ cluster->getId() }; 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] entry->clicked().connect([this, clusterId]
{ {
_filters.add(clusterId); _filters.add(clusterId);
@@ -183,7 +183,8 @@ namespace lms::ui
auto transaction{ LmsApp->getDbSession().createReadTransaction() }; auto transaction{ LmsApp->getDbSession().createReadTransaction() };
db::Track::FindParameters params; db::Track::FindParameters params;
params.setClusters(_filters.getClusterIds()); params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setTrackList(_trackListId); params.setTrackList(_trackListId);
params.setSortMethod(db::TrackSortMethod::TrackList); params.setSortMethod(db::TrackSortMethod::TrackList);
params.setRange(db::Range{ static_cast<std::size_t>(_container->getCount()), _batchSize }); 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() }; auto transaction{ session.createReadTransaction() };
TrackList::FindParameters params; TrackList::FindParameters params;
params.setClusters(_filters.getClusterIds()); params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setUser(LmsApp->getUserId()); params.setUser(LmsApp->getUserId());
params.setType(TrackListType::Playlist); params.setType(TrackListType::Playlist);
params.setRange(range); params.setRange(range);