diff --git a/approot/tracks.xml b/approot/tracks.xml index 41f92006..b8dcf259 100644 --- a/approot/tracks.xml +++ b/approot/tracks.xml @@ -36,6 +36,7 @@ ${}${release-name}${} ${}${artist-name}${} + ${clusters} ${play-btn}${add-btn} diff --git a/src/Makefile.am b/src/Makefile.am index b049316e..8d0239b2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,6 +33,7 @@ lms_SOURCES = \ $(srcdir)/ui/explore/ArtistView.cpp \ $(srcdir)/ui/explore/ArtistsView.cpp \ $(srcdir)/ui/explore/Explore.cpp \ + $(srcdir)/ui/explore/ExploreUtils.cpp \ $(srcdir)/ui/explore/Filters.cpp \ $(srcdir)/ui/explore/ReleasesView.cpp \ $(srcdir)/ui/explore/ReleaseView.cpp \ diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 54307fb3..6b553258 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -210,6 +210,50 @@ Track::getOriginalYear() const return (_originalYear > 0) ? boost::make_optional(_originalYear) : boost::none; } +std::vector> +Track::getClusterGroups(std::vector clusterTypes, std::size_t size) const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + WhereClause where; + + std::ostringstream oss; + + oss << "SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id"; + + where.And(WhereClause("t.id = ?")).bind(std::to_string(self()->id())); + { + WhereClause clusterClause; + for (auto clusterType : clusterTypes) + clusterClause.Or(WhereClause("c_type.id = ?")).bind(std::to_string(clusterType.id())); + where.And(clusterClause); + } + oss << " " << where.get(); + oss << " GROUP BY c.id ORDER BY COUNT(c.id) DESC"; + + Wt::Dbo::Query query = session()->query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) + query.bind(bindArg); + + Wt::Dbo::collection queryRes = query; + + std::map> clusters; + for (auto cluster : queryRes) + { + if (clusters[cluster->getType().id()].size() < size) + clusters[cluster->getType().id()].push_back(cluster); + } + + std::vector> res; + for (auto cluster_list : clusters) + res.push_back(cluster_list.second); + + return res; +} + Cluster::Cluster() { } diff --git a/src/database/Track.hpp b/src/database/Track.hpp index d0c90081..9fc4f3a8 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -120,8 +120,7 @@ class ClusterType : public Wt::Dbo::Dbo Wt::Dbo::collection< Wt::Dbo::ptr > _clusters; }; - -class Track +class Track : public Wt::Dbo::Dbo { public: @@ -197,6 +196,8 @@ class Track Wt::Dbo::ptr getRelease(void) const { return _release; } std::vector< Cluster::pointer > getClusters(void) const; + std::vector>> getClusterGroups(std::vector> clusterTypes, std::size_t size) const; + template void persist(Action& a) { diff --git a/src/ui/explore/ArtistView.cpp b/src/ui/explore/ArtistView.cpp index 230776df..91dcc3ab 100644 --- a/src/ui/explore/ArtistView.cpp +++ b/src/ui/explore/ArtistView.cpp @@ -34,13 +34,14 @@ #include "LmsApplication.hpp" #include "Filters.hpp" +#include "ExploreUtils.hpp" using namespace Database; namespace { const std::string artistClusterTypesSetting = "artist_cluster_types"; -const std::vector defaultArtistClusterTypes = +const std::set defaultArtistClusterTypes = { "GENRE", "ALBUMGROUPING", @@ -48,21 +49,6 @@ const std::vector defaultArtistClusterTypes = "COMMENT:SONGS-DB_OCCASION", }; -std::vector getArtistClusterTypes(Wt::Dbo::Session& session) -{ - Wt::Dbo::Transaction transaction(session); - - std::vector res; - for (auto clusterTypeName : splitString(Setting::getString(session, artistClusterTypesSetting), " ")) - { - auto clusterType = ClusterType::getByName(session, clusterTypeName); - if (clusterType) - res.push_back(clusterType); - } - - return res; -} - } // namespace namespace UserInterface { @@ -71,7 +57,7 @@ Artist::Artist(Filters* filters) : _filters(filters) { if (!Setting::exists(LmsApp->getDboSession(), artistClusterTypesSetting)) - Setting::setString(LmsApp->getDboSession(), artistClusterTypesSetting, joinStrings(defaultArtistClusterTypes, " ")); + setClusterTypesToSetting(artistClusterTypesSetting, defaultArtistClusterTypes); wApp->internalPathChanged().connect(std::bind([=] { @@ -112,7 +98,7 @@ Artist::refresh() Wt::WContainerWidget* clusterContainers = t->bindNew("clusters"); { - auto clusterTypes = getArtistClusterTypes(LmsApp->getDboSession()); + auto clusterTypes = getClusterTypesFromSetting(artistClusterTypesSetting); auto clusterGroups = artist->getClusterGroups(clusterTypes, 3); for (auto clusters : clusterGroups) diff --git a/src/ui/explore/ArtistsView.cpp b/src/ui/explore/ArtistsView.cpp index 249d8c93..2c953754 100644 --- a/src/ui/explore/ArtistsView.cpp +++ b/src/ui/explore/ArtistsView.cpp @@ -31,34 +31,20 @@ #include "LmsApplication.hpp" #include "Filters.hpp" +#include "ExploreUtils.hpp" using namespace Database; namespace { const std::string artistsClusterTypesSetting = "artists_cluster_types"; -const std::vector defaultArtistsClusterTypes = +const std::set defaultArtistsClusterTypes = { "ALBUMGROUPING", "GENRE", "ALBUMMOOD", }; -std::vector getArtistsClusterTypes(Wt::Dbo::Session& session) -{ - Wt::Dbo::Transaction transaction(session); - - std::vector res; - for (auto clusterTypeName : splitString(Setting::getString(session, artistsClusterTypesSetting), " ")) - { - auto clusterType = ClusterType::getByName(session, clusterTypeName); - if (clusterType) - res.push_back(clusterType); - } - - return res; -} - } namespace UserInterface { @@ -68,7 +54,7 @@ Artists::Artists(Filters* filters) _filters(filters) { if (!Setting::exists(LmsApp->getDboSession(), artistsClusterTypesSetting)) - Setting::setString(LmsApp->getDboSession(), artistsClusterTypesSetting, joinStrings(defaultArtistsClusterTypes, " ")); + setClusterTypesToSetting(artistsClusterTypesSetting, defaultArtistsClusterTypes); addFunction("tr", &Wt::WTemplate::Functions::tr); @@ -123,7 +109,7 @@ Artists::addSome() Wt::WContainerWidget* clusterContainers = entry->bindNew("clusters"); { - auto clusterTypes = getArtistsClusterTypes(LmsApp->getDboSession()); + auto clusterTypes = getClusterTypesFromSetting(artistsClusterTypesSetting); auto clusterGroups = artist->getClusterGroups(clusterTypes, 1); for (auto clusters : clusterGroups) diff --git a/src/ui/explore/ExploreUtils.cpp b/src/ui/explore/ExploreUtils.cpp new file mode 100644 index 00000000..6ecaeccc --- /dev/null +++ b/src/ui/explore/ExploreUtils.cpp @@ -0,0 +1,51 @@ + +/* + * Copyright (C) 2018 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "ExploreUtils.hpp" + +#include "utils/Utils.hpp" + +#include "database/Setting.hpp" + +#include "LmsApplication.hpp" + +namespace UserInterface { + +std::vector getClusterTypesFromSetting(std::string setting) +{ + std::vector res; + for (auto clusterTypeName : splitString(Database::Setting::getString(LmsApp->getDboSession(), setting), " ")) + { + auto clusterType = Database::ClusterType::getByName(LmsApp->getDboSession(), clusterTypeName); + if (clusterType) + res.push_back(clusterType); + } + + return res; +} + +void setClusterTypesToSetting(std::string setting, const std::set& clusterTypes) +{ + std::vector vec(clusterTypes.begin(), clusterTypes.end()); + Database::Setting::setString(LmsApp->getDboSession(), setting, joinStrings(vec, " ")); +} + +} // namespace UserInterface + diff --git a/src/ui/explore/ExploreUtils.hpp b/src/ui/explore/ExploreUtils.hpp new file mode 100644 index 00000000..a46a2be9 --- /dev/null +++ b/src/ui/explore/ExploreUtils.hpp @@ -0,0 +1,29 @@ + +/* + * Copyright (C) 2018 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "database/Track.hpp" + +namespace UserInterface { + +std::vector getClusterTypesFromSetting(std::string setting); + +void setClusterTypesToSetting(std::string setting, const std::set& clusterTypes); + +} // namespace UserInterface diff --git a/src/ui/explore/ReleaseView.cpp b/src/ui/explore/ReleaseView.cpp index f03ccaf2..079d9ecb 100644 --- a/src/ui/explore/ReleaseView.cpp +++ b/src/ui/explore/ReleaseView.cpp @@ -35,13 +35,14 @@ #include "LmsApplication.hpp" #include "Filters.hpp" +#include "ExploreUtils.hpp" using namespace Database; namespace { const std::string releaseClusterTypesSetting = "release_cluster_types"; -const std::vector defaultReleaseClusterTypes = +const std::set defaultReleaseClusterTypes = { "GENRE", "ALBUMGROUPING", @@ -49,20 +50,6 @@ const std::vector defaultReleaseClusterTypes = "COMMENT:SONGS-DB_OCCASION", }; -std::vector getReleaseClusterTypes(Wt::Dbo::Session& session) -{ - Wt::Dbo::Transaction transaction(session); - - std::vector res; - for (auto clusterTypeName : splitString(Setting::getString(session, releaseClusterTypesSetting), " ")) - { - auto clusterType = ClusterType::getByName(session, clusterTypeName); - if (clusterType) - res.push_back(clusterType); - } - - return res; -} } // namespace namespace UserInterface { @@ -71,7 +58,7 @@ Release::Release(Filters* filters) : _filters(filters) { if (!Setting::exists(LmsApp->getDboSession(), releaseClusterTypesSetting)) - Setting::setString(LmsApp->getDboSession(), releaseClusterTypesSetting, joinStrings(defaultReleaseClusterTypes, " ")); + setClusterTypesToSetting(releaseClusterTypesSetting, defaultReleaseClusterTypes); wApp->internalPathChanged().connect(std::bind([=] { @@ -143,7 +130,7 @@ Release::refresh() Wt::WContainerWidget* clusterContainers = t->bindNew("clusters"); { - auto clusterTypes = getReleaseClusterTypes(LmsApp->getDboSession()); + auto clusterTypes = getClusterTypesFromSetting(releaseClusterTypesSetting); auto clusterGroups = release->getClusterGroups(clusterTypes, 3); for (auto clusters : clusterGroups) diff --git a/src/ui/explore/ReleasesView.cpp b/src/ui/explore/ReleasesView.cpp index 4812e2e3..81bddc25 100644 --- a/src/ui/explore/ReleasesView.cpp +++ b/src/ui/explore/ReleasesView.cpp @@ -35,33 +35,19 @@ #include "LmsApplication.hpp" #include "Filters.hpp" +#include "ExploreUtils.hpp" using namespace Database; namespace { const std::string releasesClusterTypesSetting = "releases_cluster_types"; -const std::vector defaultReleasesClusterTypes = +const std::set defaultReleasesClusterTypes = { "GENRE", "ALBUMGROUPING", }; -std::vector getReleasesClusterTypes(Wt::Dbo::Session& session) -{ - Wt::Dbo::Transaction transaction(session); - - std::vector res; - for (auto clusterTypeName : splitString(Setting::getString(session, releasesClusterTypesSetting), " ")) - { - auto clusterType = ClusterType::getByName(session, clusterTypeName); - if (clusterType) - res.push_back(clusterType); - } - - return res; -} - } // namespace namespace UserInterface { @@ -71,7 +57,7 @@ Releases::Releases(Filters* filters) _filters(filters) { if (!Setting::exists(LmsApp->getDboSession(), releasesClusterTypesSetting)) - Setting::setString(LmsApp->getDboSession(), releasesClusterTypesSetting, joinStrings(defaultReleasesClusterTypes, " ")); + setClusterTypesToSetting(releasesClusterTypesSetting, defaultReleasesClusterTypes); addFunction("tr", &Wt::WTemplate::Functions::tr); @@ -140,7 +126,7 @@ Releases::addSome() Wt::WContainerWidget* clusterContainers = entry->bindNew("clusters"); { - auto clusterTypes = getReleasesClusterTypes(LmsApp->getDboSession()); + auto clusterTypes = getClusterTypesFromSetting(releasesClusterTypesSetting); auto clusterGroups = release->getClusterGroups(clusterTypes, 1); for (auto clusters : clusterGroups) diff --git a/src/ui/explore/TracksView.cpp b/src/ui/explore/TracksView.cpp index 53862e1c..5b7796e1 100644 --- a/src/ui/explore/TracksView.cpp +++ b/src/ui/explore/TracksView.cpp @@ -27,6 +27,7 @@ #include "database/DbArtist.hpp" #include "database/Track.hpp" #include "database/Release.hpp" +#include "database/Setting.hpp" #include "utils/Logger.hpp" #include "utils/Utils.hpp" @@ -35,15 +36,32 @@ #include "LmsApplication.hpp" #include "Filters.hpp" - -namespace UserInterface { +#include "ExploreUtils.hpp" using namespace Database; +namespace { + +const std::string trackClusterTypesSetting = "track_cluster_types"; +const std::set defaultTrackClusterTypes = +{ + "GENRE", + "ALBUMGROUPING", + "ALBUMMOOD", + "COMMENT:SONGS-DB_OCCASION", +}; + +} // namespace + +namespace UserInterface { + Tracks::Tracks(Filters* filters) : Wt::WTemplate(Wt::WString::tr("Lms.Explore.Tracks.template")), _filters(filters) { + if (!Setting::exists(LmsApp->getDboSession(), trackClusterTypesSetting)) + setClusterTypesToSetting(trackClusterTypesSetting, defaultTrackClusterTypes); + addFunction("tr", &Wt::WTemplate::Functions::tr); _search = bindNew("search"); @@ -136,6 +154,25 @@ Tracks::addSome() // Some images may not be square cover->setWidth(64); + Wt::WContainerWidget* clusterContainers = entry->bindNew("clusters"); + { + auto clusterTypes = getClusterTypesFromSetting(trackClusterTypesSetting); + auto clusterGroups = release->getClusterGroups(clusterTypes, 1); + + for (auto clusters : clusterGroups) + { + for (auto cluster : clusters) + { + auto clusterId = cluster.id(); + auto entry = clusterContainers->addWidget(LmsApp->createCluster(cluster)); + entry->clicked().connect([=] + { + _filters->add(clusterId); + }); + } + } + } + Wt::WText* playBtn = entry->bindNew("play-btn", Wt::WString::tr("Lms.Explore.Tracks.play"), Wt::TextFormat::XHTML); playBtn->clicked().connect(std::bind([=] {