diff --git a/src/Makefile.am b/src/Makefile.am index 2c38a9d8..ca24f715 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,7 +6,6 @@ lms_SOURCES = \ $(srcdir)/av/AvTranscoder.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/database/Artist.cpp \ - $(srcdir)/database/Classification.cpp \ $(srcdir)/database/DatabaseClassifier.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/DatabaseUpdater.cpp \ diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp index 03008285..b141986d 100644 --- a/src/database/Artist.cpp +++ b/src/database/Artist.cpp @@ -126,7 +126,7 @@ Artist::getQuery(Wt::Dbo::Session& session, SearchFilter filter) SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query( "SELECT a FROM artist a INNER JOIN track t ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name"); + = session.query( "SELECT a FROM artist a INNER JOIN track t ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -140,7 +140,7 @@ Artist::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query( "SELECT a.id, a.name, COUNT(DISTINCT r.id), COUNT(DISTINCT t.id) FROM artist a INNER JOIN track t ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name"); + = session.query( "SELECT a.id, a.name, COUNT(DISTINCT r.id), COUNT(DISTINCT t.id) FROM artist a INNER JOIN track t ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp index 0fe27776..638a0847 100644 --- a/src/database/Artist.hpp +++ b/src/database/Artist.hpp @@ -32,7 +32,7 @@ namespace Database { class Track; -class Genre; +class Cluster; class Release; class Artist : public Wt::Dbo::Dbo diff --git a/src/database/Classification.cpp b/src/database/Classification.cpp deleted file mode 100644 index 63a8c602..00000000 --- a/src/database/Classification.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2016 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 "Track.hpp" - -#include "Classification.hpp" - -namespace Database { - -Feature::Feature(Wt::Dbo::ptr track, const std::string& type, const std::string& value) -: _type(type), -_value(value), -_track(track) -{ -} - -Feature::pointer -Feature::create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, const std::string& type, const std::string& value) -{ - return session.add(new Feature(track, type, value)); -} - -std::vector -Feature::getByTrack(Wt::Dbo::Session& session, Track::id_type trackId, const std::string& type) -{ - Wt::Dbo::collection res = session.find().where("track_id = ? AND type = ?").bind( trackId).bind(type); - return std::vector(res.begin(), res.end()); -} - -} // namespace Database - diff --git a/src/database/Classification.hpp b/src/database/Classification.hpp deleted file mode 100644 index a219cf96..00000000 --- a/src/database/Classification.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ - -#pragma once - -#include - -namespace Database { - -class Track; - -class Feature -{ - public: - typedef Wt::Dbo::ptr pointer; - - Feature() {} - Feature(Wt::Dbo::ptr track, const std::string& type, const std::string& value); - - static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, const std::string& type, const std::string& value); - - static std::vector getByTrack(Wt::Dbo::Session& session, Track::id_type trackId, const std::string& type); - - std::string getType(void) const { return _type; } - std::string getValue(void) const { return _value; } - - template - void persist(Action& a) - { - Wt::Dbo::field(a, _type, "type"); - Wt::Dbo::field(a, _value, "value"); - Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); - } - - private: - std::string _type; - std::string _value; - - Wt::Dbo::ptr _track; -}; - -class Classification -{ - public: - typedef Wt::Dbo::ptr pointer; - - Classification() {} - - template - void persist(Action& a) - { - Wt::Dbo::field(a, _type, "type"); - Wt::Dbo::field(a, _value, "value"); - Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); - } - - private: - - std::string _type; - int _value; - - Wt::Dbo::ptr _track; -}; - -} // namespace Database - diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index 7458a46b..7e76dfaa 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -81,9 +81,8 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) _session.mapClass("artist"); - _session.mapClass("genre"); + _session.mapClass("cluster"); _session.mapClass("track"); - _session.mapClass("classification"); _session.mapClass("feature"); _session.mapClass("playlist"); _session.mapClass("playlist_entry"); @@ -102,10 +101,11 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) _session.createTables(); _session.execute("CREATE INDEX artist_name_idx ON artist(name)"); - _session.execute("CREATE INDEX genre_name_idx ON genre(name)"); + _session.execute("CREATE INDEX cluster_name_idx ON cluster(name)"); + _session.execute("CREATE INDEX cluster_type_idx ON cluster(type)"); _session.execute("CREATE INDEX release_name_idx ON release(name)"); _session.execute("CREATE INDEX track_name_idx ON track(name)"); - _session.execute("CREATE INDEX feature_type ON feature(type)"); + _session.execute("CREATE INDEX feature_type_idx ON feature(type)"); } catch(std::exception& e) { LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what(); diff --git a/src/database/DatabaseUpdater.cpp b/src/database/DatabaseUpdater.cpp index 219da9ee..85a2c923 100644 --- a/src/database/DatabaseUpdater.cpp +++ b/src/database/DatabaseUpdater.cpp @@ -378,22 +378,22 @@ Updater::getRelease( const boost::filesystem::path& file, const std::string& nam return Release::getNone( _db->getSession() ); } -std::vector -Updater::getGenres( const std::list& names) +std::vector +Updater::getGenreClusters( const std::list& names) { - std::vector< Genre::pointer > genres; + std::vector< Cluster::pointer > genres; for (const std::string& name : names) { - Genre::pointer genre ( Genre::getByName(_db->getSession(), name) ); + Cluster::pointer genre ( Cluster::get(_db->getSession(), "Genre", name) ); if (!genre) - genre = Genre::create(_db->getSession(), name); + genre = Cluster::create(_db->getSession(), "Genre", name); genres.push_back( genre ); } if (genres.empty()) - genres.push_back( Genre::getNone( _db->getSession() )); + genres.push_back( Cluster::getNone( _db->getSession() )); return genres; } @@ -480,15 +480,16 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) title = file.filename().string(); } - // ***** Genres - std::vector< Genre::pointer > genres; + // ***** Clusters + std::vector< Cluster::pointer > genres; { std::list genreList; if (items.find(MetaData::Type::Genres) != items.end()) genreList = boost::any_cast< std::list > (items[MetaData::Type::Genres]); - genres = getGenres( genreList ); + // TODO rename + genres = getGenreClusters( genreList ); } assert( !genres.empty() ); @@ -550,18 +551,19 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) track.modify()->setAddedTime( boost::posix_time::second_clock::local_time() ); { - std::string trackGenreList; + std::string trackClusterList; // Product genre list - for (Genre::pointer genre : genres) + for (Cluster::pointer genre : genres) { - if (!trackGenreList.empty()) - trackGenreList += ", "; - trackGenreList += genre->getName(); + if (!trackClusterList.empty()) + trackClusterList += ", "; + trackClusterList += genre->getName(); + + genre.modify()->addTrack(track); } - track.modify()->setGenres( trackGenreList ); + track.modify()->setGenres( trackClusterList ); } - track.modify()->setGenres( genres ); if (items.find(MetaData::Type::TrackNumber) != items.end()) track.modify()->setTrackNumber( boost::any_cast(items[MetaData::Type::TrackNumber]) ); @@ -599,11 +601,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None ); } - Track::id_type trackId = track.id(); - transaction.commit(); - _sigTrackChanged.emit(true, trackId); + _sigTrackChanged.emit(true, track.id()); } @@ -720,12 +720,12 @@ Updater::checkAudioFiles( Stats& stats ) } } - LMS_LOG(DBUPDATER, DEBUG) << "Checking Genres..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking Clusters..."; { Wt::Dbo::Transaction transaction(_db->getSession()); - // Now process orphan Genre (no track) - auto genres = Genre::getAll(_db->getSession()); + // Now process orphan Cluster (no track) + auto genres = Cluster::getAll(_db->getSession()); for (auto genre : genres) { if (genre->getTracks().size() == 0) diff --git a/src/database/DatabaseUpdater.hpp b/src/database/DatabaseUpdater.hpp index 3ef4cda0..3e543951 100644 --- a/src/database/DatabaseUpdater.hpp +++ b/src/database/DatabaseUpdater.hpp @@ -100,7 +100,7 @@ class Updater // Helpers Database::Artist::pointer getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& MBID); Database::Release::pointer getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& MBID); - std::vector getGenres( const std::list& names); + std::vector getGenreClusters( const std::list& names); void updateFileExtensions(); // Audio diff --git a/src/database/Release.cpp b/src/database/Release.cpp index 25c1b655..d82cbcef 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -93,7 +93,7 @@ Release::getQuery(Wt::Dbo::Session& session, SearchFilter filter) SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query("SELECT r FROM release r INNER JOIN artist a ON a.id = t.artist_id INNER JOIN track t ON t.release_id = r.id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); + = session.query("SELECT r FROM release r INNER JOIN artist a ON a.id = t.artist_id INNER JOIN track t ON t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -108,7 +108,7 @@ Release::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) // TODO DATE of RELEASE Wt::Dbo::Query query - = session.query("SELECT r.id, r.name, t.date, COUNT(DISTINCT t.id) FROM release r INNER JOIN track t ON t.release_id = r.id INNER JOIN artist a ON a.id = t.artist_id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); + = session.query("SELECT r.id, r.name, t.date, COUNT(DISTINCT t.id) FROM release r INNER JOIN track t ON t.release_id = r.id INNER JOIN artist a ON a.id = t.artist_id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); diff --git a/src/database/SearchFilter.cpp b/src/database/SearchFilter.cpp index f6dbaa1c..eafd86d5 100644 --- a/src/database/SearchFilter.cpp +++ b/src/database/SearchFilter.cpp @@ -60,9 +60,9 @@ SqlQuery generatePartialQuery(SearchFilter& filter) likeWhereClause.Or( WhereClause("r.name LIKE ?") ).bind("%%" + name + "%%"); break; - case SearchFilter::Field::Genre: + case SearchFilter::Field::Cluster: for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("g.name LIKE ?") ).bind("%%" + name + "%%"); + likeWhereClause.Or( WhereClause("c.name LIKE ?") ).bind("%%" + name + "%%"); break; case SearchFilter::Field::Track: @@ -97,10 +97,10 @@ SqlQuery generatePartialQuery(SearchFilter& filter) idWhereClause.Or(oss.str()); } break; - case SearchFilter::Field::Genre: + case SearchFilter::Field::Cluster: { std::ostringstream oss; - oss << "g.id IN (" << idMatch.second << ")"; + oss << "c.id IN (" << idMatch.second << ")"; idWhereClause.Or(oss.str()); } break; diff --git a/src/database/SearchFilter.hpp b/src/database/SearchFilter.hpp index ad8716d8..2156ecfe 100644 --- a/src/database/SearchFilter.hpp +++ b/src/database/SearchFilter.hpp @@ -38,8 +38,8 @@ class SearchFilter enum class Field { Artist, // artist Release, // release - Genre, // genre Track, // track + Cluster, // cluster }; typedef std::vector > > NameLikeMatchType; diff --git a/src/database/Track.cpp b/src/database/Track.cpp index be705d70..138e1a52 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2013-2016 Emeric Poupon * * This file is part of LMS. * @@ -51,17 +51,6 @@ Track::getAllIds(Wt::Dbo::Session& session) return std::vector(res.begin(), res.end()); } -void -Track::setGenres(std::vector genres) -{ - if (_genres.size()) - _genres.clear(); - - for (Genre::pointer genre : genres) { - _genres.insert( genre ); - } -} - Track::pointer Track::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p) { @@ -108,12 +97,12 @@ Track::getChecksumDuplicates(Wt::Dbo::Session& session) return std::vector(res.begin(), res.end()); } -std::vector< Genre::pointer > -Track::getGenres(void) const +std::vector< Cluster::pointer > +Track::getClusters(void) const { - std::vector< Genre::pointer > genres; - std::copy(_genres.begin(), _genres.end(), std::back_inserter(genres)); - return genres; + std::vector< Cluster::pointer > clusters; + std::copy(_clusters.begin(), _clusters.end(), std::back_inserter(clusters)); + return clusters; } std::vector< Wt::Dbo::ptr > @@ -130,7 +119,7 @@ Track::getQuery(Wt::Dbo::Session& session, SearchFilter filter) SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query( "SELECT t FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); + = session.query( "SELECT t FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c 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 r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -144,7 +133,7 @@ Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query( "SELECT t.id, a.name, r.name, t.disc_number, t.track_number, t.name, t.duration, t.date, t.original_date, t.genre_list FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); + = session.query( "SELECT t.id, a.name, r.name, t.disc_number, t.track_number, t.name, t.duration, t.date, t.original_date, t.genre_list FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c 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 r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -157,7 +146,7 @@ Track::getStats(Wt::Dbo::Session& session, SearchFilter filter) { SqlQuery sqlQuery = generatePartialQuery(filter); - Wt::Dbo::Query query = session.query( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)"); + Wt::Dbo::Query query = session.query( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c 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 r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -213,56 +202,58 @@ Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQuer } -Genre::Genre() +Cluster::Cluster() { } -Genre::Genre(const std::string& name) -: _name( std::string(name, 0, _maxNameLength) ) +Cluster::Cluster(std::string type, std::string name) +: +_type( std::string(type, 0, _maxTypeLength)), +_name( std::string(name, 0, _maxNameLength)) { } -Wt::Dbo::collection -Genre::getAll(Wt::Dbo::Session& session) +Wt::Dbo::collection +Cluster::getAll(Wt::Dbo::Session& session) { - return session.find(); + return session.find(); } -Genre::pointer -Genre::getByName(Wt::Dbo::Session& session, const std::string& name) +Cluster::pointer +Cluster::get(Wt::Dbo::Session& session, std::string type, std::string name) { // TODO use like search - return session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); + return session.find().where("type = ?").where("name = ?").bind( std::string(type, 0, _maxTypeLength)).bind( std::string(name, 0, _maxNameLength)); } -Genre::pointer -Genre::getNone(Wt::Dbo::Session& session) +Cluster::pointer +Cluster::getNone(Wt::Dbo::Session& session) { - pointer res = getByName(session, ""); + pointer res = get(session, "", ""); if (!res) - res = create(session, ""); + res = create(session, "", ""); return res; } bool -Genre::isNone(void) const +Cluster::isNone(void) const { - return (_name == ""); + return (_type == "" && _name == ""); } -Genre::pointer -Genre::create(Wt::Dbo::Session& session, const std::string& name) +Cluster::pointer +Cluster::create(Wt::Dbo::Session& session, std::string type, std::string name) { - return session.add(new Genre(name)); + return session.add(new Cluster(type, name)); } -Wt::Dbo::Query -Genre::getQuery(Wt::Dbo::Session& session, SearchFilter filter) +Wt::Dbo::Query +Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter) { SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query( "SELECT g FROM genre g INNER JOIN track_genre t_g ON t_g.genre_id = g.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_g.track_id " + sqlQuery.where().get()).groupBy("g.name").orderBy("g.name"); + = session.query( "SELECT g FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -270,13 +261,13 @@ Genre::getQuery(Wt::Dbo::Session& session, SearchFilter filter) return query; } -Wt::Dbo::Query -Genre::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) +Wt::Dbo::Query +Cluster::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) { SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query( "SELECT g.id, g.name, COUNT(DISTINCT t.id) FROM genre g INNER JOIN track_genre t_g ON t_g.genre_id = g.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_g.track_id " + sqlQuery.where().get()).groupBy("g.name").orderBy("g.name"); + = session.query( "SELECT c.id, c.type, c.name, COUNT(DISTINCT t.id) FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name"); for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -285,26 +276,48 @@ Genre::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) } void -Genre::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) +Cluster::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) { Wt::Dbo::Query query = getUIQuery(session, filter); model.setQuery(query, columnNames.empty() ? true : false); // TODO do something better - if (columnNames.size() == 2) + if (columnNames.size() == 3) { - model.addColumn( "g.name", columnNames[0] ); - model.addColumn( "COUNT(DISTINCT t.id)", columnNames[1] ); + model.addColumn( "c.type", columnNames[0] ); + model.addColumn( "c.name", columnNames[1] ); + model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2] ); } } -std::vector -Genre::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +std::vector +Cluster::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) { Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); return std::vector(res.begin(), res.end()); } +Feature::Feature(Wt::Dbo::ptr track, const std::string& type, const std::string& value) +: _type(type), +_value(value), +_track(track) +{ +} + +Feature::pointer +Feature::create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, const std::string& type, const std::string& value) +{ + return session.add(new Feature(track, type, value)); +} + +std::vector +Feature::getByTrack(Wt::Dbo::Session& session, Track::id_type trackId, const std::string& type) +{ + Wt::Dbo::collection res = session.find().where("track_id = ? AND type = ?").bind( trackId).bind(type); + return std::vector(res.begin(), res.end()); +} + + } // namespace Database diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 8b10223d..9d412f94 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2013-2016 Emeric Poupon * * This file is part of LMS. * @@ -17,8 +17,7 @@ * along with LMS. If not, see . */ -#ifndef _AUDIO_TYPES_HPP_ -#define _AUDIO_TYPES_HPP_ +#pragma once #include #include @@ -39,50 +38,56 @@ class Artist; class Release; class Track; class PlaylistEntry; -class Classification; class Feature; -class Genre +class Cluster { public: - typedef Wt::Dbo::ptr pointer; - typedef Wt::Dbo::dbo_traits::IdType id_type; + typedef Wt::Dbo::ptr pointer; + typedef Wt::Dbo::dbo_traits::IdType id_type; - Genre(); - Genre(const std::string& name); + Cluster(); + Cluster(std::string type, std::string name); // Find utility - static pointer getByName(Wt::Dbo::Session& session, const std::string& name); + static pointer get(Wt::Dbo::Session& session, std::string type, std::string name); static pointer getNone(Wt::Dbo::Session& session); static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static Wt::Dbo::collection getAll(Wt::Dbo::Session& session); + static Wt::Dbo::collection getAll(Wt::Dbo::Session& session); // MVC models for the user interface - // Genre ID, name, track count - typedef boost::tuple UIQueryResult; + // ClusterID, type, name, track count + typedef boost::tuple UIQueryResult; static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); // Create utility - static pointer create(Wt::Dbo::Session& session, const std::string& name); + static pointer create(Wt::Dbo::Session& session, std::string type, std::string name); // Accessors const std::string& getName(void) const { return _name; } + const std::string& getType(void) const { return _type; } bool isNone(void) const; const Wt::Dbo::collection< Wt::Dbo::ptr >& getTracks() const { return _tracks;} + void addTrack(Wt::Dbo::ptr track) { _tracks.insert(track); } + template void persist(Action& a) { Wt::Dbo::field(a, _name, "name"); - Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::field(a, _type, "type"); + Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); } private: static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter); static const std::size_t _maxNameLength = 128; + static const std::size_t _maxTypeLength = 128; + + std::string _type; std::string _name; Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; @@ -160,8 +165,6 @@ class Track void setMBID(const std::string& MBID) { _MBID = MBID; } void setArtist(Wt::Dbo::ptr artist) { _artist = artist; } void setRelease(Wt::Dbo::ptr release) { _release = release; } - void setGenres(std::vector genres); - void addClassification(Wt::Dbo::ptr classification); void addFeature(Wt::Dbo::ptr feature); int getTrackNumber(void) const { return _trackNumber; } @@ -180,9 +183,7 @@ class Track const std::string& getMBID(void) const { return _MBID; } Wt::Dbo::ptr getArtist(void) const { return _artist; } Wt::Dbo::ptr getRelease(void) const { return _release; } - std::vector< Genre::pointer > getGenres(void) const; - bool hasGenre(Genre::pointer genre) const { return _genres.count(genre); } - std::vector< Wt::Dbo::ptr > getClassifications(void) const; + std::vector< Cluster::pointer > getClusters(void) const; std::vector< Wt::Dbo::ptr > getFeatures(void) const; template @@ -205,9 +206,8 @@ class Track Wt::Dbo::field(a, _MBID, "mbid"); Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track"); - Wt::Dbo::hasMany(a, _classifications, Wt::Dbo::ManyToOne, "track"); Wt::Dbo::hasMany(a, _features, Wt::Dbo::ManyToOne, "track"); } @@ -237,17 +237,43 @@ class Track Wt::Dbo::ptr _artist; Wt::Dbo::ptr _release; - Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track - Wt::Dbo::collection< Wt::Dbo::ptr > _playlistEntries; - Wt::Dbo::collection< Wt::Dbo::ptr > _classifications; - Wt::Dbo::collection< Wt::Dbo::ptr > _features; + Wt::Dbo::collection< Wt::Dbo::ptr > _features; + Wt::Dbo::collection< Wt::Dbo::ptr > _clusters; + Wt::Dbo::collection< Wt::Dbo::ptr > _playlistEntries; + }; +/* A track feature */ +class Feature +{ + public: + typedef Wt::Dbo::ptr pointer; + Feature() {} + Feature(Wt::Dbo::ptr track, const std::string& type, const std::string& value); + static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, const std::string& type, const std::string& value); + static std::vector getByTrack(Wt::Dbo::Session& session, Track::id_type trackId, const std::string& type); + + std::string getType(void) const { return _type; } + std::string getValue(void) const { return _value; } + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _type, "type"); + Wt::Dbo::field(a, _value, "value"); + Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); + } + + private: + std::string _type; + std::string _value; + + Wt::Dbo::ptr _track; +}; } // namespace database -#endif diff --git a/src/database/Types.hpp b/src/database/Types.hpp index 0842ad7d..a04b978b 100644 --- a/src/database/Types.hpp +++ b/src/database/Types.hpp @@ -22,7 +22,6 @@ #include "Artist.hpp" #include "Track.hpp" #include "Playlist.hpp" -#include "Classification.hpp" #include "Release.hpp" #include "Video.hpp" #include "MediaDirectory.hpp" diff --git a/src/ui/audio/desktop/DesktopAudio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp index 22c22608..08c857a2 100644 --- a/src/ui/audio/desktop/DesktopAudio.cpp +++ b/src/ui/audio/desktop/DesktopAudio.cpp @@ -71,9 +71,9 @@ _playQueue(nullptr) // Filters Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout(); - TableFilterGenre *filterGenre = new TableFilterGenre(); - filterLayout->addWidget(filterGenre); - _filterChain.addFilter(filterGenre); + TableFilterCluster *filterCluster = new TableFilterCluster(); + filterLayout->addWidget(filterCluster); + _filterChain.addFilter(filterCluster); TableFilterArtist *filterArtist = new TableFilterArtist(); filterLayout->addWidget(filterArtist); @@ -213,7 +213,7 @@ _playQueue(nullptr) // Set the selected tracks to the play queue filterArtist->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks)); filterRelease->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks)); - filterGenre->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks)); + filterCluster->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks)); // Play button // Set the selected tracks to the play queue diff --git a/src/ui/audio/desktop/KeywordSearchFilter.cpp b/src/ui/audio/desktop/KeywordSearchFilter.cpp index 6b4d4355..3749960a 100644 --- a/src/ui/audio/desktop/KeywordSearchFilter.cpp +++ b/src/ui/audio/desktop/KeywordSearchFilter.cpp @@ -52,7 +52,7 @@ KeywordSearchFilter::getConstraint(Database::SearchFilter& filter) filter.nameLikeMatch.push_back({ {Database::SearchFilter::Field::Artist, {value}}, {Database::SearchFilter::Field::Release, {value}}, - {Database::SearchFilter::Field::Genre, {value}}, + {Database::SearchFilter::Field::Cluster, {value}}, {Database::SearchFilter::Field::Track, {value}} }); } diff --git a/src/ui/audio/desktop/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp index 6438f713..9f2677da 100644 --- a/src/ui/audio/desktop/TableFilter.cpp +++ b/src/ui/audio/desktop/TableFilter.cpp @@ -30,23 +30,24 @@ namespace Desktop { using namespace Database; -TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent) +TableFilterCluster::TableFilterCluster(Wt::WContainerWidget* parent) : Wt::WTableView( parent ), Filter() { - const std::vector columnNames = {"Genre", "Tracks"}; + const std::vector columnNames = {"Type", "Name", "Tracks"}; SearchFilter filter; - Genre::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); + Cluster::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); this->setSelectionMode(Wt::ExtendedSelection); this->setSortingEnabled(true); this->setAlternatingRowColors(true); this->setModel(&_queryModel); - this->setColumnWidth(1, 80); + this->setColumnWidth(1, 120); + this->setColumnWidth(2, 80); - this->selectionChanged().connect(this, &TableFilterGenre::emitUpdate); + this->selectionChanged().connect(this, &TableFilterCluster::emitUpdate); #if WT_VERSION >= 0X03030400 this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); @@ -74,25 +75,25 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent) } void -TableFilterGenre::layoutSizeChanged (int width, int height) +TableFilterCluster::layoutSizeChanged (int width, int height) { - std::size_t trackColumnSize = this->columnWidth(1).toPixels(); + std::size_t otherColumnSizes = this->columnWidth(1).toPixels() + this->columnWidth(2).toPixels(); // Set the remaining size for the name column - this->setColumnWidth(0, width - trackColumnSize - (7 * 2) - 2); + this->setColumnWidth(0, width - otherColumnSizes - (7 * 3) - 2); } // Set constraints on this filter void -TableFilterGenre::refresh(SearchFilter& filter) +TableFilterCluster::refresh(SearchFilter& filter) { this->clearSelection(); - Genre::updateUIQueryModel(DboSession(), _queryModel, filter); + Cluster::updateUIQueryModel(DboSession(), _queryModel, filter); } // Get constraint created by this filter void -TableFilterGenre::getConstraint(SearchFilter& filter) +TableFilterCluster::getConstraint(SearchFilter& filter) { Wt::WModelIndexSet indexSet = this->selectedIndexes(); @@ -102,9 +103,9 @@ TableFilterGenre::getConstraint(SearchFilter& filter) continue; // TODO set invisible track id - Database::Genre::id_type id = _queryModel.resultRow( index.row() ).get<0>(); + Cluster::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - filter.idMatch[Database::SearchFilter::Field::Genre].push_back(id); + filter.idMatch[Database::SearchFilter::Field::Cluster].push_back(id); } } diff --git a/src/ui/audio/desktop/TableFilter.hpp b/src/ui/audio/desktop/TableFilter.hpp index 991414a9..243b17f2 100644 --- a/src/ui/audio/desktop/TableFilter.hpp +++ b/src/ui/audio/desktop/TableFilter.hpp @@ -30,10 +30,10 @@ namespace UserInterface { namespace Desktop { -class TableFilterGenre : public Wt::WTableView, public Filter +class TableFilterCluster : public Wt::WTableView, public Filter { public: - TableFilterGenre(Wt::WContainerWidget* parent = 0); + TableFilterCluster(Wt::WContainerWidget* parent = 0); // Set constraints on this filter void refresh(Database::SearchFilter& filter); @@ -51,8 +51,7 @@ class TableFilterGenre : public Wt::WTableView, public Filter SigDoubleClicked _sigDoubleClicked; - // Name, track count - Wt::Dbo::QueryModel _queryModel; + Wt::Dbo::QueryModel _queryModel; }; class TableFilterArtist : public Wt::WTableView, public Filter