diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 4ad03b9a..03d32c2b 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -16,11 +16,11 @@ } .Lms-cluster-type-4 { - background-color: darkslateblue; + background-color: darkgoldenrod; } .Lms-cluster-type-5 { - background-color: darkslateblue; + background-color: darkkhaki; } .Lms-cluster-type-6 { diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp index 0f2ebacc..823cf35c 100644 --- a/src/database/DbArtist.cpp +++ b/src/database/DbArtist.cpp @@ -182,23 +182,48 @@ Artist::getReleases(const std::set& clusterIds) const return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); } -std::vector> -Artist::getClusters(int size) const +std::vector>> +Artist::getClusterGroups(std::vector clusterTypes, std::size_t size) const { assert(self()); assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); assert(session()); - Wt::Dbo::Query query = session()->query - ("select c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN artist a ON t.artist_id = a.id") - .where("a.id = ?").bind(self()->id()) - .groupBy("c.id") - .orderBy("COUNT(c.id) DESC") - .limit(size); + WhereClause where; - Wt::Dbo::collection res = query; + 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 INNER JOIN artist a ON t.artist_id = a.id"; - return std::vector(res.begin(), res.end()); + where.And(WhereClause("a.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; } } // namespace Database diff --git a/src/database/DbArtist.hpp b/src/database/DbArtist.hpp index 89c9a92b..1b5f6629 100644 --- a/src/database/DbArtist.hpp +++ b/src/database/DbArtist.hpp @@ -31,6 +31,7 @@ namespace Database class Track; class Cluster; +class ClusterType; class Release; class Artist : public Wt::Dbo::Dbo @@ -65,7 +66,9 @@ class Artist : public Wt::Dbo::Dbo std::vector> getReleases(const std::set& clusterIds = std::set()) const; // Get the cluster of the tracks made by this artist - std::vector> getClusters(int size) const; + // Each clusters are grouped by cluster type, sorted by the number of occurence + // size is the max number of cluster per cluster type + std::vector>> getClusterGroups(std::vector> clusterTypes, std::size_t size) const; void setMBID(std::string mbid) { _MBID = mbid; } diff --git a/src/database/Release.cpp b/src/database/Release.cpp index 92be18e2..0fe6ba65 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -226,23 +226,48 @@ Release::getTracks(const std::set& clusterIds) const return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); } -std::vector> -Release::getClusters(int size) const +std::vector>> +Release::getClusterGroups(std::vector clusterTypes, std::size_t size) const { assert(self()); assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); assert(session()); - Wt::Dbo::Query query = session()->query - ("select c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON t.release_id = r.id") - .where("r.id = ?").bind(self()->id()) - .groupBy("c.id") - .orderBy("COUNT(c.id) DESC") - .limit(size); + WhereClause where; - Wt::Dbo::collection res = query; + std::ostringstream oss; - return std::vector(res.begin(), res.end()); + 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 INNER JOIN release r ON t.release_id = r.id"; + + where.And(WhereClause("r.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; } } // namespace Database diff --git a/src/database/Release.hpp b/src/database/Release.hpp index 1e143e29..c7020556 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -55,7 +55,10 @@ class Release : public Wt::Dbo::Dbo bool& moreExpected); std::vector> getTracks(const std::set& clusters = std::set()) const; - std::vector> getClusters(int size) const; + // Get the cluster of the tracks that belong to this release + // Each clusters are grouped by cluster type, sorted by the number of occurence + // size is the max number of cluster per cluster type + std::vector>> getClusterGroups(std::vector> clusterTypes, std::size_t size) const; // Create static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); diff --git a/src/metadata/AvFormat.cpp b/src/metadata/AvFormat.cpp index deb9db2b..d2ba1788 100644 --- a/src/metadata/AvFormat.cpp +++ b/src/metadata/AvFormat.cpp @@ -36,7 +36,7 @@ AvFormat::AvFormat(const ClusterTypes& clusterTypes) } boost::optional -AvFormat::parse(const boost::filesystem::path& p) +AvFormat::parse(const boost::filesystem::path& p, bool debug) { Items items; @@ -73,9 +73,10 @@ AvFormat::parse(const boost::filesystem::path& p) { const std::string tag = boost::to_upper_copy(metadata.first); const std::string value = metadata.second; -#if 0 - std::cout << "TAG = " << tag << ", VAL = " << value << std::endl; -#endif + + if (debug) + std::cout << "TAG = " << tag << ", VAL = " << value << std::endl; + if (tag == "ARTIST") items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( value) )); else if (tag == "ALBUM") diff --git a/src/metadata/AvFormat.hpp b/src/metadata/AvFormat.hpp index aaec59fe..9039c90a 100644 --- a/src/metadata/AvFormat.hpp +++ b/src/metadata/AvFormat.hpp @@ -34,7 +34,7 @@ class AvFormat : public Parser AvFormat(const ClusterTypes& clusterTypes = defaultClusterTypes); - boost::optional parse(const boost::filesystem::path& p); + boost::optional parse(const boost::filesystem::path& p, bool debug = false); }; } // namespace MetaData diff --git a/src/metadata/MetaData.cpp b/src/metadata/MetaData.cpp index fc22e27d..d949ef93 100644 --- a/src/metadata/MetaData.cpp +++ b/src/metadata/MetaData.cpp @@ -23,8 +23,11 @@ namespace MetaData { const ClusterTypes Parser::defaultClusterTypes = { - {"GENRE"}, - {"ALBUMGROUPING"}, + "GENRE", + "ALBUMGROUPING", + "MOOD", + "ALBUMMOOD", + "COMMENT:SONGS-DB_OCCASION", }; } // namespace MetaData diff --git a/src/metadata/MetaData.hpp b/src/metadata/MetaData.hpp index 11606b70..f09b1504 100644 --- a/src/metadata/MetaData.hpp +++ b/src/metadata/MetaData.hpp @@ -73,7 +73,7 @@ namespace MetaData // Provide a map for tag name -> Cluster name Parser(const ClusterTypes& clusterTypes) : _clusterTypes(clusterTypes) {} - virtual boost::optional parse(const boost::filesystem::path& p) = 0; + virtual boost::optional parse(const boost::filesystem::path& p, bool debug = false) = 0; void updateClusterTypes(const ClusterTypes& clusterTypes) { _clusterTypes = clusterTypes; } const ClusterTypes& getClusterTypes() const { return _clusterTypes; } diff --git a/src/metadata/TagLibParser.cpp b/src/metadata/TagLibParser.cpp index acc12cc4..373781e3 100644 --- a/src/metadata/TagLibParser.cpp +++ b/src/metadata/TagLibParser.cpp @@ -38,7 +38,7 @@ TagLibParser::TagLibParser(const ClusterTypes& clusterTypes) } boost::optional -TagLibParser::parse(const boost::filesystem::path& p) +TagLibParser::parse(const boost::filesystem::path& p, bool debug) { TagLib::FileRef f(p.string().c_str(), true, // read audio properties @@ -89,13 +89,13 @@ TagLibParser::parse(const boost::filesystem::path& p) // TODO validate MBID format -#if 0 - std::cout << "TAG = '" << tag << "'" << std::endl; - for (auto value : values) + if (debug) { - std::cout << "\t'" << value.to8Bit(true) << "'" << std::endl; + std::cout << "TAG = '" << tag << "', VALUES = "; + for (auto value : values) + std::cout << "'" << value.to8Bit(true) << "',"; + std::cout << std::endl; } -#endif if (tag == "ARTIST") items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( values.front().to8Bit(true)))); diff --git a/src/metadata/TagLibParser.hpp b/src/metadata/TagLibParser.hpp index 1b85ed0d..2b6c7425 100644 --- a/src/metadata/TagLibParser.hpp +++ b/src/metadata/TagLibParser.hpp @@ -34,7 +34,7 @@ class TagLibParser : public Parser TagLibParser(const ClusterTypes& clusterTypes = defaultClusterTypes); - boost::optional parse(const boost::filesystem::path& p); + boost::optional parse(const boost::filesystem::path& p, bool debug = false); }; } // namespace MetaData diff --git a/src/scanner/MediaScanner.cpp b/src/scanner/MediaScanner.cpp index 933adb25..06864e7c 100644 --- a/src/scanner/MediaScanner.cpp +++ b/src/scanner/MediaScanner.cpp @@ -39,7 +39,7 @@ namespace { const std::string updatePeriodSetting = "update_period"; const std::string updateStartTimeSetting = "update_start_time"; -const std::string clustersSetting = "clusters"; +const std::string clusterTypesSetting = "cluster_types"; const std::string fileExtensionsSetting = "file_extensions"; const std::vector defaultFileExtensions = @@ -152,10 +152,10 @@ _db(connectionPool) if (!Setting::exists(_db.getSession(), fileExtensionsSetting)) Setting::setString(_db.getSession(), fileExtensionsSetting, joinStrings(defaultFileExtensions, " ")); - if (!Setting::exists(_db.getSession(), clustersSetting)) + if (!Setting::exists(_db.getSession(), clusterTypesSetting)) { std::vector defaultClusterTypes(MetaData::Parser::defaultClusterTypes.begin(), MetaData::Parser::defaultClusterTypes.end()); - Setting::setString(_db.getSession(), clustersSetting, joinStrings(defaultClusterTypes, " ")); + Setting::setString(_db.getSession(), clusterTypesSetting, joinStrings(defaultClusterTypes, " ")); } refreshScanSettings(); @@ -324,7 +324,7 @@ MediaScanner::refreshScanSettings() _rootDirectories.push_back(rootDir->getPath()); MetaData::ClusterTypes clusterTypes; - for (auto cluster : splitString(Setting::getString(_db.getSession(), clustersSetting), " ")) + for (auto cluster : splitString(Setting::getString(_db.getSession(), clusterTypesSetting), " ")) clusterTypes.insert(cluster); _metadataParser.updateClusterTypes(clusterTypes); diff --git a/src/ui/explore/ArtistView.cpp b/src/ui/explore/ArtistView.cpp index 3de4130f..3bae5b00 100644 --- a/src/ui/explore/ArtistView.cpp +++ b/src/ui/explore/ArtistView.cpp @@ -23,6 +23,7 @@ #include #include "database/Types.hpp" +#include "database/Setting.hpp" #include "utils/Logger.hpp" #include "utils/Utils.hpp" @@ -33,11 +34,44 @@ #include "Filters.hpp" #include "ArtistView.hpp" +using namespace Database; + +namespace { + +const std::string artistClusterTypesSetting = "artist_cluster_types"; +const std::vector defaultArtistClusterTypes = +{ + "GENRE", + "ALBUMGROUPING", + "ALBUMMOOD", + "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 { Artist::Artist(Filters* filters) : _filters(filters) { + if (!Setting::exists(LmsApp->getDboSession(), artistClusterTypesSetting)) + Setting::setString(LmsApp->getDboSession(), artistClusterTypesSetting, joinStrings(defaultArtistClusterTypes, " ")); + wApp->internalPathChanged().connect(std::bind([=] { refresh(); @@ -64,6 +98,7 @@ Artist::refresh() Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto artist = Database::Artist::getById(LmsApp->getDboSession(), *artistId); + if (!artist) { LmsApp->goHome(); @@ -76,16 +111,20 @@ Artist::refresh() Wt::WContainerWidget* clusterContainers = t->bindNew("clusters"); { - auto clusters = artist->getClusters(3); + auto clusterTypes = getArtistClusterTypes(LmsApp->getDboSession()); + auto clusterGroups = artist->getClusterGroups(clusterTypes, 3); - for (auto cluster : clusters) + for (auto clusters : clusterGroups) { - auto clusterId = cluster.id(); - auto entry = clusterContainers->addWidget(LmsApp->createCluster(cluster)); - entry->clicked().connect([=] + for (auto cluster : clusters) { - _filters->add(clusterId); - }); + auto clusterId = cluster.id(); + auto entry = clusterContainers->addWidget(LmsApp->createCluster(cluster)); + entry->clicked().connect([=] + { + _filters->add(clusterId); + }); + } } } diff --git a/src/ui/explore/ReleaseView.cpp b/src/ui/explore/ReleaseView.cpp index 4d24b1bb..fe8744e4 100644 --- a/src/ui/explore/ReleaseView.cpp +++ b/src/ui/explore/ReleaseView.cpp @@ -24,6 +24,7 @@ #include #include "database/Types.hpp" +#include "database/Setting.hpp" #include "utils/Logger.hpp" #include "utils/Utils.hpp" @@ -34,11 +35,43 @@ #include "Filters.hpp" #include "ReleaseView.hpp" +using namespace Database; + +namespace { + +const std::string releaseClusterTypesSetting = "release_cluster_types"; +const std::vector defaultReleaseClusterTypes = +{ + "GENRE", + "ALBUMGROUPING", + "ALBUMMOOD", + "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 { Release::Release(Filters* filters) : _filters(filters) { + if (!Setting::exists(LmsApp->getDboSession(), releaseClusterTypesSetting)) + Setting::setString(LmsApp->getDboSession(), releaseClusterTypesSetting, joinStrings(defaultReleaseClusterTypes, " ")); + wApp->internalPathChanged().connect(std::bind([=] { refresh(); @@ -109,16 +142,20 @@ Release::refresh() Wt::WContainerWidget* clusterContainers = t->bindNew("clusters"); { - auto clusters = release->getClusters(3); + auto clusterTypes = getReleaseClusterTypes(LmsApp->getDboSession()); + auto clusterGroups = release->getClusterGroups(clusterTypes, 3); - for (auto cluster : clusters) + for (auto clusters : clusterGroups) { - auto clusterId = cluster.id(); - auto entry = clusterContainers->addWidget(LmsApp->createCluster(cluster)); - entry->clicked().connect([=] + for (auto cluster : clusters) { - _filters->add(clusterId); - }); + auto clusterId = cluster.id(); + auto entry = clusterContainers->addWidget(LmsApp->createCluster(cluster)); + entry->clicked().connect([=] + { + _filters->add(clusterId); + }); + } } } diff --git a/test/TestAvMetadata.cpp b/test/TestAvMetadata.cpp index 9dc0d4c5..93985aac 100644 --- a/test/TestAvMetadata.cpp +++ b/test/TestAvMetadata.cpp @@ -30,7 +30,7 @@ int main(int argc, char *argv[]) for (auto& parser : parsers) { - boost::optional items = parser->parse(argv[1]); + boost::optional items = parser->parse(argv[1], true); if (!items) {