diff --git a/src/Makefile.am b/src/Makefile.am index 478d69e7..ca1124bc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,11 +11,9 @@ lms_SOURCES = \ $(srcdir)/config/ConfigReader.cpp \ $(srcdir)/cover/CoverArt.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ - $(srcdir)/database/Artist.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/Genre.cpp \ $(srcdir)/database/MediaDirectory.cpp \ - $(srcdir)/database/Release.cpp \ $(srcdir)/database/SqlQuery.cpp \ $(srcdir)/database/Track.cpp \ $(srcdir)/database/User.cpp \ @@ -48,6 +46,7 @@ lms_SOURCES = \ $(srcdir)/ui/audio/KeywordSearchFilter.cpp \ $(srcdir)/ui/audio/PlayQueue.cpp \ $(srcdir)/ui/audio/TableFilter.cpp \ + $(srcdir)/ui/audio/TableFilterGenre.cpp \ $(srcdir)/ui/audio/TrackView.cpp \ $(srcdir)/ui/common/DirectoryValidator.cpp \ $(srcdir)/ui/common/SessionData.cpp \ diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index e08d086d..7dabf4c5 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -77,30 +77,5 @@ Grabber::getFromTrack(Database::Track::pointer track) return res; } -std::vector -Grabber::getFromRelease(Database::Release::pointer release) -{ - if (!release) - return std::vector(); - - // TODO - // Check if there is an image file in the directory of the release - // For now, just get the cover art from the first track of the release - - Wt::Dbo::collection tracks (release->getTracks()); - - Database::Track::pointer firstTrack; - if (tracks.begin() != tracks.end()) - firstTrack = *tracks.begin(); - - if (firstTrack) - { - return Grabber::getFromTrack(firstTrack); - } - else - return std::vector(); - -} - } // namespace CoverArt diff --git a/src/cover/CoverArtGrabber.hpp b/src/cover/CoverArtGrabber.hpp index 3d3c0959..d9ce3a82 100644 --- a/src/cover/CoverArtGrabber.hpp +++ b/src/cover/CoverArtGrabber.hpp @@ -36,7 +36,6 @@ class Grabber static std::vector getFromInputFormatContext(const Av::InputFormatContext& input); static std::vector getFromTrack(Database::Track::pointer track); - static std::vector getFromRelease(Database::Release::pointer release); }; } // namespace CoverArt diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database-updater/DatabaseUpdater.cpp index 97c1a9be..22b7e530 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database-updater/DatabaseUpdater.cpp @@ -320,35 +320,6 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) title = file.filename().string(); } - // ***** Artist - Wt::Dbo::ptr artist; - if (items.find(MetaData::Type::Artist) != items.end()) - { - const std::string artistName (boost::any_cast(items[MetaData::Type::Artist])); - artist = Artist::getByName(_db.getSession(), artistName ); - if (!artist) - artist = Artist::create( _db.getSession(), artistName ); - } - else - artist = Artist::getNone(_db.getSession()); - - assert(artist); - - - // ***** Release - Wt::Dbo::ptr release; - if (items.find(MetaData::Type::Album) != items.end()) - { - const std::string albumName (boost::any_cast(items[MetaData::Type::Album])); - release = Release::getByName(_db.getSession(), albumName); - if (!release) - release = Release::create( _db.getSession(), albumName ); - } - else - release = Release::getNone( _db.getSession() ); - - assert(release); - // ***** Genres typedef std::list GenreList; @@ -376,7 +347,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) if (!track) { // Create a new song - track = Track::create(_db.getSession(), file, artist, release); + track = Track::create(_db.getSession(), file); LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Adding '" << file << "'"; stats.nbAdded++; } @@ -403,8 +374,12 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) track.modify()->setGenres( trackGenreList ); } track.modify()->setGenres( genres ); - track.modify()->setArtist( artist ); - track.modify()->setRelease( release ); + + if (items.find(MetaData::Type::Artist) != items.end()) + track.modify()->setArtistName( boost::any_cast(items[MetaData::Type::Artist]) ); + + if (items.find(MetaData::Type::Album) != items.end()) + track.modify()->setReleaseName( boost::any_cast(items[MetaData::Type::Album]) ); if (items.find(MetaData::Type::TrackNumber) != items.end()) track.modify()->setTrackNumber( boost::any_cast(items[MetaData::Type::TrackNumber]) ); @@ -540,28 +515,6 @@ Updater::checkAudioFiles( Stats& stats ) } } - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Artists..."; - // Now process orphan Artists (no track) - typedef Wt::Dbo::collection< Wt::Dbo::ptr > Artists; - Artists artists = Artist::getAllOrphans(_db.getSession()); - - for (Artists::iterator it = artists.begin(); it != artists.end(); ++it) - { - LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Removing orphan artist " << (*it)->getName(); - (*it).remove(); - } - - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Releases..."; - // Now process orphan Release (no track) - typedef Wt::Dbo::collection< Wt::Dbo::ptr > Releases; - Releases releases = Release::getAllOrphans(_db.getSession()); - - for (Releases::iterator it = releases.begin(); it != releases.end(); ++it) - { - LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Removing orphan release " << (*it)->getName(); - (*it).remove(); - } - // Now process orphan Genre (no track) LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres..."; typedef Wt::Dbo::collection< Wt::Dbo::ptr > Genres; diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp deleted file mode 100644 index c171ae0a..00000000 --- a/src/database/Artist.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2013 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 - -#include "AudioTypes.hpp" - -#include "SqlQuery.hpp" - -namespace Database -{ - -Artist::Artist(const std::string& name) -: _name(std::string(name, 0 , _maxNameLength)) -{ -} - -// Accesoors -Artist::pointer -Artist::getByName(Wt::Dbo::Session& session, const std::string& name) -{ - return session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); -} - -// Create -Artist::pointer -Artist::create(Wt::Dbo::Session& session, const std::string& name) -{ - return session.add( new Artist( name ) ); -} - -Artist::pointer -Artist::getNone(Wt::Dbo::Session& session) -{ - pointer res = getByName(session, ""); - if (!res) - res = create(session, ""); - - return res; -} - -Wt::Dbo::collection -Artist::getAll(Wt::Dbo::Session& session, int offset, int size) -{ - return session.find().offset(offset).limit(size); -} - -Wt::Dbo::collection -Artist::getAllOrphans(Wt::Dbo::Session& session) -{ - return session.query< Wt::Dbo::ptr >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL"); -} - -Wt::Dbo::collection -Artist::getAll(Wt::Dbo::Session& session, - const std::vector& genreIds, - int offset, int size) -{ - std::string sqlQuery = "SELECT a FROM artist a"; - - if (!genreIds.empty()) - { - sqlQuery += " INNER JOIN genre g ON g.id = t_g.genre_id"; - sqlQuery += " INNER JOIN track t ON t.id = t_g.track_id AND t.artist_id = a.id"; - sqlQuery += " INNER JOIN track_genre t_g ON t_g.track_id = t.id AND t_g.genre_id = g.id"; - } - - WhereClause where; - { - WhereClause genreWhere; - - for (std::size_t i = 0; i < genreIds.size(); ++i) - genreWhere.Or( WhereClause("g.id = ?") ); - - where.And(genreWhere); - } - - Wt::Dbo::Query query = session.query( sqlQuery + " " + where.get() ).offset(offset).limit(size); - - BOOST_FOREACH(const Genre::id_type genreId, genreIds) - query.bind(genreId); - - query.groupBy("a"); - - return query; -} - - -} // namespace Database - diff --git a/src/database/AudioTypes.hpp b/src/database/AudioTypes.hpp index dc912d60..ddc21bf6 100644 --- a/src/database/AudioTypes.hpp +++ b/src/database/AudioTypes.hpp @@ -33,8 +33,6 @@ namespace Database { class Track; -class Release; -class Artist; class Genre { @@ -73,99 +71,6 @@ class Genre Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; }; - -class Artist -{ - public: - - typedef Wt::Dbo::ptr pointer; - typedef Wt::Dbo::dbo_traits::IdType id_type; - - Artist() {} - Artist(const std::string& p_name); - - // Accessors - static pointer getByName(Wt::Dbo::Session& session, const std::string& name); - static pointer getNone(Wt::Dbo::Session& session); - static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); - static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, - const std::vector& genreIds, - int offset = -1, int size = -1); - static Wt::Dbo::collection getAllOrphans(Wt::Dbo::Session& session); - - const std::string& getName(void) const { return _name; } - const Wt::Dbo::collection< Wt::Dbo::ptr >& getTracks(void) const { return _tracks;} - - // Create - static pointer create(Wt::Dbo::Session& session, const std::string& name); - - bool isNone(void) const; - - template - void persist(Action& a) - { - Wt::Dbo::field(a, _name, "name"); - - Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist"); - } - - - private: - - static const std::size_t _maxNameLength = 128; - - std::string _name; - - Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; // Tracks of this artist -}; - - -// Album release -class Release -{ - public: - - typedef Wt::Dbo::ptr pointer; - typedef Wt::Dbo::dbo_traits::IdType id_type; - - Release() {} - Release(const std::string& name); - - // Accessors - static pointer getByName(Wt::Dbo::Session& session, const std::string& name); - static pointer getById(Wt::Dbo::Session& session, id_type id); - static pointer getNone(Wt::Dbo::Session& session); - static Wt::Dbo::collection getAllOrphans(Wt::Dbo::Session& session); - static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, - const std::vector& artistIds, - const std::vector& genreIds, - int offset = -1, int size = -1); - - // Create - static pointer create(Wt::Dbo::Session& session, const std::string& name); - - std::string getName() const { return _name; } - bool isNone(void) const; - const Wt::Dbo::collection >& getTracks(void) const { return _tracks;} - boost::posix_time::time_duration getDuration(void) const; - - - template - void persist(Action& a) - { - Wt::Dbo::field(a, _name, "name"); - - Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release"); - } - - private: - static const std::size_t _maxNameLength = 128; - std::string _name; - - Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; // Tracks in the release -}; - - class Track { public: @@ -174,25 +79,37 @@ class Track typedef Wt::Dbo::dbo_traits::IdType id_type; Track() {} - Track(const boost::filesystem::path& p, Artist::pointer artist, Release::pointer release); + Track(const boost::filesystem::path& p); // Find utilities static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p); static pointer getById(Wt::Dbo::Session& session, id_type id); static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session); static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session, - const std::vector& artistIds, - const std::vector& releaseIds, - const std::vector& genreIds, + const std::vector& artists, // OR filter if many + const std::vector& releases, // OR filter if many + const std::vector& genres, // OR filter if many int offset = -1, int size = -1); + static std::vector getReleases(Wt::Dbo::Session& session, + const std::vector& artists, // OR filter if many + const std::vector& genres, // OR filter if many + int offset = -1, int size = -1); + + static std::vector getArtists(Wt::Dbo::Session& session, + const std::vector& genres, // OR filter + int offset = -1, int size = -1); + + // Create utility - static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p, Artist::pointer artist, Release::pointer release); + static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p); // Accessors void setTrackNumber(int num) { _trackNumber = num; } void setDiscNumber(int num) { _discNumber = num; } void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); } + void setArtistName(const std::string& name) { _artistName = std::string(name, 0, _maxNameLength); } + void setReleaseName(const std::string& name) { _releaseName = std::string(name, 0, _maxNameLength); } void setDuration(boost::posix_time::time_duration duration) { _duration = duration; } void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; } void setChecksum(const std::vector& checksum) { _fileChecksum = checksum; } @@ -200,18 +117,16 @@ class Track void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; } void setGenres(const std::string& genreList) { _genreList = genreList; } void setGenres(std::vector genres); - void setArtist(Artist::pointer artist) { _artist = artist; } - void setRelease(Release::pointer release) { _release = release; } int getTrackNumber(void) const { return _trackNumber; } int getDiscNumber(void) const { return _discNumber; } std::string getName(void) const { return _name; } + std::string getArtistName(void) const { return _artistName; } + std::string getReleaseName(void) const { return _releaseName; } const std::string& getPath(void) const { return _filePath; } boost::posix_time::time_duration getDuration(void) const { return _duration; } boost::posix_time::ptime getDate(void) const { return _date; } boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; } - Artist::pointer getArtist(void) const { return _artist; } - Release::pointer getRelease(void) const { return _release; } bool hasGenre(Genre::pointer genre) const { return _genres.count(genre); } std::vector< Genre::pointer > getGenres(void) const; @@ -224,6 +139,8 @@ class Track Wt::Dbo::field(a, _trackNumber, "track_number"); Wt::Dbo::field(a, _discNumber, "disc_number"); Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _artistName, "artist_name"); + Wt::Dbo::field(a, _releaseName, "release_name"); Wt::Dbo::field(a, _duration, "duration"); Wt::Dbo::field(a, _date, "date"); Wt::Dbo::field(a, _originalDate, "original_date"); @@ -232,8 +149,6 @@ class Track Wt::Dbo::field(a, _fileLastWrite, "last_write"); Wt::Dbo::field(a, _fileChecksum, "checksum"); Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade); } private: @@ -243,6 +158,8 @@ class Track int _trackNumber; int _discNumber; std::string _name; + std::string _artistName; + std::string _releaseName; boost::posix_time::time_duration _duration; boost::posix_time::ptime _date; boost::posix_time::ptime _originalDate; @@ -251,9 +168,7 @@ class Track std::vector _fileChecksum; boost::posix_time::ptime _fileLastWrite; - Artist::pointer _artist; // Associated Artist - Release::pointer _release; // Associated Release - Wt::Dbo::collection< Genre::pointer > _genres; // Tracks in the release + Wt::Dbo::collection< Genre::pointer > _genres; // Tracks that belong to this genre }; } // namespace database diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index 1ea5c73e..c0f7ddeb 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -85,9 +85,6 @@ _dbBackend( db.string() ) _session.setConnection(_dbBackend); _session.mapClass("genre"); _session.mapClass("track"); - _session.mapClass("artist"); - _session.mapClass("release"); - _session.mapClass("release"); _session.mapClass("video"); _session.mapClass("media_directory"); _session.mapClass("media_directory_settings"); @@ -99,6 +96,8 @@ _dbBackend( db.string() ) try { _session.createTables(); + _dbBackend.executeSql("CREATE INDEX artist_name_idx ON track(artist_name)"); + _dbBackend.executeSql("CREATE INDEX release_name_idx ON track(release_name)"); } catch(std::exception& e) { LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what(); diff --git a/src/database/Release.cpp b/src/database/Release.cpp deleted file mode 100644 index 138c37b4..00000000 --- a/src/database/Release.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2013 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 - -#include "SqlQuery.hpp" - -#include "AudioTypes.hpp" - -namespace Database { - -Release::Release(const std::string& name) -: _name(std::string(name, 0, _maxNameLength)) -{ -} - -Release::pointer -Release::getByName(Wt::Dbo::Session& session, const std::string& name) -{ - return session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); -} - -Release::pointer -Release::getById(Wt::Dbo::Session& session, id_type id) -{ - return session.find().where("id = ?").bind(id); -} - -Release::pointer -Release::getNone(Wt::Dbo::Session& session) -{ - pointer res = getByName(session, ""); - if (!res) - res = create(session, ""); - - return res; -} - -Release::pointer -Release::create(Wt::Dbo::Session& session, const std::string& name) -{ - return session.add(new Release(name)); -} - -Wt::Dbo::collection -Release::getAll(Wt::Dbo::Session& session, - const std::vector& artistIds, - const std::vector& genreIds, - int offset, int size) -{ - std::string sqlQuery = "SELECT r FROM release r"; - - if (!artistIds.empty() || !genreIds.empty()) - sqlQuery += " INNER JOIN track t ON t.release_id = r.id"; - - if (!artistIds.empty()) - { - sqlQuery += " INNER JOIN artist a ON a.id = t.artist_id"; - } - - if (!genreIds.empty()) - { - sqlQuery += " INNER JOIN genre g ON g.id = t_g.genre_id"; - sqlQuery += " INNER JOIN track_genre t_g ON t_g.track_id = t.id AND t_g.genre_id = g.id"; - } - - WhereClause where; - { - WhereClause artistWhere; - - for (std::size_t i = 0; i < artistIds.size(); ++i) - artistWhere.Or( WhereClause("a.id = ?") ); - - where.And(artistWhere); - } - - { - WhereClause genreWhere; - - for (std::size_t i = 0; i < genreIds.size(); ++i) - genreWhere.Or( WhereClause("g.id = ?") ); - - where.And(genreWhere); - } - - Wt::Dbo::Query query = session.query( sqlQuery + " " + where.get() ).offset(offset).limit(size); - - BOOST_FOREACH(const Artist::id_type artistId, artistIds) - query.bind(artistId); - - BOOST_FOREACH(const Genre::id_type genreId, genreIds) - query.bind(genreId); - - query.groupBy("r"); - - return query; -} - -boost::posix_time::time_duration -Release::getDuration(void) const -{ - typedef Wt::Dbo::collection< Wt::Dbo::ptr > Tracks; - - boost::posix_time::time_duration res; - - for (Tracks::const_iterator it = _tracks.begin(); it != _tracks.end(); ++it) - res += (*it)->getDuration(); - - return res; -} - -Wt::Dbo::collection -Release::getAllOrphans(Wt::Dbo::Session& session) -{ - return session.query< Wt::Dbo::ptr >("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL"); -} - - - -} // namespace Database diff --git a/src/database/Track.cpp b/src/database/Track.cpp index b61a0e20..d0e966cb 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -25,13 +25,11 @@ namespace Database { -Track::Track(const boost::filesystem::path& p, Artist::pointer artist, Release::pointer release) +Track::Track(const boost::filesystem::path& p) : _trackNumber(0), _discNumber(0), -_filePath( p.string() ), -_artist(artist), -_release(release) +_filePath( p.string() ) { } @@ -61,9 +59,9 @@ Track::getById(Wt::Dbo::Session& session, id_type id) Track::pointer -Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p, Artist::pointer artist, Release::pointer release) +Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p) { - return session.add(new Track(p, artist, release) ); + return session.add(new Track(p) ); } Wt::Dbo::collection< Track::pointer > @@ -82,20 +80,14 @@ Track::getGenres(void) const Wt::Dbo::collection< Track::pointer > Track::getAll(Wt::Dbo::Session& session, - const std::vector& artistIds, - const std::vector& releaseIds, - const std::vector& genreIds, + const std::vector& artists, + const std::vector& releases, + const std::vector& genres, int offset, int size) { std::string sqlQuery = "SELECT t FROM track t"; - if (!artistIds.empty()) - sqlQuery += " INNER JOIN artist a ON a.id = t.artist_id"; - - if (!releaseIds.empty()) - sqlQuery += " INNER JOIN release r ON r.id = t.release_id"; - - if (!genreIds.empty()) + if (!genres.empty()) { sqlQuery += " INNER JOIN genre g ON g.id = t_g.genre_id"; sqlQuery += " INNER JOIN track_genre t_g ON t_g.track_id = t.id AND t_g.genre_id = g.id"; @@ -106,8 +98,8 @@ Track::getAll(Wt::Dbo::Session& session, { WhereClause artistWhere; - for (std::size_t i = 0; i < artistIds.size(); ++i) - artistWhere.Or( WhereClause("a.id = ?") ); + for (std::size_t i = 0; i < artists.size(); ++i) + artistWhere.Or( WhereClause("t.artist_name = ?") ); where.And(artistWhere); } @@ -115,8 +107,8 @@ Track::getAll(Wt::Dbo::Session& session, { WhereClause releaseWhere; - for (std::size_t i = 0; i < releaseIds.size(); ++i) - releaseWhere.Or( WhereClause("r.id = ?") ); + for (std::size_t i = 0; i < releases.size(); ++i) + releaseWhere.Or( WhereClause("t.release_name = ?") ); where.And(releaseWhere); } @@ -124,8 +116,8 @@ Track::getAll(Wt::Dbo::Session& session, { WhereClause genreWhere; - for (std::size_t i = 0; i < genreIds.size(); ++i) - genreWhere.Or( WhereClause("g.id = ?") ); + for (std::size_t i = 0; i < genres.size(); ++i) + genreWhere.Or( WhereClause("g.name = ?") ); where.And(genreWhere); } @@ -133,20 +125,104 @@ Track::getAll(Wt::Dbo::Session& session, Wt::Dbo::Query query = session.query( sqlQuery + " " + where.get() ).offset(offset).limit(size); - BOOST_FOREACH(const Artist::id_type artistId, artistIds) - query.bind(artistId); + BOOST_FOREACH(const std::string& artist, artists) + query.bind(artist); - BOOST_FOREACH(const Release::id_type releaseId, releaseIds) - query.bind(releaseId); + BOOST_FOREACH(const std::string& release, releases) + query.bind(release); - BOOST_FOREACH(const Genre::id_type genreId, genreIds) - query.bind(genreId); + BOOST_FOREACH(const std::string& genre, genres) + query.bind(genre); query.groupBy("t"); return query; } +std::vector +Track::getArtists(Wt::Dbo::Session& session, + const std::vector& genres, + int offset, int size) +{ + std::string sqlQuery = "SELECT t.artist_name FROM track t"; + + if (!genres.empty()) + { + sqlQuery += " INNER JOIN genre g ON g.id = t_g.genre_id"; + sqlQuery += " INNER JOIN track_genre t_g ON t_g.track_id = t.id AND t_g.genre_id = g.id"; + } + + WhereClause whereClause; + + for (std::size_t i = 0; i < genres.size(); ++i) + whereClause.Or( WhereClause("g.name = ?") ); + + Wt::Dbo::Query query = session.query( sqlQuery + " " + whereClause.get() ).offset(offset).limit(size).groupBy("t.artist_name"); + + BOOST_FOREACH(const std::string& genre, genres) + query.bind(genre); + + typedef Wt::Dbo::collection< std::string > ArtistNames; + ArtistNames artistNames(query); + + std::vector res; + for (ArtistNames::const_iterator it = artistNames.begin(); it != artistNames.end(); ++it) + res.push_back((*it)); + + return res; + +} + +std::vector +Track::getReleases(Wt::Dbo::Session& session, + const std::vector& artists, + const std::vector& genres, + int offset, int size) +{ + + std::string sqlQuery = "SELECT t.release_name FROM track t"; + + if (!genres.empty()) + { + sqlQuery += " INNER JOIN genre g ON g.id = t_g.genre_id"; + sqlQuery += " INNER JOIN track_genre t_g ON t_g.track_id = t.id AND t_g.genre_id = g.id"; + } + + WhereClause whereClause; + + { + WhereClause clause; + + BOOST_FOREACH(const std::string& artist, artists) + clause.Or( WhereClause("t.artist_name = ?") ).bind(artist); + + whereClause.And(clause); + } + { + WhereClause clause; + + BOOST_FOREACH(const std::string& genre, genres) + clause.Or( WhereClause("g.name = ?") ).bind(genre); + + whereClause.And(clause); + } + + Wt::Dbo::Query query = session.query( sqlQuery + " " + whereClause.get() ).offset(offset).limit(size).groupBy("t.release_name"); + + BOOST_FOREACH(const std::string& bindArg, whereClause.getBindArgs()) + query.bind(bindArg); + + typedef Wt::Dbo::collection< std::string > ReleaseNames; + ReleaseNames releaseNames(query); + + std::vector res; + for (ReleaseNames::const_iterator it = releaseNames.begin(); it != releaseNames.end(); ++it) + res.push_back((*it)); + + return res; +} + + } // namespace Database diff --git a/src/remote/proto/collection.proto b/src/remote/proto/collection.proto index 4c8737ec..0af2ea6d 100644 --- a/src/remote/proto/collection.proto +++ b/src/remote/proto/collection.proto @@ -20,24 +20,24 @@ message AudioCollectionRequest { required BatchParameter batch_parameter = 1; // Search filters - repeated uint64 genre_id = 2; // Artist that has at least a track of the genre + repeated string genre = 2; // Artist that has at least a track of the genre } message GetReleaseList { required BatchParameter batch_parameter = 1; // Search filters - repeated uint64 artist_id = 2; // Release that contains at least a track of one of these artists - repeated uint64 genre_id = 3; // Release that has at least a track of the genre + repeated string artist = 2; // Release that contains at least a track of one of these artists + repeated string genre = 3; // Release that has at least a track of the genre } message GetTrackList { required BatchParameter batch_parameter = 1; // Search filters - repeated uint64 artist_id = 2; // Track that belongs to these artists - repeated uint64 release_id = 3; // Track that is part of these releases - repeated uint64 genre_id = 4; // Track that has at least a track of the genre + repeated string artist = 2; // Track that belongs to these artists + repeated string release = 3; // Track that is part of these releases + repeated string genre = 4; // Track that has at least a track of the genre } message GetCoverArt @@ -50,7 +50,7 @@ message AudioCollectionRequest required Type type = 1; - optional uint64 release_id = 2; // Release that owns the cover art + optional string release = 2; // Release that owns the cover art optional uint64 track_id = 3; // Track that owns the cover art required uint32 size = 4; // Scale image to size*size pixels. Set 0 to get the biggest image @@ -86,22 +86,22 @@ message AudioCollectionResponse message GenreList { - repeated Genre genres = 2; + repeated Genre genres = 1; } message ArtistList { - repeated Artist artists = 2; + repeated Artist artists = 1; } message ReleaseList { - repeated Release releases = 2; + repeated Release releases = 1; } message TrackList { - repeated Track tracks = 2; + repeated Track tracks = 1; } message CoverArt @@ -112,32 +112,26 @@ message AudioCollectionResponse message Genre { - required uint64 id = 1; // Genre Id - - required string name = 2; + required string name = 1; } message Artist { - required uint64 id = 1; // Artist Id - - required string name = 2; + required string name = 1; } message Release { - required uint64 id = 1; // Release id - - required string name = 2; + required string name = 1; } message Track { required uint64 id = 1; // Track id - required uint64 artist_id = 2; - required uint64 release_id = 3; - repeated uint64 genre_id = 4; + required string artist = 2; + required string release = 3; + repeated string genre = 4; optional uint32 disc_number = 5; optional uint32 track_number = 6; diff --git a/src/remote/server/AudioCollectionRequestHandler.cpp b/src/remote/server/AudioCollectionRequestHandler.cpp index 5b9fc383..17719257 100644 --- a/src/remote/server/AudioCollectionRequestHandler.cpp +++ b/src/remote/server/AudioCollectionRequestHandler.cpp @@ -143,7 +143,6 @@ AudioCollectionRequestHandler::processGetGenres(const AudioCollectionRequest::Ge AudioCollectionResponse_Genre* genre = response.add_genres(); genre->set_name( std::string( boost::locale::conv::to_utf((*it)->getName(), "UTF-8") ) ); - genre->set_id(it->id()); } return true; @@ -165,24 +164,22 @@ AudioCollectionRequestHandler::processGetArtists(const AudioCollectionRequest::G size = std::min(size, _maxListArtists); // Get filters - std::vector genreIds; - for (int id = 0; id < request.genre_id_size(); ++id) - genreIds.push_back( request.genre_id(id) ); + std::vector genres; + for (int id = 0; id < request.genre_size(); ++id) + genres.push_back( request.genre(id) ); // Now fetch requested data... Wt::Dbo::Transaction transaction( _db.getSession() ); - Wt::Dbo::collection artists = Database::Artist::getAll( _db.getSession(), genreIds, request.batch_parameter().offset(), static_cast(size) ); + std::vector artists + = Database::Track::getArtists(_db.getSession(), + genres, + request.batch_parameter().offset(), static_cast(size) ); - typedef Wt::Dbo::collection< Database::Artist::pointer > Artists; - - for (Artists::const_iterator it = artists.begin(); it != artists.end(); ++it) + BOOST_FOREACH(const std::string& artist, artists) { - AudioCollectionResponse_Artist* artist = response.add_artists(); - - artist->set_name( std::string( boost::locale::conv::to_utf((*it)->getName(), "UTF-8") ) ); - artist->set_id(it->id()); + response.add_artists()->set_name( std::string( boost::locale::conv::to_utf(artist, "UTF-8") ) ); } return true; @@ -203,30 +200,25 @@ AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest:: size = _maxListReleases; size = std::min(size, _maxListReleases); - std::vector artistIds; - for (int id = 0; id < request.artist_id_size(); ++id) - artistIds.push_back( request.artist_id(id) ); + std::vector artists; + for (int id = 0; id < request.artist_size(); ++id) + artists.push_back( request.artist(id) ); - std::vector genreIds; - for (int id = 0; id < request.genre_id_size(); ++id) - genreIds.push_back( request.genre_id(id) ); + std::vector genres; + for (int id = 0; id < request.genre_size(); ++id) + genres.push_back( request.genre(id) ); Wt::Dbo::Transaction transaction( _db.getSession() ); - Wt::Dbo::collection releases - = Database::Release::getAll( _db.getSession(), - artistIds, - genreIds, + std::vector releases + = Database::Track::getReleases( _db.getSession(), + artists, + genres, request.batch_parameter().offset(), static_cast(size)); - typedef Wt::Dbo::collection< Database::Release::pointer > Releases; - - for (Releases::const_iterator it = releases.begin(); it != releases.end(); ++it) + BOOST_FOREACH(const std::string& release, releases) { - AudioCollectionResponse_Release* release = response.add_releases(); - - release->set_name( std::string( boost::locale::conv::to_utf((*it)->getName(), "UTF-8") ) ); - release->set_id(it->id()); + response.add_releases()->set_name( std::string( boost::locale::conv::to_utf(release, "UTF-8"))); } return true; @@ -248,27 +240,26 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge size = std::min(size, _maxListTracks); // Get filters - std::vector artistIds; - for (int id = 0; id < request.artist_id_size(); ++id) - artistIds.push_back( request.artist_id(id) ); + std::vector artists; + for (int id = 0; id < request.artist_size(); ++id) + artists.push_back( request.artist(id) ); - std::vector releaseIds; - for (int id = 0; id < request.release_id_size(); ++id) - releaseIds.push_back( request.release_id(id) ); + std::vector releases; + for (int id = 0; id < request.release_size(); ++id) + releases.push_back( request.release(id) ); - std::vector genreIds; - for (int id = 0; id < request.genre_id_size(); ++id) - genreIds.push_back( request.genre_id(id) ); + std::vector genres; + for (int id = 0; id < request.genre_size(); ++id) + genres.push_back( request.genre(id) ); Wt::Dbo::Transaction transaction( _db.getSession() ); Wt::Dbo::collection tracks = Database::Track::getAll( _db.getSession(), - artistIds, - releaseIds, - genreIds, - request.batch_parameter().offset(), - static_cast(size)); + artists, + releases, + genres, + request.batch_parameter().offset(), static_cast(size)); typedef Wt::Dbo::collection< Database::Track::pointer > Tracks; @@ -279,8 +270,8 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge track->set_id(it->id()); track->set_disc_number( (*it)->getDiscNumber() ); track->set_track_number( (*it)->getTrackNumber() ); - track->set_artist_id( (*it)->getArtist().id() ); - track->set_release_id( (*it)->getRelease().id() ); + track->set_artist( (*it)->getArtistName() ); + track->set_release( (*it)->getReleaseName() ); track->set_name( std::string( boost::locale::conv::to_utf((*it)->getName(), "UTF-8") ) ); track->set_duration_secs( (*it)->getDuration().total_seconds() ); @@ -292,7 +283,7 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge track->set_original_release_date( std::to_string((*it)->getOriginalDate().date().year()) ); BOOST_FOREACH(Database::Genre::pointer genre, (*it)->getGenres()) - track->add_genre_id( genre.id() ); + track->add_genre( genre->getName() ); } @@ -306,74 +297,64 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest:: response.set_type(AudioCollectionResponse::TypeCoverArt); + Wt::Dbo::Transaction transaction( _db.getSession() ); + Database::Track::pointer track; + switch(request.type()) { case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease: - - if (request.has_release_id()) + if (request.has_release()) { - Wt::Dbo::Transaction transaction( _db.getSession() ); + Wt::Dbo::collection tracks = Database::Track::getAll(_db.getSession(), + std::vector(), // artist + std::vector(1, request.release()), // release + std::vector(), // genre + -1, 1); - // Get the request release - Database::Release::pointer release = Database::Release::getById( _db.getSession(), request.release_id()); + Wt::Dbo::collection::iterator it = tracks.begin(); - std::vector coverArts = CoverArt::Grabber::getFromRelease(release); + if (it != tracks.end()) + track = *it; - BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts) - { - AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art(); - - if (request.has_size()) - { - std::size_t size = request.size(); - if (size > _maxCoverArtSize || size == 0) - size = _maxCoverArtSize; - if (size < _minCoverArtSize) - size = _minCoverArtSize; - - coverArt.scale(size); - } - - cover_art->set_mime_type(coverArt.getMimeType()); - cover_art->set_data( std::string( coverArt.getData().begin(), coverArt.getData().end()) ); - } + res = true; } - res = true; break; case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack: if (request.has_track_id()) { - Wt::Dbo::Transaction transaction( _db.getSession() ); - // Get the request release - Database::Track::pointer track = Database::Track::getById( _db.getSession(), request.track_id()); + track = Database::Track::getById( _db.getSession(), request.track_id()); - std::vector coverArts = CoverArt::Grabber::getFromTrack(track); - - BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts) - { - AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art(); - - if (request.has_size()) - { - std::size_t size = request.size(); - if (size > _maxCoverArtSize || size == 0) - size = _maxCoverArtSize; - if (size < _minCoverArtSize) - size = _minCoverArtSize; - - coverArt.scale(size); - } - - cover_art->set_mime_type(coverArt.getMimeType()); - cover_art->set_data( std::string( coverArt.getData().begin(), coverArt.getData().end()) ); - } + res = true; } - res = true; break; } + if (!res) + return false; + + std::vector coverArts = CoverArt::Grabber::getFromTrack(track); + + BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts) + { + AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art(); + + if (request.has_size()) + { + std::size_t size = request.size(); + if (size > _maxCoverArtSize || size == 0) + size = _maxCoverArtSize; + if (size < _minCoverArtSize) + size = _minCoverArtSize; + + coverArt.scale(size); + } + + cover_art->set_mime_type(coverArt.getMimeType()); + cover_art->set_data( std::string( coverArt.getData().begin(), coverArt.getData().end()) ); + } + return res; } diff --git a/src/ui/audio/Audio.cpp b/src/ui/audio/Audio.cpp index 779fde0f..89331c57 100644 --- a/src/ui/audio/Audio.cpp +++ b/src/ui/audio/Audio.cpp @@ -28,6 +28,7 @@ #include "logger/Logger.hpp" #include "TableFilter.hpp" +#include "TableFilterGenre.hpp" #include "KeywordSearchFilter.hpp" #include "TrackView.hpp" #include "PlayQueue.hpp" @@ -49,17 +50,17 @@ _mediaPlayer(nullptr) Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout(); { - TableFilter *filter = new TableFilter(_db, "genre", "name"); + TableFilterGenre *filter = new TableFilterGenre(_db); filterLayout->addWidget(filter); _filterChain.addFilter(filter); } { - TableFilter *filter = new TableFilter(_db, "artist", "name"); + TableFilter *filter = new TableFilter(_db, "track", "artist_name"); filterLayout->addWidget(filter); _filterChain.addFilter(filter); } { - TableFilter *filter = new TableFilter(_db, "release", "name"); + TableFilter *filter = new TableFilter(_db, "track", "release_name"); filterLayout->addWidget(filter); _filterChain.addFilter(filter); } diff --git a/src/ui/audio/FilterChain.cpp b/src/ui/audio/FilterChain.cpp index b096fd08..403a6d2e 100644 --- a/src/ui/audio/FilterChain.cpp +++ b/src/ui/audio/FilterChain.cpp @@ -52,9 +52,6 @@ FilterChain::updateFilters(std::size_t startIdx) Filter::Constraint currentConstraint; - // TODO: replace with inner joins? - currentConstraint.where.And( WhereClause( "track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id")); - for (std::size_t idFilter = 0; idFilter < _filters.size(); ++idFilter) { Filter* filter = _filters.at(idFilter); diff --git a/src/ui/audio/KeywordSearchFilter.cpp b/src/ui/audio/KeywordSearchFilter.cpp index f90ed90e..9782a66a 100644 --- a/src/ui/audio/KeywordSearchFilter.cpp +++ b/src/ui/audio/KeywordSearchFilter.cpp @@ -39,7 +39,7 @@ KeywordSearchFilter::getConstraint(Constraint& constraint) // No active search means no constaint! if (!_lastEmittedText.empty()) { const std::string bindText ("%%" + _lastEmittedText + "%%"); - constraint.where.And( WhereClause("(track.name like ? or release.name like ? or artist.name like ?)").bind(bindText).bind(bindText).bind(bindText)); + constraint.where.And( WhereClause("(track.name like ? or track.release_name like ? or track.artist_name like ? or track.genre_list like ?)").bind(bindText).bind(bindText).bind(bindText).bind(bindText)); } // else no constraint! diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/PlayQueue.cpp index eed0f68e..67191329 100644 --- a/src/ui/audio/PlayQueue.cpp +++ b/src/ui/audio/PlayQueue.cpp @@ -87,7 +87,7 @@ PlayQueue::addTracks(const std::vector& trackIds) _model->setData(dataRow, 0, track.id()); _model->setData(dataRow, 1, dataRow + 1); - _model->setData(dataRow, 2, track->getArtist()->getName() + " - " + track->getName()); + _model->setData(dataRow, 2, track->getArtistName() + " - " + track->getName()); _model->setData(dataRow, 3, track->getDuration()); } } diff --git a/src/ui/audio/TableFilter.cpp b/src/ui/audio/TableFilter.cpp index 9bfd10a2..196efa7a 100644 --- a/src/ui/audio/TableFilter.cpp +++ b/src/ui/audio/TableFilter.cpp @@ -32,9 +32,9 @@ _db(db), _table(table), _field(field) { - _queryModel.setQuery( _db.getSession().query< ResultType >("select " + _table + "." + _field + ",count(DISTINCT track.id),0 as ORDERBY from track,artist,release,genre,track_genre WHERE track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id GROUP BY " + _table + "." + _field + " UNION select '',0,1 AS ORDERBY").orderBy("ORDERBY DESC," + _table + "." + _field)); - _queryModel.addColumn( _table + "." + _field, table); - _queryModel.addColumn( "count(DISTINCT track.id)", "Tracks"); + _queryModel.setQuery( _db.getSession().query< ResultType >("select track." + _field + ", COUNT(DISTINCT track.id) from track GROUP BY track." + _field).orderBy("track." + _field)); + _queryModel.addColumn( "track." + _field, field); + _queryModel.addColumn( "COUNT(DISTINCT track.id)", "tracks"); this->setSelectionMode(Wt::ExtendedSelection); this->setSortingEnabled(false); @@ -67,19 +67,16 @@ TableFilter::refresh(const Constraint& constraint) { SqlQuery sqlQuery; - sqlQuery.select(_table + "." + _field + ",count(DISTINCT track.id),0 as ORDERBY"); - sqlQuery.from().And( FromClause("artist,release,track,genre,track_genre")) ; + sqlQuery.select("track." + _field + ", COUNT(DISTINCT track.id)"); + sqlQuery.from().And( FromClause("track")) ; sqlQuery.where().And(constraint.where); // Add constraint made by other filters - sqlQuery.groupBy().And( _table + "." + _field); // Add constraint made by other filters + sqlQuery.groupBy().And( "track." + _field); // Add constraint made by other filters - SqlQuery AllSqlQuery; - AllSqlQuery.select("'',0,1 AS ORDERBY"); + LMS_LOG(MOD_UI, SEV_DEBUG) << _table << ", generated query = '" << sqlQuery.get() << "'"; - LMS_LOG(MOD_UI, SEV_DEBUG) << _table << ", generated query = '" << sqlQuery.get() + " UNION " + AllSqlQuery.get() << "'"; + Wt::Dbo::Query query = _db.getSession().query( sqlQuery.get() ); - Wt::Dbo::Query query = _db.getSession().query( sqlQuery.get() + " UNION " + AllSqlQuery.get() ); - - query.orderBy("ORDERBY DESC," + _table + "." + _field); + query.orderBy(_table + "." + _field); BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) { LMS_LOG(MOD_UI, SEV_DEBUG) << "Binding value '" << bindArg << "'"; @@ -108,13 +105,7 @@ TableFilter::getConstraint(Constraint& constraint) const ResultType& result = _queryModel.resultRow( index.row() ); // Get the track part - std::string name( result.get<0>() ); - bool isAll( result.get<2>() ) ; - - if (isAll) { - // no constraint, just return - return; - } + std::string name(result.get<0>()); clause.Or(_table + "." + _field + " = ?").bind(name); } diff --git a/src/ui/audio/TableFilter.hpp b/src/ui/audio/TableFilter.hpp index b9ad55a7..76644ff5 100644 --- a/src/ui/audio/TableFilter.hpp +++ b/src/ui/audio/TableFilter.hpp @@ -50,7 +50,7 @@ class TableFilter : public Wt::WTableView, public Filter const std::string _field; // Name, track count, special value that means 'all' if set to 1 - typedef boost::tuple ResultType; + typedef boost::tuple ResultType; Wt::Dbo::QueryModel< ResultType > _queryModel; }; diff --git a/src/ui/audio/TableFilterGenre.cpp b/src/ui/audio/TableFilterGenre.cpp new file mode 100644 index 00000000..6ad84c07 --- /dev/null +++ b/src/ui/audio/TableFilterGenre.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 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 + +#include "logger/Logger.hpp" + +#include "TableFilterGenre.hpp" + +namespace UserInterface { + +TableFilterGenre::TableFilterGenre(Database::Handler& db, Wt::WContainerWidget* parent) +: Wt::WTableView( parent ), +Filter(), +_db(db) +{ + _queryModel.setQuery( _db.getSession().query< ResultType >("select genre.name, COUNT(DISTINCT track.id) from genre INNER JOIN track_genre ON track_genre.genre_id = genre.id INNER JOIN track ON track.id = track_genre.track_id").orderBy("genre.name").groupBy("genre.name").orderBy("genre.name")); + _queryModel.addColumn("genre.name", "Genre"); + _queryModel.addColumn("COUNT(DISTINCT track.id)", "tracks"); + + this->setSelectionMode(Wt::ExtendedSelection); + this->setSortingEnabled(false); + this->setAlternatingRowColors(true); + this->setModel(&_queryModel); + + this->selectionChanged().connect(this, &TableFilterGenre::emitUpdate); + + setLayoutSizeAware(true); + + _queryModel.setBatchSize(100); +} + +void +TableFilterGenre::layoutSizeChanged (int width, int height) +{ + LMS_LOG(MOD_UI, SEV_DEBUG) << "LAYOUT CHANGED!"; + + /* TODO + std::size_t trackColumnSize = this->columnWidth(1).toPixels() + 30 ; + // Set the remaining size for the name column + this->setColumnWidth(0, width - 7 - trackColumnSize); + */ + +} + +// Set constraints on this filter +void +TableFilterGenre::refresh(const Constraint& constraint) +{ + SqlQuery sqlQuery; + + sqlQuery.select("genre.name, COUNT(DISTINCT track.id)"); + sqlQuery.from().And( std::string("genre INNER JOIN track_genre ON track_genre.genre_id = genre.id INNER JOIN track ON track.id = track_genre.track_id") ); + sqlQuery.where().And(constraint.where); // Add constraint made by other filters + sqlQuery.groupBy().And( std::string("genre.name") ); + + LMS_LOG(MOD_UI, SEV_DEBUG) << "genre, generated query = '" << sqlQuery.get() << "'"; + + Wt::Dbo::Query query = _db.getSession().query( sqlQuery.get() ); + + query.orderBy("genre.name"); + + BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) { + LMS_LOG(MOD_UI, SEV_DEBUG) << "Binding value '" << bindArg << "'"; + query.bind(bindArg); + } + + _queryModel.setQuery( query, true ); + +} + +// Get constraint created by this filter +void +TableFilterGenre::getConstraint(Constraint& constraint) +{ + Wt::WModelIndexSet indexSet = this->selectedIndexes(); + + // WHERE statement + WhereClause clause; + + BOOST_FOREACH(Wt::WModelIndex index, indexSet) { + + if (!index.isValid()) + continue; + + const ResultType& result = _queryModel.resultRow( index.row() ); + + // Get the track part + std::string name(result.get<0>()); + + if (name == "") + clause.Or( std::string("track.genre_list = ?") ).bind(""); + else + clause.Or( std::string("track.genre_list LIKE ?") ).bind("%%" + name + "%%"); + } + + // Adding our WHERE clause + constraint.where.And( clause ); + +} + +} // namespace UserInterface + diff --git a/src/ui/audio/TableFilterGenre.hpp b/src/ui/audio/TableFilterGenre.hpp new file mode 100644 index 00000000..b3968d44 --- /dev/null +++ b/src/ui/audio/TableFilterGenre.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2013 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 . + */ + +#ifndef TABLE_FILTER_GENRE_HPP +#define TABLE_FILTER_GENRE_HPP + + +#include +#include + +#include "Filter.hpp" +#include "database/DatabaseHandler.hpp" + +namespace UserInterface { + +class TableFilterGenre : public Wt::WTableView, public Filter +{ + + public: + TableFilterGenre(Database::Handler& db, Wt::WContainerWidget* parent = 0); + + // Set constraints on this filter + void refresh(const Constraint& constraint); + + // Get constraints created by this filter + void getConstraint(Constraint& constraint); + + void layoutSizeChanged (int width, int height); + + protected: + + Database::Handler& _db; + + // genre_name, count + typedef boost::tuple ResultType; + Wt::Dbo::QueryModel< ResultType > _queryModel; + +}; + +} // namespace UserInterface + +#endif + diff --git a/src/ui/audio/TrackView.cpp b/src/ui/audio/TrackView.cpp index 93a6a89d..d89522d0 100644 --- a/src/ui/audio/TrackView.cpp +++ b/src/ui/audio/TrackView.cpp @@ -33,9 +33,9 @@ TrackView::TrackView( Database::Handler& db, Wt::WContainerWidget* parent) : Wt::WTableView( parent ), _db(db) { - _queryModel.setQuery(_db.getSession().query("select track,release,artist from track,release,artist where track.release_id = release.id and track.artist_id = artist.id" ).orderBy("artist.name,release.name,track.disc_number,track.track_number")); - _queryModel.addColumn( "artist.name", "Artist" ); - _queryModel.addColumn( "release.name", "Album" ); + _queryModel.setQuery(_db.getSession().query("select track from track").orderBy("track.artist_name,track.date,track.release_name,track.disc_number,track.track_number")); + _queryModel.addColumn( "track.artist_name", "Artist" ); + _queryModel.addColumn( "track.release_name", "Album" ); _queryModel.addColumn( "track.disc_number", "Disc #" ); _queryModel.addColumn( "track.track_number", "Track #" ); _queryModel.addColumn( "track.name", "Track" ); @@ -107,15 +107,15 @@ TrackView::refresh(const Constraint& constraint) SqlQuery sqlQuery; - sqlQuery.select( "track,release,artist" ); - sqlQuery.from().And( FromClause("artist,release,track,genre,track_genre")); + sqlQuery.select( "track" ); + sqlQuery.from().And( FromClause("track")); sqlQuery.where().And(constraint.where); LMS_LOG(MOD_UI, SEV_DEBUG) << "TRACK REQ = '" << sqlQuery.get() << "'"; Wt::Dbo::Query query = _db.getSession().query( sqlQuery.get() ); - query.groupBy("track").orderBy("artist.name,track.date,release.name,track.disc_number,track.track_number"); + query.groupBy("track").orderBy("track.artist_name,track.date,track.release_name,track.disc_number,track.track_number"); BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) { LMS_LOG(MOD_UI, SEV_DEBUG) << "Binding value '" << bindArg << "'"; @@ -126,28 +126,6 @@ TrackView::refresh(const Constraint& constraint) } -/* -void -TrackView::handleTrackSelected(void) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - if (!indexSet.empty()) { - Wt::WModelIndex currentIndex( *indexSet.begin() ); - - // Check there are remainin tracks! - if (currentIndex.isValid()) - { - ResultType result = _queryModel.resultRow( currentIndex.row()); - - // Get the track part - Wt::Dbo::ptr track ( result.get<0>() ); - - _trackSelected.emit( track->getPath() ); - } - - } -} -*/ void TrackView::getSelectedTracks(std::vector& track_ids) { diff --git a/src/ui/audio/TrackView.hpp b/src/ui/audio/TrackView.hpp index 0c0ca203..0c492257 100644 --- a/src/ui/audio/TrackView.hpp +++ b/src/ui/audio/TrackView.hpp @@ -60,7 +60,7 @@ class TrackView : public Wt::WTableView, public Filter Database::Handler& _db; - typedef boost::tuple ResultType; + typedef boost::tuple ResultType; Wt::Dbo::QueryModel< ResultType > _queryModel; Wt::WTableView* _tableView; diff --git a/test/DatabaseIntegrity.cpp b/test/DatabaseIntegrity.cpp index d2b97a2d..1bd22e0f 100644 --- a/test/DatabaseIntegrity.cpp +++ b/test/DatabaseIntegrity.cpp @@ -44,8 +44,6 @@ int main(void) BOOST_FOREACH(Database::Track::pointer track, tracks ) { assert( !track->getName().empty() ); - assert( track->getArtist() ); - assert( track->getRelease() ); assert( !track->getGenres().empty() ); assert( !track->getDuration().is_not_a_date_time() ); } diff --git a/test/Makefile.am b/test/Makefile.am index be44845c..a80eb388 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -30,9 +30,7 @@ remote_client_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_src database_user_SOURCES = \ $(srcdir)/CheckDatabaseUser.cpp \ $(top_srcdir)/src/logger/Logger.cpp \ - $(top_srcdir)/src/database/Artist.cpp \ $(top_srcdir)/src/database/Genre.cpp \ - $(top_srcdir)/src/database/Release.cpp \ $(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/MediaDirectory.cpp \ @@ -46,9 +44,7 @@ database_user_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_src database_integrity_SOURCES = \ $(srcdir)/DatabaseIntegrity.cpp \ $(top_srcdir)/src/logger/Logger.cpp \ - $(top_srcdir)/src/database/Artist.cpp \ $(top_srcdir)/src/database/Genre.cpp \ - $(top_srcdir)/src/database/Release.cpp \ $(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/MediaDirectory.cpp \ diff --git a/test/RemoteClient.cpp b/test/RemoteClient.cpp index dffdeb7c..b95c7749 100644 --- a/test/RemoteClient.cpp +++ b/test/RemoteClient.cpp @@ -34,37 +34,34 @@ struct GenreInfo { - uint64_t id; std::string name; }; std::ostream& operator<<(std::ostream& os, const GenreInfo& info) { - os << "id = " << info.id << ", name = '" << info.name << "'"; + os << "name = '" << info.name << "'"; return os; } struct ArtistInfo { - uint64_t id; std::string name; }; std::ostream& operator<<(std::ostream& os, const ArtistInfo& info) { - os << "id = " << info.id << ", name = '" << info.name << "'"; + os << "name = '" << info.name << "'"; return os; } struct ReleaseInfo { - uint64_t id; std::string name; }; std::ostream& operator<<(std::ostream& os, const ReleaseInfo& info) { - os << "id = " << info.id << ", name = '" << info.name; + os << "name = '" << info.name; return os; } @@ -125,7 +122,8 @@ class TestClient _socket.handshake(boost::asio::ssl::stream_base::client); } - void getArtists(std::vector& artists, const std::vector& genreIds) + void getArtists(std::vector& artists, + const std::vector& genres = std::vector()) { const std::size_t requestedBatchSize = 128; @@ -133,12 +131,12 @@ class TestClient std::size_t res = 0; - while ((res = getArtists(artists, genreIds, offset, requestedBatchSize) ) > 0) + while ((res = getArtists(artists, genres, offset, requestedBatchSize) ) > 0) offset += res; } - std::size_t getArtists(std::vector& artists, const std::vector& genreIds, std::size_t offset, std::size_t size) + std::size_t getArtists(std::vector& artists, const std::vector& genres, std::size_t offset, std::size_t size) { std::size_t nbArtists = 0; @@ -150,8 +148,8 @@ class TestClient request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetArtistList); request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_size(size); request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_offset(offset); - BOOST_FOREACH(uint64_t genreId, genreIds) - request.mutable_audio_collection_request()->mutable_get_artists()->add_genre_id(genreId); + BOOST_FOREACH(const std::string& genre, genres) + request.mutable_audio_collection_request()->mutable_get_artists()->add_genre(genre); sendMsg(request); @@ -172,7 +170,6 @@ class TestClient throw std::runtime_error("no artist name!"); ArtistInfo artist; - artist.id = response.audio_collection_response().artist_list().artists(i).id(); artist.name = response.audio_collection_response().artist_list().artists(i).name(); artists.push_back( artist ); @@ -226,7 +223,6 @@ class TestClient throw std::runtime_error("no genre name!"); GenreInfo genre; - genre.id = response.audio_collection_response().genre_list().genres(i).id(); genre.name = response.audio_collection_response().genre_list().genres(i).name(); genres.push_back( genre ); @@ -235,19 +231,21 @@ class TestClient return nbAdded; } - void getReleases(std::vector& releases, const std::vector artistIds, const std::vector genreIds) + void getReleases(std::vector& releases, + const std::vector& artists = std::vector(), + const std::vector& genres = std::vector()) { const std::size_t requestedBatchSize = 256; std::size_t offset = 0; std::size_t res = 0; - while ((res = getReleases(releases, artistIds, genreIds, offset, requestedBatchSize) ) > 0) + while ((res = getReleases(releases, artists, genres, offset, requestedBatchSize) ) > 0) offset += res; } - std::size_t getReleases(std::vector& releases, const std::vector artistIds, const std::vector genreIds, std::size_t offset, std::size_t size) + std::size_t getReleases(std::vector& releases, const std::vector& artists, const std::vector& genres, std::size_t offset, std::size_t size) { std::size_t nbAdded = 0; @@ -259,11 +257,11 @@ class TestClient request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetReleaseList); request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_size(size); request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_offset(offset); - BOOST_FOREACH(uint64_t artistId, artistIds) - request.mutable_audio_collection_request()->mutable_get_releases()->add_artist_id(artistId); + BOOST_FOREACH(const std::string& artist, artists) + request.mutable_audio_collection_request()->mutable_get_releases()->add_artist(artist); - BOOST_FOREACH(uint64_t genreId, genreIds) - request.mutable_audio_collection_request()->mutable_get_releases()->add_genre_id(genreId); + BOOST_FOREACH(const std::string& genre, genres) + request.mutable_audio_collection_request()->mutable_get_releases()->add_genre(genre); sendMsg(request); @@ -290,7 +288,6 @@ class TestClient throw std::runtime_error("no release name!"); ReleaseInfo release; - release.id = response.audio_collection_response().release_list().releases(i).id(); release.name = response.audio_collection_response().release_list().releases(i).name(); releases.push_back( release ); @@ -299,20 +296,23 @@ class TestClient return nbAdded; } - void getTracks(std::vector& tracks, const std::vector artistIds, const std::vector releaseIds, const std::vector genreIds) + void getTracks(std::vector& tracks, + const std::vector& artists = std::vector(), + const std::vector& releases = std::vector(), + const std::vector& genres = std::vector()) { const std::size_t requestedBatchSize = 0; std::size_t offset = 0; std::size_t res = 0; - while ((res = getTracks(tracks, artistIds, releaseIds, genreIds, offset, requestedBatchSize) ) > 0) + while ((res = getTracks(tracks, artists, releases, genres, offset, requestedBatchSize) ) > 0) offset += res; } std::size_t getTracks(std::vector& tracks, - const std::vector artistIds, - const std::vector releaseIds, - const std::vector genreIds, + const std::vector& artists, + const std::vector& releases, + const std::vector& genres, std::size_t offset, std::size_t size) { @@ -327,12 +327,12 @@ class TestClient request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_size(size); request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_offset(offset); - BOOST_FOREACH(uint64_t artistId, artistIds) - request.mutable_audio_collection_request()->mutable_get_tracks()->add_artist_id(artistId); - BOOST_FOREACH(uint64_t releaseId, releaseIds) - request.mutable_audio_collection_request()->mutable_get_tracks()->add_release_id(releaseId); - BOOST_FOREACH(uint64_t genreId, genreIds) - request.mutable_audio_collection_request()->mutable_get_tracks()->add_genre_id(genreId); + BOOST_FOREACH(const std::string& artist, artists) + request.mutable_audio_collection_request()->mutable_get_tracks()->add_artist(artist); + BOOST_FOREACH(const std::string& release, releases) + request.mutable_audio_collection_request()->mutable_get_tracks()->add_release(release); + BOOST_FOREACH(const std::string& genre, genres) + request.mutable_audio_collection_request()->mutable_get_tracks()->add_genre(genre); sendMsg(request); @@ -419,7 +419,7 @@ class TestClient } } - void getCoverRelease(std::vector& coverArt, uint64_t releaseId) + void getCoverRelease(std::vector& coverArt, const std::string& release) { // Send request Remote::ClientMessage request; @@ -428,7 +428,7 @@ class TestClient request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetCoverArt); request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( Remote::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease); - request.mutable_audio_collection_request()->mutable_get_cover_art()->set_release_id( releaseId ); + request.mutable_audio_collection_request()->mutable_get_cover_art()->set_release( release ); request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 ); sendMsg(request); @@ -739,7 +739,8 @@ enum class Test { ReleaseFilterArtist, ReleaseFilterGenre, TrackFilters, - Cover, + CoverByTrack, + CoverByRelease, Transcode, }; @@ -749,7 +750,8 @@ std::set tests = { Test::ReleaseFilterArtist, Test::ReleaseFilterGenre, Test::TrackFilters, -// Test::Cover, +// Test::CoverByTrack, + Test::CoverByRelease, // Test::Transcode, }; @@ -781,7 +783,7 @@ int main() // ****** Artists ********* std::cout << "Getting artists..." << std::endl; std::vector artists; - client.getArtists(artists, std::vector()); + client.getArtists(artists); std::cout << "Got " << artists.size() << " artists!" << std::endl; BOOST_FOREACH(const ArtistInfo& artist, artists) @@ -799,14 +801,15 @@ int main() // **** Releases ****** std::cout << "Getting releases..." << std::endl; std::vector releases; - client.getReleases(releases, std::vector(), std::vector()); + client.getReleases(releases); BOOST_FOREACH(const ReleaseInfo& release, releases) std::cout << "Release: '" << release << "'" << std::endl; // **** Tracks ****** std::cout << "Getting tracks..." << std::endl; std::vector tracks; - client.getTracks(tracks, std::vector(), std::vector(), std::vector()); + client.getTracks(tracks); + BOOST_FOREACH(const TrackInfo& track, tracks) std::cout << "Track: '" << track << "'" << std::endl; @@ -819,7 +822,7 @@ int main() { std::cout << "Getting artists from genre '" << genre.name << "'" << std::endl; std::vector artists; - client.getArtists(artists, std::vector(1, genre.id)); + client.getArtists(artists, std::vector(1, genre.name)); BOOST_FOREACH(const ArtistInfo& artist, artists) std::cout << "Genre '" << genre.name << "' -> Artist: " << artist << std::endl; @@ -833,7 +836,7 @@ int main() { std::cout << "Getting release from artist '" << artist.name << "'" << std::endl; std::vector releases; - client.getReleases(releases, std::vector(1, artist.id), std::vector()); + client.getReleases(releases, std::vector(1, artist.name) ); BOOST_FOREACH(const ReleaseInfo& release, releases) std::cout << "Artist '" << artist.name << "' -> Release: '" << release << "'" << std::endl; @@ -847,7 +850,7 @@ int main() { std::cout << "Getting release from genre '" << genre.name << "'" << std::endl; std::vector releases; - client.getReleases(releases, std::vector(), std::vector(1, genre.id)); + client.getReleases(releases, std::vector(), std::vector(1, genre.name)); BOOST_FOREACH(const ReleaseInfo& release, releases) std::cout << "Genre '" << genre.name << "' -> Release: '" << release << "'" << std::endl; @@ -861,7 +864,7 @@ int main() BOOST_FOREACH(const ArtistInfo& artist, artists) { std::vector tracks; - client.getTracks(tracks, std::vector(1, artist.id), std::vector(), std::vector()); + client.getTracks(tracks, std::vector(1, artist.name)); std::cout << "Artist '" << artist.name << "', nb tracks = " << tracks.size() << std::endl; BOOST_FOREACH(const TrackInfo& track, tracks) @@ -870,20 +873,20 @@ int main() } // ***** Covers ******* - if (test(Test::Cover)) + if (test(Test::CoverByRelease)) { std::cout << "Getting cover for each release..." << std::endl; BOOST_FOREACH(const ReleaseInfo& release, releases) { std::vector coverArts; - client.getCoverRelease(coverArts, release.id); + client.getCoverRelease(coverArts, release.name); if (writeCovers) { boost::filesystem::create_directory("cover"); BOOST_FOREACH(const Cover coverArt, coverArts) { - std::ostringstream oss; oss << "cover/" << release.id << "." << release.name << ".jpeg"; + std::ostringstream oss; oss << "cover/" << release.name << ".jpeg"; std::ofstream out(oss.str().c_str()); BOOST_FOREACH(unsigned char c, coverArt.data) out.put(c); @@ -892,7 +895,10 @@ int main() std::cout << "Release '" << release << "', spotted " << coverArts.size() << " covers!" << std::endl; } + } + if (test(Test::CoverByTrack)) + { std::cout << "Getting cover for each track..." << std::endl; BOOST_FOREACH(const TrackInfo& track, tracks) {