Merge branch 'database' into develop

This commit is contained in:
emeric
2015-07-26 20:31:20 +02:00
44 changed files with 1600 additions and 686 deletions
+3
View File
@@ -11,9 +11,12 @@ lms_SOURCES = \
$(srcdir)/config/ConfigReader.cpp \ $(srcdir)/config/ConfigReader.cpp \
$(srcdir)/cover/CoverArt.cpp \ $(srcdir)/cover/CoverArt.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \
$(srcdir)/database/Artist.cpp \
$(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/DatabaseHandler.cpp \
$(srcdir)/database/MediaDirectory.cpp \ $(srcdir)/database/MediaDirectory.cpp \
$(srcdir)/database/Playlist.cpp \ $(srcdir)/database/Playlist.cpp \
$(srcdir)/database/Release.cpp \
$(srcdir)/database/SearchFilter.cpp \
$(srcdir)/database/SqlQuery.cpp \ $(srcdir)/database/SqlQuery.cpp \
$(srcdir)/database/Track.cpp \ $(srcdir)/database/Track.cpp \
$(srcdir)/database/User.cpp \ $(srcdir)/database/User.cpp \
+4 -6
View File
@@ -196,7 +196,7 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI
std::vector<CoverArt> std::vector<CoverArt>
Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std::size_t nbMaxCovers) const Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers) const
{ {
using namespace Database; using namespace Database;
@@ -205,13 +205,11 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std:
// Get the first track of the release // Get the first track of the release
{ {
SearchFilter filter;
filter.exactMatch[SearchFilter::Field::Release].push_back(releaseName);
Wt::Dbo::Transaction transaction(session); Wt::Dbo::Transaction transaction(session);
std::vector<Track::pointer> tracks std::vector<Track::pointer> tracks = Track::getByFilter(session,
= Track::getAll(session, filter, -1, 1 /* limit result size */); SearchFilter::IdMatch({{SearchFilter::Field::Release, {releaseId}}}),
-1, 1 /* limit result size */);
if (tracks.empty()) if (tracks.empty())
return std::vector<CoverArt>(); return std::vector<CoverArt>();
+1 -1
View File
@@ -52,7 +52,7 @@ class Grabber
std::vector<CoverArt> getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers = 1) const; std::vector<CoverArt> getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; std::vector<CoverArt> getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const; std::vector<CoverArt> getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std::size_t nbMaxCovers = 1) const; std::vector<CoverArt> getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const;
private: private:
Grabber(); Grabber();
+134 -26
View File
@@ -261,11 +261,104 @@ Updater::process(boost::system::error_code err)
} }
} }
Artist::pointer
Updater::getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& mbid)
{
Artist::pointer artist;
// First try to get by MBID
if (!mbid.empty())
{
artist = Artist::getByMBID( _db.getSession(), mbid );
if (!artist)
artist = Artist::create( _db.getSession(), name, mbid);
return artist;
}
// Fall back on artist name (collisions may occur)
if (!name.empty())
{
for (Artist::pointer sameNamedArtist : Artist::getByName( _db.getSession(), name ))
{
if (sameNamedArtist->getMBID().empty())
{
artist = sameNamedArtist;
break;
}
}
// No Artist found with the same name and without MBID -> creating
if (!artist)
artist = Artist::create( _db.getSession(), name);
return artist;
}
return Artist::getNone( _db.getSession() );
}
Release::pointer
Updater::getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& mbid)
{
Release::pointer release;
// First try to get by MBID
if (!mbid.empty())
{
release = Release::getByMBID( _db.getSession(), mbid );
if (!release)
release = Release::create( _db.getSession(), name, mbid);
return release;
}
// Fall back on release name (collisions may occur)
if (!name.empty())
{
for (Release::pointer sameNamedRelease : Release::getByName( _db.getSession(), name ))
{
if (sameNamedRelease->getMBID().empty())
{
release = sameNamedRelease;
break;
}
}
// No release found with the same name and without MBID -> creating
if (!release)
release = Release::create( _db.getSession(), name);
return release;
}
return Release::getNone( _db.getSession() );
}
std::vector<Genre::pointer>
Updater::getGenres( const std::list<std::string>& names)
{
std::vector< Genre::pointer > genres;
for (const std::string& name : names)
{
Genre::pointer genre ( Genre::getByName(_db.getSession(), name) );
if (!genre)
genre = Genre::create(_db.getSession(), name);
genres.push_back( genre );
}
if (genres.empty())
genres.push_back( Genre::getNone( _db.getSession() ));
return genres;
}
void void
Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{ {
try { try {
// Check last update time // Check last update time
boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) ); boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) );
@@ -335,26 +428,44 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
// ***** Genres // ***** Genres
typedef std::list<std::string> GenreList;
GenreList genreList;
std::vector< Genre::pointer > genres; std::vector< Genre::pointer > genres;
if (items.find(MetaData::Type::Genres) != items.end())
{ {
genreList = (boost::any_cast<GenreList>(items[MetaData::Type::Genres])); genres = getGenres( boost::any_cast< std::list<std::string> > (items[MetaData::Type::Genres]) );
BOOST_FOREACH(const std::string& genre, genreList) {
Genre::pointer dbGenre ( Genre::getByName(_db.getSession(), genre) );
if (!dbGenre)
dbGenre = Genre::create(_db.getSession(), genre);
genres.push_back( dbGenre );
}
} }
if (genres.empty())
genres.push_back( Genre::getNone( _db.getSession() ));
assert( !genres.empty() ); assert( !genres.empty() );
// Get the associated Artist
Artist::pointer artist;
{
std::string artistName;
std::string artistMusicBrainzID;
if (items.find(MetaData::Type::MusicBrainzArtistID) != items.end())
artistMusicBrainzID = boost::any_cast<std::string>(items[MetaData::Type::MusicBrainzArtistID] );
if (items.find(MetaData::Type::Artist) != items.end())
artistName = boost::any_cast<std::string>(items[MetaData::Type::Artist]);
artist = getArtist(file, artistName, artistMusicBrainzID);
}
assert(artist);
// Get the associated Release
Release::pointer release;
{
std::string releaseName;
std::string releaseMusicBrainzID;
if (items.find(MetaData::Type::MusicBrainzAlbumID) != items.end())
releaseMusicBrainzID = boost::any_cast<std::string>(items[MetaData::Type::MusicBrainzAlbumID] );
if (items.find(MetaData::Type::Album) != items.end())
releaseName = boost::any_cast<std::string>(items[MetaData::Type::Album]);
release = getRelease(file, releaseName, releaseMusicBrainzID);
}
assert(release);
// If file already exist, update data // If file already exist, update data
// Otherwise, create it // Otherwise, create it
if (!track) if (!track)
@@ -372,6 +483,8 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
assert(track); assert(track);
track.modify()->setArtist(artist);
track.modify()->setRelease(release);
track.modify()->setLastWriteTime(lastWriteTime); track.modify()->setLastWriteTime(lastWriteTime);
track.modify()->setName(title); track.modify()->setName(title);
track.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]) ); track.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]) );
@@ -379,22 +492,17 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{ {
std::string trackGenreList; std::string trackGenreList;
// Product genre list // Product genre list
BOOST_FOREACH(const std::string& genre, genreList) { for (Genre::pointer genre : genres)
{
if (!trackGenreList.empty()) if (!trackGenreList.empty())
trackGenreList += ", "; trackGenreList += ", ";
trackGenreList += genre; trackGenreList += genre->getName();
} }
track.modify()->setGenres( trackGenreList ); track.modify()->setGenres( trackGenreList );
} }
track.modify()->setGenres( genres ); track.modify()->setGenres( genres );
if (items.find(MetaData::Type::Artist) != items.end())
track.modify()->setArtistName( boost::any_cast<std::string>(items[MetaData::Type::Artist]) );
if (items.find(MetaData::Type::Album) != items.end())
track.modify()->setReleaseName( boost::any_cast<std::string>(items[MetaData::Type::Album]) );
if (items.find(MetaData::Type::TrackNumber) != items.end()) if (items.find(MetaData::Type::TrackNumber) != items.end())
track.modify()->setTrackNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TrackNumber]) ); track.modify()->setTrackNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TrackNumber]) );
@@ -538,7 +646,7 @@ Updater::checkAudioFiles( Stats& stats )
} }
// Now process orphan Genre (no track) // Now process orphan Genre (no track)
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres..."; /* LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres...";
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Genre> > Genres; typedef Wt::Dbo::collection< Wt::Dbo::ptr<Genre> > Genres;
Genres genres = Genre::getAll(_db.getSession()); Genres genres = Genre::getAll(_db.getSession());
@@ -549,7 +657,7 @@ Updater::checkAudioFiles( Stats& stats )
if (genre->getTracks().size() == 0) if (genre->getTracks().size() == 0)
genre.remove(); genre.remove();
} }
*/
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check audio files done!"; LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check audio files done!";
} }
+4
View File
@@ -72,6 +72,10 @@ class Updater
Database::MediaDirectory::Type type, Database::MediaDirectory::Type type,
Stats& stats); Stats& stats);
// 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<Database::Genre::pointer> getGenres( const std::list<std::string>& names);
// Audio // Audio
void checkAudioFiles( Stats& stats ); void checkAudioFiles( Stats& stats );
+132
View File
@@ -0,0 +1,132 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "Artist.hpp"
#include "SqlQuery.hpp"
#include "logger/Logger.hpp"
namespace Database
{
Artist::Artist(const std::string& name, const std::string& MBID)
: _name(std::string(name, 0 , _maxNameLength)),
_MBID(MBID)
{
}
std::vector<Artist::pointer>
Artist::getByName(Wt::Dbo::Session& session, const std::string& name)
{
Wt::Dbo::collection<Artist::pointer> res = session.find<Artist>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
return std::vector<Artist::pointer>(res.begin(), res.end());
}
Artist::pointer
Artist::getByMBID(Wt::Dbo::Session& session, const std::string& mbid)
{
return session.find<Artist>().where("mbid = ?").bind(mbid);
}
Artist::pointer
Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID)
{
return session.add(new Artist(name, MBID));
}
Artist::pointer
Artist::getNone(Wt::Dbo::Session& session)
{
std::vector<pointer> res = getByName(session, "<None>");
if (res.empty())
return create(session, "<None>");
return res.front();
}
std::vector<Artist::pointer>
Artist::getAll(Wt::Dbo::Session& session, int offset, int size)
{
Wt::Dbo::collection<pointer> res = session.find<Artist>().offset(offset).limit(size);
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getAllOrphans(Wt::Dbo::Session& session)
{
Wt::Dbo::collection<Artist::pointer> res = session.query< Wt::Dbo::ptr<Artist> >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL");
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
Wt::Dbo::collection<Artist::pointer> res = getQuery(session, filter).limit(size).offset(offset);
return std::vector<pointer>(res.begin(), res.end());
}
Wt::Dbo::Query<Artist::pointer>
Artist::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<pointer> query
= session.query<pointer>( "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");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
Wt::Dbo::Query<Artist::UIQueryResult>
Artist::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<UIQueryResult> query
= session.query<UIQueryResult>( "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");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
void
Artist::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<UIQueryResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames)
{
Wt::Dbo::Query<UIQueryResult> query = getUIQuery(session, filter);
model.setQuery(query, columnNames.empty() ? true : false);
// TODO do something better
if (columnNames.size() == 3)
{
model.addColumn( "a.name", columnNames.at(0));
model.addColumn( "COUNT(DISTINCT r.id)", columnNames.at(1) );
model.addColumn( "COUNT(DISTINCT t.id)", columnNames.at(2) );
}
}
} // namespace Database
+97
View File
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _DB_ARTIST_HPP_
#define _DB_ARTIST_HPP_
#include <string>
#include <vector>
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/QueryModel>
#include "SearchFilter.hpp"
namespace Database
{
class Track;
class Genre;
class Artist
{
public:
typedef Wt::Dbo::ptr<Artist> pointer;
typedef Wt::Dbo::dbo_traits<Artist>::IdType id_type;
Artist() {}
Artist(const std::string& name, const std::string& MBID = "");
// Accessors
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static pointer getById(Wt::Dbo::Session& session, id_type id);
static pointer getNone(Wt::Dbo::Session& session); // Special entry
static std::vector<pointer> getByName(Wt::Dbo::Session& session, const std::string& name);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1);
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session);
// Accessors
std::string getName(void) const { return _name; }
std::string getMBID(void) const { return _MBID; }
void setMBID(std::string mbid) { _MBID = mbid; }
// Create
static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = "");
// MVC models for the user interface
// ID, Artist name, albums, tracks
typedef boost::tuple<id_type, std::string, int, int> UIQueryResult;
static Wt::Dbo::Query<UIQueryResult> getUIQuery(Wt::Dbo::Session& session, SearchFilter filter);
static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<UIQueryResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>());
bool isNone(void) const;
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _MBID, "mbid");
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist");
}
private:
static Wt::Dbo::Query<pointer> getQuery(Wt::Dbo::Session& session, SearchFilter filter);
static const std::size_t _maxNameLength = 128;
std::string _name;
std::string _MBID; // Musicbrainz Identifier
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > _tracks; // Tracks of this artist
};
} // namespace Database
#endif
+5 -2
View File
@@ -77,10 +77,12 @@ Handler::Handler(boost::filesystem::path db)
_dbBackend( db.string() ) _dbBackend( db.string() )
{ {
_session.setConnection(_dbBackend); _session.setConnection(_dbBackend);
_session.mapClass<Database::Artist>("artist");
_session.mapClass<Database::Genre>("genre"); _session.mapClass<Database::Genre>("genre");
_session.mapClass<Database::Track>("track"); _session.mapClass<Database::Track>("track");
_session.mapClass<Database::Playlist>("playlist"); _session.mapClass<Database::Playlist>("playlist");
_session.mapClass<Database::PlaylistEntry>("playlist_entry"); _session.mapClass<Database::PlaylistEntry>("playlist_entry");
_session.mapClass<Database::Release>("release");
_session.mapClass<Database::Video>("video"); _session.mapClass<Database::Video>("video");
_session.mapClass<Database::MediaDirectory>("media_directory"); _session.mapClass<Database::MediaDirectory>("media_directory");
_session.mapClass<Database::MediaDirectorySettings>("media_directory_settings"); _session.mapClass<Database::MediaDirectorySettings>("media_directory_settings");
@@ -92,9 +94,10 @@ _dbBackend( db.string() )
try { try {
_session.createTables(); _session.createTables();
_dbBackend.executeSql("CREATE INDEX artist_name_idx ON track(artist_name)"); _dbBackend.executeSql("CREATE INDEX artist_name_idx ON artist(name)");
_dbBackend.executeSql("CREATE INDEX release_name_idx ON track(release_name)");
_dbBackend.executeSql("CREATE INDEX genre_name_idx ON genre(name)"); _dbBackend.executeSql("CREATE INDEX genre_name_idx ON genre(name)");
_dbBackend.executeSql("CREATE INDEX release_name_idx ON release(name)");
_dbBackend.executeSql("CREATE INDEX track_name_idx ON track(name)");
} }
catch(std::exception& e) { catch(std::exception& e) {
LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what(); LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what();
+2
View File
@@ -22,6 +22,8 @@
#include <Wt/Dbo/Dbo> #include <Wt/Dbo/Dbo>
#include <string>
namespace Database { namespace Database {
class PlaylistEntry; class PlaylistEntry;
+115
View File
@@ -0,0 +1,115 @@
#include "Release.hpp"
#include "SearchFilter.hpp"
#include "SqlQuery.hpp"
namespace Database
{
Release::Release(const std::string& name, const std::string& MBID)
: _name(std::string(name, 0 , _maxNameLength)),
_MBID(MBID)
{
}
std::vector<Release::pointer>
Release::getByName(Wt::Dbo::Session& session, const std::string& name)
{
Wt::Dbo::collection<Release::pointer> res = session.find<Release>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
return std::vector<Release::pointer>(res.begin(), res.end());
}
Release::pointer
Release::getByMBID(Wt::Dbo::Session& session, const std::string& mbid)
{
return session.find<Release>().where("mbid = ?").bind(mbid);
}
Release::pointer
Release::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID)
{
return session.add(new Release(name, MBID));
}
Release::pointer
Release::getNone(Wt::Dbo::Session& session)
{
std::vector<pointer> res = getByName(session, "<None>");
if (res.empty())
return create(session, "<None>");
return res.front();
}
std::vector<Release::pointer>
Release::getAll(Wt::Dbo::Session& session, int offset, int size)
{
Wt::Dbo::collection<pointer> res = session.find<Release>().offset(offset).limit(size);
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getAllOrphans(Wt::Dbo::Session& session)
{
Wt::Dbo::collection<Release::pointer> res = session.query< Wt::Dbo::ptr<Release> >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL");
return std::vector<pointer>(res.begin(), res.end());
}
Wt::Dbo::Query<Release::pointer>
Release::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<pointer> query
= session.query<pointer>("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");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
Wt::Dbo::Query<Release::UIQueryResult>
Release::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
// TODO DATE of RELEASE
Wt::Dbo::Query<UIQueryResult> query
= session.query<UIQueryResult>("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");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
void
Release::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<UIQueryResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames)
{
Wt::Dbo::Query<UIQueryResult> query = getUIQuery(session, filter);
model.setQuery(query, columnNames.empty() ? true : false);
// TODO do something better
if (columnNames.size() == 3)
{
model.addColumn( "r.name", columnNames[0]);
model.addColumn( "t.date", columnNames[1]);
model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2]);
}
}
std::vector<Release::pointer>
Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
Wt::Dbo::collection<pointer> res = getQuery(session, filter).limit(size).offset(offset);
return std::vector<pointer>(res.begin(), res.end());
}
} // namespace Database
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _DB_RELEASE_HPP_
#define _DB_RELEASE_HPP_
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/QueryModel>
#include "SearchFilter.hpp"
namespace Database
{
class Track;
class Release
{
public:
typedef Wt::Dbo::ptr<Release> pointer;
typedef Wt::Dbo::dbo_traits<Release>::IdType id_type;
Release() {}
Release(const std::string& name, const std::string& MBID = "");
// Accessors
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static std::vector<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); // Special entry
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, int offset, int size);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
// Create
static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = "");
// MVC models for the user interface
// ID, Release name, year, track counts
typedef boost::tuple<id_type, std::string, boost::posix_time::ptime, int> UIQueryResult;
static Wt::Dbo::Query<UIQueryResult> getUIQuery(Wt::Dbo::Session& session, SearchFilter filter);
static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>());
// Accessosrs
std::string getName() const { return _name; }
std::string getMBID() const { return _MBID; }
bool isNone(void) const;
boost::posix_time::time_duration getDuration(void) const;
void setMBID(std::string mbid) { _MBID = mbid; }
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _MBID, "mbid");
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
}
private:
static Wt::Dbo::Query<pointer> getQuery(Wt::Dbo::Session& session, SearchFilter filter);
static const std::size_t _maxNameLength = 128;
std::string _name;
std::string _MBID;
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > _tracks; // Tracks in the release
};
} // namespace Database
#endif
+94
View File
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
*/
#include "SearchFilter.hpp"
namespace Database
{
SqlQuery generatePartialQuery(SearchFilter& filter)
{
SqlQuery sqlQuery;
WhereClause likeWhereClause;
// Process name like parameters
for (auto nameLikeMatch : filter.nameLikeMatch)
{
// Artist
switch (nameLikeMatch.first)
{
case SearchFilter::Field::Artist:
for (const std::string& name : nameLikeMatch.second)
likeWhereClause.Or( WhereClause("a.name LIKE ?") ).bind("%%" + name + "%%");
break;
case SearchFilter::Field::Release:
for (const std::string& name : nameLikeMatch.second)
likeWhereClause.Or( WhereClause("r.name LIKE ?") ).bind("%%" + name + "%%");
break;
case SearchFilter::Field::Genre:
for (const std::string& name : nameLikeMatch.second)
likeWhereClause.Or( WhereClause("g.name LIKE ?") ).bind("%%" + name + "%%");
break;
case SearchFilter::Field::Track:
for (const std::string& name : nameLikeMatch.second)
likeWhereClause.Or( WhereClause("t.name LIKE ?") ).bind("%%" + name + "%%");
break;
}
}
sqlQuery.where().And( likeWhereClause );
// Process id exact match parameters
for (auto idMatch : filter.idMatch)
{
WhereClause idWhereClause;
switch (idMatch.first)
{
case SearchFilter::Field::Artist:
for (auto id : idMatch.second)
idWhereClause.Or( WhereClause("a.id = ?") ).bind( std::to_string(id));
break;
case SearchFilter::Field::Release:
for (auto id : idMatch.second)
idWhereClause.Or( WhereClause("r.id = ?") ).bind( std::to_string(id));
break;
case SearchFilter::Field::Genre:
for (auto id : idMatch.second)
idWhereClause.Or( WhereClause("g.id = ?") ).bind( std::to_string(id));
break;
case SearchFilter::Field::Track:
for (auto id : idMatch.second)
idWhereClause.Or( WhereClause("t.id = ?") ).bind( std::to_string(id));
break;
}
sqlQuery.where().And( idWhereClause );
}
return sqlQuery;
}
} // namespace Database
+78
View File
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _DB_SEARCH_FILTER_HPP_
#define _DB_SEARCH_FILTER_HPP_
#include <string>
#include <vector>
#include <map>
#include <Wt/Dbo/Dbo>
#include "SqlQuery.hpp"
namespace Database
{
class SearchFilter
{
public:
enum class Field {
Artist, // artist
Release, // release
Genre, // genre
Track, // track
};
typedef std::map<Field, std::vector<std::string> > NameLikeMatchMap;
typedef std::map<Field, std::vector< Wt::Dbo::dbo_default_traits::IdType> > IdMatchMap;
SearchFilter() {}
static SearchFilter IdMatch( const IdMatchMap& _idMatch )
{
return SearchFilter(_idMatch);
}
static SearchFilter NameLikeMatch( const NameLikeMatchMap& _nameLikeMatch )
{
return SearchFilter(_nameLikeMatch);
}
// The filter is a AND of the following conditions:
// ((Field1.name LIKE STR1-1 OR Field1.name LIKE STR1-2 ...) OR (Field2.name LIKE STR2-1 OR Field2.name LIKE STR2-2 ...) ...
NameLikeMatchMap nameLikeMatch;
// ((Field1.id = ID1-1 OR Field1.id = ID1-2 ... ) AND ((Field2.id = ID2-1 OR Field2.id = ID2-2 ... ) ...
IdMatchMap idMatch;
private:
SearchFilter(const NameLikeMatchMap& _nameLikeMatch) : nameLikeMatch(_nameLikeMatch) {}
SearchFilter(const IdMatchMap& _idMatch) : idMatch(_idMatch) {}
};
SqlQuery generatePartialQuery(SearchFilter& filter);
} // namespace Database
#endif // _DB_SEARCH_FILTER_HPP_
+64 -231
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <boost/foreach.hpp>
#include <Wt/Dbo/QueryModel> #include <Wt/Dbo/QueryModel>
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
@@ -29,104 +27,6 @@
namespace Database { namespace Database {
static SqlQuery generatePartialQuery(SearchFilter& filter, bool forceGenreInnerJoin = false)
{
bool genreJoin = forceGenreInnerJoin;
SqlQuery sqlQuery;
// Process like searches
BOOST_FOREACH(SearchFilter::FieldValues& likeMatch, filter.likeMatches)
{
WhereClause likeWhereClause;
// Artist
{
WhereClause whereClause;
BOOST_FOREACH(const std::string& name, likeMatch[SearchFilter::Field::Artist])
whereClause.Or( WhereClause("t.artist_name LIKE ?") ).bind("%%" + name + "%%");
likeWhereClause.Or( whereClause );
}
// Release
{
WhereClause whereClause;
BOOST_FOREACH(const std::string& name, likeMatch[SearchFilter::Field::Release])
whereClause.Or( WhereClause("t.release_name LIKE ?") ).bind("%%" + name + "%%");
likeWhereClause.Or( whereClause );
}
// Genre
{
WhereClause whereClause;
BOOST_FOREACH(const std::string& name, likeMatch[SearchFilter::Field::Genre])
{
whereClause.Or( WhereClause("g.name LIKE ?") ).bind("%%" + name + "%%");
genreJoin = true;
}
likeWhereClause.Or( whereClause );
}
// Track
{
WhereClause whereClause;
BOOST_FOREACH(const std::string& name, likeMatch[SearchFilter::Field::Track])
whereClause.Or( WhereClause("t.name LIKE ?") ).bind("%%" + name + "%%");
likeWhereClause.Or( whereClause );
}
sqlQuery.where().And( likeWhereClause );
}
// Add exact search constraints
// Artist
{
WhereClause whereClause;
BOOST_FOREACH(const std::string& name, filter.exactMatch[SearchFilter::Field::Artist])
whereClause.Or( WhereClause("t.artist_name = ?") ).bind(name);
sqlQuery.where().And( whereClause );
}
// Release
{
WhereClause whereClause;
BOOST_FOREACH(const std::string& name, filter.exactMatch[SearchFilter::Field::Release])
whereClause.Or( WhereClause("t.release_name = ?") ).bind(name);
sqlQuery.where().And( whereClause );
}
// Genre
{
WhereClause whereClause;
BOOST_FOREACH(const std::string& name, filter.exactMatch[SearchFilter::Field::Genre])
{
whereClause.Or( WhereClause("g.name = ?") ).bind(name);
genreJoin = true;
}
sqlQuery.where().And( whereClause );
}
if (genreJoin)
{
sqlQuery.innerJoin().And( InnerJoinClause("genre g ON g.id = t_g.genre_id"));
sqlQuery.innerJoin().And( InnerJoinClause("track_genre t_g ON t_g.track_id = t.id"));
}
return sqlQuery;
}
Track::Track(const boost::filesystem::path& p) Track::Track(const boost::filesystem::path& p)
: :
_trackNumber(0), _trackNumber(0),
@@ -148,7 +48,7 @@ Track::setGenres(std::vector<Genre::pointer> genres)
if (_genres.size()) if (_genres.size())
_genres.clear(); _genres.clear();
BOOST_FOREACH(Genre::pointer genre, genres) { for (Genre::pointer genre : genres) {
_genres.insert( genre ); _genres.insert( genre );
} }
} }
@@ -156,7 +56,7 @@ Track::setGenres(std::vector<Genre::pointer> genres)
Track::pointer Track::pointer
Track::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p) Track::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p)
{ {
return session.find<Track>().where("path = ?").bind(p.string()); return session.find<Track>().where("file_path = ?").bind(p.string());
} }
Track::pointer Track::pointer
@@ -165,6 +65,11 @@ Track::getById(Wt::Dbo::Session& session, id_type id)
return session.find<Track>().where("id = ?").bind(id); return session.find<Track>().where("id = ?").bind(id);
} }
Track::pointer
Track::getByMBID(Wt::Dbo::Session& session, const std::string& mbid)
{
return session.find<Track>().where("mbid = ?").bind(mbid);
}
Track::pointer Track::pointer
Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p) Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p)
@@ -181,104 +86,52 @@ Track::getGenres(void) const
} }
Wt::Dbo::Query< Track::pointer > Wt::Dbo::Query< Track::pointer >
Track::getAllQuery(Wt::Dbo::Session& session, SearchFilter filter) Track::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
{ {
SqlQuery sqlQuery = generatePartialQuery(filter); SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<pointer> query Wt::Dbo::Query<pointer> query
= session.query<Track::pointer>( "SELECT t FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.id").orderBy("t.artist_name,t.date,t.release_name,t.disc_number,t.track_number"); = session.query<pointer>( "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");
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
Wt::Dbo::Query< Track::UIQueryResult >
Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<UIQueryResult> query
= session.query<UIQueryResult>( "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");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg); query.bind(bindArg);
return query; return query;
} }
std::vector<Track::pointer> std::vector<Track::pointer>
Track::getAll(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{ {
Wt::Dbo::collection<Track::pointer> res = getAllQuery(session, filter).limit(size).offset(offset); Wt::Dbo::collection<pointer> res = getQuery(session, filter).limit(size).offset(offset);
return std::vector<Track::pointer>(res.begin(), res.end());
}
std::vector<Track::pointer> return std::vector<pointer>(res.begin(), res.end());
Track::getTracks(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
return getAll(session, filter, offset, size);
}
Wt::Dbo::Query<Track::ReleaseResult>
Track::getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<ReleaseResult> query
= session.query<ReleaseResult>("SELECT t.release_name, t.date, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.release_name").orderBy("t.release_name");
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
} }
void void
Track::updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<ReleaseResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames) Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames)
{ {
Wt::Dbo::Query<ReleaseResult> query = getReleasesQuery(session, filter); Wt::Dbo::Query< UIQueryResult > query = getUIQuery(session, filter);
model.setQuery(query, columnNames.empty() ? true : false);
// TODO do something better
if (columnNames.size() == 3)
{
model.addColumn( "t.release_name", columnNames[0]);
model.addColumn( "t.date", columnNames[1]);
model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2] );
}
}
Wt::Dbo::Query<Track::ArtistResult>
Track::getArtistsQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<ArtistResult> query
= session.query<ArtistResult>( "SELECT t.artist_name, COUNT(DISTINCT t.release_name), COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.artist_name").orderBy("t.artist_name");
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
void
Track::updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<ArtistResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames)
{
Wt::Dbo::Query<ArtistResult> query = getArtistsQuery(session, filter);
model.setQuery(query, columnNames.empty() ? true : false);
// TODO do something better
if (columnNames.size() == 3)
{
model.addColumn( "t.artist_name", columnNames.at(0));
model.addColumn( "COUNT(DISTINCT t.release_name)", columnNames.at(1) );
model.addColumn( "COUNT(DISTINCT t.id)", columnNames.at(2) );
}
}
void
Track::updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< pointer >& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames)
{
Wt::Dbo::Query< pointer > query = getAllQuery(session, filter);
model.setQuery(query, columnNames.empty() ? true : false); model.setQuery(query, columnNames.empty() ? true : false);
// TODO do something better // TODO do something better
if (columnNames.size() == 9) if (columnNames.size() == 9)
{ {
model.addColumn( "t.artist_name", columnNames[0] ); model.addColumn( "a.name", columnNames[0] );
model.addColumn( "t.release_name", columnNames[1] ); model.addColumn( "r.name", columnNames[1] );
model.addColumn( "t.disc_number", columnNames[2] ); model.addColumn( "t.disc_number", columnNames[2] );
model.addColumn( "t.track_number", columnNames[3] ); model.addColumn( "t.track_number", columnNames[3] );
model.addColumn( "t.name", columnNames[4] ); model.addColumn( "t.name", columnNames[4] );
@@ -290,48 +143,6 @@ Track::updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< po
} }
std::vector<std::string>
Track::getArtists(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<std::string> query
= session.query<std::string>( "SELECT DISTINCT t.artist_name FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).offset(offset).limit(size).orderBy("t.artist_name");
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs())
query.bind(bindArg);
typedef Wt::Dbo::collection< std::string > ArtistNames;
ArtistNames artistNames(query);
std::vector<std::string> res;
for (ArtistNames::const_iterator it = artistNames.begin(); it != artistNames.end(); ++it)
res.push_back((*it));
return res;
}
std::vector<std::string>
Track::getReleases(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<std::string> query
= session.query<std::string>( "SELECT DISTINCT t.release_name FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).offset(offset).limit(size).orderBy("t.release_name");
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs())
query.bind(bindArg);
typedef Wt::Dbo::collection< std::string > ReleaseNames;
ReleaseNames releaseNames(query);
std::vector<std::string> res;
for (ReleaseNames::const_iterator it = releaseNames.begin(); it != releaseNames.end(); ++it)
res.push_back((*it));
return res;
}
Genre::Genre() Genre::Genre()
{ {
} }
@@ -341,6 +152,12 @@ Genre::Genre(const std::string& name)
{ {
} }
std::vector<Genre::pointer>
Genre::getAll(Wt::Dbo::Session& session, int offset, int size)
{
Wt::Dbo::collection<pointer> res = session.find<Genre>().offset(offset).limit(size);
return std::vector<Genre::pointer>(res.begin(), res.end());
}
Genre::pointer Genre::pointer
Genre::getByName(Wt::Dbo::Session& session, const std::string& name) Genre::getByName(Wt::Dbo::Session& session, const std::string& name)
@@ -370,30 +187,38 @@ Genre::create(Wt::Dbo::Session& session, const std::string& name)
return session.add(new Genre(name)); return session.add(new Genre(name));
} }
Wt::Dbo::collection<Genre::pointer> Wt::Dbo::Query<Genre::pointer>
Genre::getAll(Wt::Dbo::Session& session, int offset, int size) Genre::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
{ {
return session.find<Genre>().offset(offset).limit(size).orderBy("name"); SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<pointer> query
= session.query<pointer>( "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");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
} }
Wt::Dbo::Query<Genre::GenreResult> Wt::Dbo::Query<Genre::UIQueryResult>
Genre::getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter) Genre::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
{ {
SqlQuery sqlQuery = generatePartialQuery(filter, true); SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<Genre::GenreResult> query Wt::Dbo::Query<UIQueryResult> query
= session.query<Genre::GenreResult>( "SELECT g.name, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("g.name").orderBy("g.name"); = session.query<UIQueryResult>( "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");
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg); query.bind(bindArg);
return query; return query;
} }
void void
Genre::updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<Genre::GenreResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames) Genre::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<UIQueryResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames)
{ {
Wt::Dbo::Query<Genre::GenreResult> query = getAllQuery(session, filter); Wt::Dbo::Query<UIQueryResult> query = getUIQuery(session, filter);
model.setQuery(query, columnNames.empty() ? true : false); model.setQuery(query, columnNames.empty() ? true : false);
// TODO do something better // TODO do something better
@@ -404,5 +229,13 @@ Genre::updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<Gen
} }
} }
std::vector<Genre::pointer>
Genre::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
Wt::Dbo::collection<pointer> res = getQuery(session, filter).limit(size).offset(offset);
return std::vector<pointer>(res.begin(), res.end());
}
} // namespace Database } // namespace Database
+53 -50
View File
@@ -32,25 +32,9 @@
namespace Database { namespace Database {
// Find utilities
struct SearchFilter
{
enum class Field {
Artist, // artist name
Release, // release name
Genre, // genre name
Track, // track name
};
typedef std::map<Field, std::vector<std::string> > FieldValues;
// Formulas :
// ((like1-1 LIKE ? OR like1-2 LIKE = ? ...) AND (like2-1 LIKE ? OR like2-2 LIKE ? ...)) AND exact1 = ? AND exact2 = ? ...
std::vector<FieldValues> likeMatches;
FieldValues exactMatch;
};
class Artist;
class Release;
class Track; class Track;
class PlaylistEntry; class PlaylistEntry;
@@ -67,10 +51,14 @@ class Genre
// Find utility // Find utility
static pointer getByName(Wt::Dbo::Session& session, const std::string& name); static pointer getByName(Wt::Dbo::Session& session, const std::string& name);
static pointer getNone(Wt::Dbo::Session& session); static pointer getNone(Wt::Dbo::Session& session);
static Wt::Dbo::collection<pointer> getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
typedef boost::tuple<std::string, int> GenreResult; static std::vector<pointer> getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1);
static Wt::Dbo::Query<GenreResult> getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter);
static void updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<GenreResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>()); // MVC models for the user interface
// Genre ID, name, track count
typedef boost::tuple<id_type, std::string, int> UIQueryResult;
static Wt::Dbo::Query<UIQueryResult> getUIQuery(Wt::Dbo::Session& session, SearchFilter filter);
static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<UIQueryResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>());
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, const std::string& name); static pointer create(Wt::Dbo::Session& session, const std::string& name);
@@ -88,6 +76,8 @@ class Genre
} }
private: private:
static Wt::Dbo::Query<pointer> getQuery(Wt::Dbo::Session& session, SearchFilter filter);
static const std::size_t _maxNameLength = 128; static const std::size_t _maxNameLength = 128;
std::string _name; std::string _name;
@@ -114,22 +104,28 @@ class Track
// Find utility functions // Find utility functions
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p); static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getById(Wt::Dbo::Session& session, id_type id);
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session); static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
// Used for remote // Used for remote
static std::vector<pointer> getAll(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static std::vector<pointer> getTracks(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static std::vector<std::string> getReleases(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static std::vector<std::string> getArtists(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
// Utility fonctions // Utility fonctions
// MVC models for the user interface // MVC models for the user interface
static void updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< pointer >& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>()); // ID, Artist name, Release Name, DiscNumber, TrackNumber, Name, duration, date, original date, genre list
// Release name, year, track counts typedef boost::tuple<id_type, //ID
typedef boost::tuple<std::string, boost::posix_time::ptime, int> ReleaseResult; std::string, // Artist name
static void updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<ReleaseResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>()); std::string, // Release Name
// Artist name, albums, tracks int, // Disc Number
typedef boost::tuple<std::string, int, int> ArtistResult; int, // Track Number
static void updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<ArtistResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>()); std::string, // Name
boost::posix_time::time_duration, // Duration
boost::posix_time::ptime, // Date
boost::posix_time::ptime, // Original date
std::string> // genre list
UIQueryResult;
static Wt::Dbo::Query< UIQueryResult > getUIQuery(Wt::Dbo::Session& session, SearchFilter filter);
static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>());
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p); static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p);
@@ -138,32 +134,35 @@ class Track
void setTrackNumber(int num) { _trackNumber = num; } void setTrackNumber(int num) { _trackNumber = num; }
void setDiscNumber(int num) { _discNumber = num; } void setDiscNumber(int num) { _discNumber = num; }
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); } 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 setDuration(boost::posix_time::time_duration duration) { _duration = duration; }
void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; } void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; }
void setAddedTime(boost::posix_time::ptime time) { _fileAdded = time; }
void setChecksum(const std::vector<unsigned char>& checksum) { _fileChecksum = checksum; } void setChecksum(const std::vector<unsigned char>& checksum) { _fileChecksum = checksum; }
void setDate(const boost::posix_time::ptime& date) { _date = date; } void setDate(const boost::posix_time::ptime& date) { _date = date; }
void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; } void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; }
void setGenres(const std::string& genreList) { _genreList = genreList; } void setGenres(const std::string& genreList) { _genreList = genreList; }
void setGenres(std::vector<Genre::pointer> genres);
void setCoverType(CoverType coverType) { _coverType = coverType; } void setCoverType(CoverType coverType) { _coverType = coverType; }
void setMBID(const std::string& MBID) { _MBID = MBID; }
void setArtist(Wt::Dbo::ptr<Artist> artist) { _artist = artist; }
void setRelease(Wt::Dbo::ptr<Release> release) { _release = release; }
void setGenres(std::vector<Genre::pointer> genres);
int getTrackNumber(void) const { return _trackNumber; } int getTrackNumber(void) const { return _trackNumber; }
int getDiscNumber(void) const { return _discNumber; } int getDiscNumber(void) const { return _discNumber; }
std::string getName(void) const { return _name; } std::string getName(void) const { return _name; }
std::string getArtistName(void) const { return _artistName; }
std::string getReleaseName(void) const { return _releaseName; }
boost::filesystem::path getPath(void) const { return _filePath; } boost::filesystem::path getPath(void) const { return _filePath; }
boost::posix_time::time_duration getDuration(void) const { return _duration; } boost::posix_time::time_duration getDuration(void) const { return _duration; }
boost::posix_time::ptime getDate(void) const { return _date; } boost::posix_time::ptime getDate(void) const { return _date; }
boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; } boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; }
bool hasGenre(Genre::pointer genre) const { return _genres.count(genre); }
std::vector< Genre::pointer > getGenres(void) const;
boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; } boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; }
boost::posix_time::ptime getAddedTime(void) const { return _fileAdded; }
const std::vector<unsigned char>& getChecksum(void) const { return _fileChecksum; } const std::vector<unsigned char>& getChecksum(void) const { return _fileChecksum; }
CoverType getCoverType(void) const { return _coverType; } CoverType getCoverType(void) const { return _coverType; }
const std::string& getMBID(void) const { return _MBID; }
Wt::Dbo::ptr<Artist> getArtist(void) const { return _artist; }
Wt::Dbo::ptr<Release> getRelease(void) const { return _release; }
std::vector< Genre::pointer > getGenres(void) const;
bool hasGenre(Genre::pointer genre) const { return _genres.count(genre); }
template<class Action> template<class Action>
void persist(Action& a) void persist(Action& a)
@@ -171,25 +170,25 @@ class Track
Wt::Dbo::field(a, _trackNumber, "track_number"); Wt::Dbo::field(a, _trackNumber, "track_number");
Wt::Dbo::field(a, _discNumber, "disc_number"); Wt::Dbo::field(a, _discNumber, "disc_number");
Wt::Dbo::field(a, _name, "name"); 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, _duration, "duration");
Wt::Dbo::field(a, _date, "date"); Wt::Dbo::field(a, _date, "date");
Wt::Dbo::field(a, _originalDate, "original_date"); Wt::Dbo::field(a, _originalDate, "original_date");
Wt::Dbo::field(a, _genreList, "genre_list"); Wt::Dbo::field(a, _genreList, "genre_list");
Wt::Dbo::field(a, _filePath, "path"); Wt::Dbo::field(a, _filePath, "file_path");
Wt::Dbo::field(a, _fileLastWrite, "last_write"); Wt::Dbo::field(a, _fileLastWrite, "file_last_write");
Wt::Dbo::field(a, _fileAdded, "file_added");
Wt::Dbo::field(a, _fileChecksum, "checksum"); Wt::Dbo::field(a, _fileChecksum, "checksum");
Wt::Dbo::field(a, _coverType, "cover_type"); Wt::Dbo::field(a, _coverType, "cover_type");
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, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track"); Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track");
} }
private: private:
static Wt::Dbo::Query< pointer > getAllQuery(Wt::Dbo::Session& session, SearchFilter filter); static Wt::Dbo::Query< pointer > getQuery(Wt::Dbo::Session& session, SearchFilter filter);
static Wt::Dbo::Query<ReleaseResult> getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter);
static Wt::Dbo::Query<ArtistResult> getArtistsQuery(Wt::Dbo::Session& session, SearchFilter filter);
static const std::size_t _maxNameLength = 128; static const std::size_t _maxNameLength = 128;
@@ -200,14 +199,18 @@ class Track
std::string _releaseName; std::string _releaseName;
boost::posix_time::time_duration _duration; boost::posix_time::time_duration _duration;
boost::posix_time::ptime _date; boost::posix_time::ptime _date;
boost::posix_time::ptime _originalDate; boost::posix_time::ptime _originalDate; // original date time
std::string _genreList; std::string _genreList;
std::string _filePath; std::string _filePath;
std::vector<unsigned char> _fileChecksum; std::vector<unsigned char> _fileChecksum;
boost::posix_time::ptime _fileLastWrite; boost::posix_time::ptime _fileLastWrite;
boost::posix_time::ptime _fileAdded;
CoverType _coverType; CoverType _coverType;
std::string _MBID; // Musicbrainz Identifier
Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track Wt::Dbo::ptr<Artist> _artist;
Wt::Dbo::ptr<Release> _release;
Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track
Wt::Dbo::collection< Wt::Dbo::ptr<PlaylistEntry> > _playlistEntries; Wt::Dbo::collection< Wt::Dbo::ptr<PlaylistEntry> > _playlistEntries;
}; };
+2
View File
@@ -19,8 +19,10 @@
// header file aimed to ease database class declarations // header file aimed to ease database class declarations
#include "Artist.hpp"
#include "Track.hpp" #include "Track.hpp"
#include "Playlist.hpp" #include "Playlist.hpp"
#include "Release.hpp"
#include "Video.hpp" #include "Video.hpp"
#include "MediaDirectory.hpp" #include "MediaDirectory.hpp"
#include "User.hpp" #include "User.hpp"
+8
View File
@@ -161,6 +161,14 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items)
items.insert( std::make_pair(MetaData::Type::Genres, genres)); items.insert( std::make_pair(MetaData::Type::Genres, genres));
} }
else if (boost::iequals(it->first, "MusicBrainz Artist Id"))
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, string_trim( string_to_utf8(it->second)) ));
}
else if (boost::iequals(it->first, "MusicBrainz Album Id"))
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, string_trim( string_to_utf8(it->second)) ));
}
/* else /* else
LMS_LOG(MOD_METADATA, SEV_DEBUG) << "key = " << it->first << ", value = " << it->second; LMS_LOG(MOD_METADATA, SEV_DEBUG) << "key = " << it->first << ", value = " << it->second;
*/ */
+2
View File
@@ -43,6 +43,8 @@ namespace MetaData
AudioStreams, // vector<AudioStream> AudioStreams, // vector<AudioStream>
VideoStreams, // vector<VideoStream> VideoStreams, // vector<VideoStream>
SubtitleStreams, // vector<SubtitleStream> SubtitleStreams, // vector<SubtitleStream>
MusicBrainzArtistID, // string
MusicBrainzAlbumID, // string
}; };
// Used by Streams // Used by Streams
+34 -22
View File
@@ -5,6 +5,14 @@ package Remote;
message AudioCollectionRequest message AudioCollectionRequest
{ {
message SearchFilter
{
repeated uint64 artist_id = 1; // has one of these artists
repeated uint64 genre_id = 2; // has one of these genres
repeated uint64 release_id = 3; // hase one of these releases
repeated uint64 track_id = 4; // has one of theses tracks
}
message BatchParameter message BatchParameter
{ {
required uint32 offset = 1; // First element requested required uint32 offset = 1; // First element requested
@@ -14,30 +22,25 @@ message AudioCollectionRequest
message GetGenreList message GetGenreList
{ {
required BatchParameter batch_parameter = 1; required BatchParameter batch_parameter = 1;
optional SearchFilter search_filter = 2;
} }
message GetArtistList message GetArtistList
{ {
required BatchParameter batch_parameter = 1; required BatchParameter batch_parameter = 1;
// Search filters optional SearchFilter search_filter = 2;
repeated string genre = 2; // Artist that has at least a track of the genre
} }
message GetReleaseList message GetReleaseList
{ {
required BatchParameter batch_parameter = 1; required BatchParameter batch_parameter = 1;
// Search filters optional SearchFilter search_filter = 2;
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 message GetTrackList
{ {
required BatchParameter batch_parameter = 1; required BatchParameter batch_parameter = 1;
// Search filters optional SearchFilter search_filter = 2;
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 message GetCoverArt
@@ -50,7 +53,7 @@ message AudioCollectionRequest
required Type type = 1; required Type type = 1;
optional string release = 2; // Release that owns the cover art optional uint64 release_id = 2; // Release that owns the cover art
optional uint64 track_id = 3; // Track 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 required uint32 size = 4; // Scale image to size*size pixels. Set 0 to get the biggest image
@@ -112,33 +115,42 @@ message AudioCollectionResponse
message Genre message Genre
{ {
required string name = 1; required uint64 id = 1;
optional string mbid = 2;
required string name = 3;
} }
message Artist message Artist
{ {
required string name = 1; required uint64 id = 1;
optional string mbid = 2;
required string name = 3;
} }
message Release message Release
{ {
required string name = 1; required uint64 id = 1;
optional string mbid = 2;
required string name = 3;
} }
message Track message Track
{ {
required uint64 id = 1; // Track id required uint64 id = 1; // Track id
optional string mbid = 2;
required string artist = 2; required uint64 artist_id = 3;
required string release = 3; required uint64 release_id = 4;
repeated string genre = 4; repeated uint64 genre_id = 5;
optional uint32 disc_number = 5; optional string artist_mbid = 6;
optional uint32 track_number = 6; optional string release_mbid = 7;
required string name = 7; optional uint32 disc_number = 8;
required uint32 duration_secs = 8; optional uint32 track_number = 9;
optional string release_date = 9; required string name = 10;
optional string original_release_date = 10; required uint32 duration_secs = 11;
optional string release_date = 12;
optional string original_release_date = 13;
} }
enum Type { enum Type {
@@ -21,7 +21,6 @@
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <boost/uuid/sha1.hpp> #include <boost/uuid/sha1.hpp>
#include <boost/foreach.hpp>
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
@@ -35,11 +34,29 @@ namespace Server {
using namespace Database; using namespace Database;
AudioCollectionRequestHandler::AudioCollectionRequestHandler(Database::Handler& db) static SearchFilter SearchFilterFromRequest(const AudioCollectionRequest_SearchFilter& request)
{
SearchFilter filter;
for (int id = 0; id < request.artist_id_size(); ++id)
filter.idMatch[SearchFilter::Field::Artist].push_back( request.artist_id(id) );
for (int id = 0; id < request.genre_id_size(); ++id)
filter.idMatch[SearchFilter::Field::Genre].push_back( request.genre_id(id) );
for (int id = 0; id < request.release_id_size(); ++id)
filter.idMatch[SearchFilter::Field::Release].push_back( request.release_id(id) );
for (int id = 0; id < request.track_id_size(); ++id)
filter.idMatch[SearchFilter::Field::Track].push_back( request.track_id(id) );
return filter;
}
AudioCollectionRequestHandler::AudioCollectionRequestHandler(Handler& db)
: _db(db) : _db(db)
{} {}
bool bool
AudioCollectionRequestHandler::process(const AudioCollectionRequest& request, AudioCollectionResponse& response) AudioCollectionRequestHandler::process(const AudioCollectionRequest& request, AudioCollectionResponse& response)
{ {
@@ -133,17 +150,21 @@ AudioCollectionRequestHandler::processGetGenres(const AudioCollectionRequest::Ge
size = _maxListArtists; size = _maxListArtists;
size = std::min(size, _maxListArtists); size = std::min(size, _maxListArtists);
// Get filters
SearchFilter filter;
if (request.has_search_filter())
filter = SearchFilterFromRequest(request.search_filter());
Wt::Dbo::Transaction transaction( _db.getSession() ); Wt::Dbo::Transaction transaction( _db.getSession() );
Wt::Dbo::collection<Database::Genre::pointer> genres = Database::Genre::getAll( _db.getSession(), request.batch_parameter().offset(), static_cast<int>(size)); std::vector<Genre::pointer> genres = Genre::getByFilter( _db.getSession(), filter, request.batch_parameter().offset(), static_cast<int>(size));
typedef Wt::Dbo::collection< Database::Genre::pointer > Genres; for (Genre::pointer genre : genres)
for (Genres::const_iterator it = genres.begin(); it != genres.end(); ++it)
{ {
AudioCollectionResponse_Genre* genre = response.add_genres(); AudioCollectionResponse_Genre* addGenre = response.add_genres();
genre->set_name( std::string( boost::locale::conv::to_utf<char>((*it)->getName(), "UTF-8") ) ); addGenre->set_id(genre.id());
addGenre->set_name( std::string( boost::locale::conv::to_utf<char>(genre->getName(), "UTF-8") ) );
} }
return true; return true;
@@ -164,23 +185,28 @@ AudioCollectionRequestHandler::processGetArtists(const AudioCollectionRequest::G
size = _maxListArtists; size = _maxListArtists;
size = std::min(size, _maxListArtists); size = std::min(size, _maxListArtists);
SearchFilter filter;
// Get filters // Get filters
std::vector<std::string> genres; SearchFilter filter;
for (int id = 0; id < request.genre_size(); ++id) if (request.has_search_filter())
filter.exactMatch[SearchFilter::Field::Genre].push_back( request.genre(id) ); filter = SearchFilterFromRequest(request.search_filter());
// Now fetch requested data... // Now fetch requested data...
Wt::Dbo::Transaction transaction( _db.getSession() ); Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<std::string> artists std::vector<Artist::pointer> artists
= Database::Track::getArtists(_db.getSession(), filter, = Artist::getByFilter(_db.getSession(), filter,
request.batch_parameter().offset(), static_cast<int>(size) ); request.batch_parameter().offset(), static_cast<int>(size) );
BOOST_FOREACH(const std::string& artist, artists) for (Artist::pointer artist : artists)
response.add_artists()->set_name( std::string( boost::locale::conv::to_utf<char>(artist, "UTF-8") ) ); {
AudioCollectionResponse_Artist *addArtist = response.add_artists();
addArtist->set_id(artist.id());
addArtist->set_name( std::string( boost::locale::conv::to_utf<char>(artist->getName(), "UTF-8") ) );
if (!artist->getMBID().empty())
addArtist->set_mbid(artist->getMBID());
}
return true; return true;
} }
@@ -200,25 +226,25 @@ AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest::
size = _maxListReleases; size = _maxListReleases;
size = std::min(size, _maxListReleases); size = std::min(size, _maxListReleases);
// Get filters
SearchFilter filter; SearchFilter filter;
if (request.has_search_filter())
std::vector<std::string> artists; filter = SearchFilterFromRequest(request.search_filter());
for (int id = 0; id < request.artist_size(); ++id)
filter.exactMatch[SearchFilter::Field::Artist].push_back( request.artist(id) );
std::vector<std::string> genres;
for (int id = 0; id < request.genre_size(); ++id)
filter.exactMatch[SearchFilter::Field::Genre].push_back( request.genre(id) );
Wt::Dbo::Transaction transaction( _db.getSession() ); Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<std::string> releases std::vector<Release::pointer> releases
= Database::Track::getReleases( _db.getSession(), filter, = Release::getByFilter( _db.getSession(), filter,
request.batch_parameter().offset(), static_cast<int>(size)); request.batch_parameter().offset(), static_cast<int>(size));
BOOST_FOREACH(const std::string& release, releases) for (Release::pointer release : releases)
{ {
response.add_releases()->set_name( std::string( boost::locale::conv::to_utf<char>(release, "UTF-8"))); AudioCollectionResponse_Release *addRelease = response.add_releases();
addRelease->set_id(release.id());
addRelease->set_name( std::string( boost::locale::conv::to_utf<char>(release->getName(), "UTF-8")));
if (!release->getMBID().empty())
addRelease->set_mbid(release->getMBID());
} }
return true; return true;
@@ -241,34 +267,24 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge
// Get filters // Get filters
SearchFilter filter; SearchFilter filter;
if (request.has_search_filter())
std::vector<std::string> artists; filter = SearchFilterFromRequest(request.search_filter());
for (int id = 0; id < request.artist_size(); ++id)
filter.exactMatch[SearchFilter::Field::Artist].push_back( request.artist(id) );
std::vector<std::string> releases;
for (int id = 0; id < request.release_size(); ++id)
filter.exactMatch[SearchFilter::Field::Release].push_back( request.release(id) );
std::vector<std::string> genres;
for (int id = 0; id < request.genre_size(); ++id)
filter.exactMatch[SearchFilter::Field::Genre].push_back( request.genre(id) );
Wt::Dbo::Transaction transaction( _db.getSession() ); Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<Database::Track::pointer> tracks std::vector<Track::pointer> tracks
= Database::Track::getAll( _db.getSession(), filter, = Track::getByFilter( _db.getSession(), filter,
request.batch_parameter().offset(), static_cast<int>(size)); request.batch_parameter().offset(), static_cast<int>(size));
BOOST_FOREACH(Database::Track::pointer track, tracks) for (Track::pointer track : tracks)
{ {
AudioCollectionResponse_Track* newTrack = response.add_tracks(); AudioCollectionResponse_Track* newTrack = response.add_tracks();
newTrack->set_id(track.id()); newTrack->set_id(track.id());
newTrack->set_disc_number( track->getDiscNumber() ); newTrack->set_disc_number( track->getDiscNumber() );
newTrack->set_track_number( track->getTrackNumber() ); newTrack->set_track_number( track->getTrackNumber() );
newTrack->set_artist( track->getArtistName() ); newTrack->set_artist_id( track->getArtist().id() );
newTrack->set_release( track->getReleaseName() ); newTrack->set_release_id( track->getRelease().id() );
newTrack->set_name( std::string( boost::locale::conv::to_utf<char>(track->getName(), "UTF-8") ) ); newTrack->set_name( std::string( boost::locale::conv::to_utf<char>(track->getName(), "UTF-8") ) );
newTrack->set_duration_secs( track->getDuration().total_seconds() ); newTrack->set_duration_secs( track->getDuration().total_seconds() );
@@ -278,9 +294,11 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge
newTrack->set_release_date( std::to_string(track->getDate().date().year()) ); newTrack->set_release_date( std::to_string(track->getDate().date().year()) );
if (!track->getOriginalDate().is_special()) if (!track->getOriginalDate().is_special())
newTrack->set_original_release_date( std::to_string(track->getOriginalDate().date().year()) ); newTrack->set_original_release_date( std::to_string(track->getOriginalDate().date().year()) );
if (!track->getMBID().empty())
newTrack->set_mbid(track->getMBID());
BOOST_FOREACH(Database::Genre::pointer genre, track->getGenres()) for (Genre::pointer genre : track->getGenres())
newTrack->add_genre( genre->getName() ); newTrack->add_genre_id( genre.id() );
} }
@@ -299,9 +317,9 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::
switch(request.type()) switch(request.type())
{ {
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease: case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease:
if (request.has_release()) if (request.has_release_id())
{ {
coverArts = CoverArt::Grabber::instance().getFromRelease( _db.getSession(), request.release()); coverArts = CoverArt::Grabber::instance().getFromRelease( _db.getSession(), request.release_id());
res = true; res = true;
} }
break; break;
@@ -315,7 +333,7 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::
break; break;
} }
BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts) for (CoverArt::CoverArt& coverArt : coverArts)
{ {
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art(); AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
@@ -345,12 +363,12 @@ AudioCollectionRequestHandler::processGetRevision(AudioCollectionResponse::Revis
Wt::Dbo::Transaction transaction( _db.getSession() ); Wt::Dbo::Transaction transaction( _db.getSession() );
Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get( _db.getSession() ); MediaDirectorySettings::pointer settings = MediaDirectorySettings::get( _db.getSession() );
std::string hashStr = boost::posix_time::to_iso_string(settings->getLastUpdated()); std::string hashStr = boost::posix_time::to_iso_string(settings->getLastUpdated());
boost::uuids::detail::sha1 s; boost::uuids::detail::sha1 s;
BOOST_FOREACH(const char c, hashStr) for (const char c : hashStr)
s.process_byte(c); s.process_byte(c);
unsigned int digest[5]; unsigned int digest[5];
+1 -1
View File
@@ -199,7 +199,7 @@ AudioMediaPlayer::load(Database::Track::id_type trackId)
bitrate = CurrentUser()->getAudioBitrate(); bitrate = CurrentUser()->getAudioBitrate();
trackPath = track->getPath(); trackPath = track->getPath();
_mediaTitle->setText ( Wt::WString::fromUTF8(track->getName()) ); _mediaTitle->setText ( Wt::WString::fromUTF8(track->getName()) );
_mediaArtistRelease->setText ( Wt::WString::fromUTF8(track->getArtistName()) + " - " + Wt::WString::fromUTF8(track->getReleaseName()) ); _mediaArtistRelease->setText ( Wt::WString::fromUTF8(track->getArtist()->getName()) + " - " + Wt::WString::fromUTF8(track->getRelease()->getName()) );
_mediaCover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 72))); _mediaCover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 72)));
} }
+23 -16
View File
@@ -52,6 +52,8 @@ void WPopupMenuClear(Wt::WPopupMenu* menu)
namespace UserInterface { namespace UserInterface {
namespace Desktop { namespace Desktop {
using namespace Database;
// Special playlist generated each time the playque gets changed // Special playlist generated each time the playque gets changed
// Restored at the beginning of the session // Restored at the beginning of the session
static const std::string CurrentQueuePlaylistName = "__current__"; static const std::string CurrentQueuePlaylistName = "__current__";
@@ -269,7 +271,7 @@ Audio::playlistShowSaveDialog(std::string playlistName)
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
// Actually create the dialog only if the given list already exists // Actually create the dialog only if the given list already exists
if (Database::Playlist::get(DboSession(), playlistName, CurrentUser())) if (Playlist::get(DboSession(), playlistName, CurrentUser()))
{ {
Wt::WMessageBox *messageBox = new Wt::WMessageBox Wt::WMessageBox *messageBox = new Wt::WMessageBox
("Overwrite playlist", ("Overwrite playlist",
@@ -301,25 +303,25 @@ Audio::playlistSaveFromPlayqueue(std::string playlistName)
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), playlistName, CurrentUser()); Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser());
if (playlist) if (playlist)
{ {
LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'"; LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'";
playlist.remove(); playlist.remove();
} }
playlist = Database::Playlist::create(DboSession(), playlistName, false, CurrentUser()); playlist = Playlist::create(DboSession(), playlistName, false, CurrentUser());
std::vector<Database::Track::id_type> trackIds; std::vector<Track::id_type> trackIds;
_playQueue->getTracks(trackIds); _playQueue->getTracks(trackIds);
int pos = 0; int pos = 0;
BOOST_FOREACH(Database::Track::id_type trackId, trackIds) for (Track::id_type trackId : trackIds)
{ {
Database::Track::pointer track = Database::Track::getById(DboSession(), trackId); Track::pointer track = Track::getById(DboSession(), trackId);
if (track) if (track)
Database::PlaylistEntry::create(DboSession(), track, playlist, pos++); PlaylistEntry::create(DboSession(), track, playlist, pos++);
} }
LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries"; LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries";
@@ -330,16 +332,16 @@ Audio::playlistLoadToPlayqueue(std::string playlistName)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue"; LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue";
std::vector<Database::Track::id_type> entries; std::vector<Track::id_type> entries;
{ {
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), playlistName, CurrentUser()); Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser());
if (!playlist) if (!playlist)
return; return;
entries = Database::PlaylistEntry::getEntries(DboSession(), playlist); entries = PlaylistEntry::getEntries(DboSession(), playlist);
} }
_playQueue->clear(); _playQueue->clear();
@@ -365,7 +367,7 @@ Audio::playlistShowDeleteDialog(std::string name)
{ {
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), name, CurrentUser()); Playlist::pointer playlist = Playlist::get(DboSession(), name, CurrentUser());
if (playlist) if (playlist)
playlist.remove(); playlist.remove();
@@ -396,9 +398,9 @@ Audio::playlistRefreshMenus()
})); }));
_popupMenuSave->addSeparator(); _popupMenuSave->addSeparator();
std::vector<Database::Playlist::pointer> playlists = Database::Playlist::get(DboSession(), CurrentUser()); std::vector<Playlist::pointer> playlists = Playlist::get(DboSession(), CurrentUser());
BOOST_FOREACH(Database::Playlist::pointer playlist, playlists) for (Playlist::pointer playlist : playlists)
{ {
if (playlist->getName() == CurrentQueuePlaylistName) if (playlist->getName() == CurrentQueuePlaylistName)
continue; continue;
@@ -431,7 +433,7 @@ Audio::search(std::string searchText)
void void
Audio::addSelectedTracks(void) Audio::addSelectedTracks(void)
{ {
std::vector<Database::Track::id_type> trackIds; std::vector<Track::id_type> trackIds;
_trackView->getSelectedTracks(trackIds); _trackView->getSelectedTracks(trackIds);
// If nothing is selected, get the whole track list // If nothing is selected, get the whole track list
@@ -444,7 +446,9 @@ Audio::addSelectedTracks(void)
void void
Audio::playSelectedTracks(PlayQueueAddType addType) Audio::playSelectedTracks(PlayQueueAddType addType)
{ {
std::vector<Database::Track::id_type> trackIds; std::vector<Track::id_type> trackIds;
LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing selected tracks... nb selected = " << _trackView->getNbSelectedTracks() << ", add type = " << (addType == PlayQueueAddAllTracks ? "AddAll" : "AddSelected");
_playQueue->clear(); _playQueue->clear();
@@ -458,6 +462,9 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
break; break;
case PlayQueueAddSelectedTracks: case PlayQueueAddSelectedTracks:
LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding selected tracks...";
// If nothing selected, get all the track and play everything // If nothing selected, get all the track and play everything
if (_trackView->getNbSelectedTracks() == 0) if (_trackView->getNbSelectedTracks() == 0)
{ {
@@ -485,7 +492,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
} }
void void
Audio::playTrack(Database::Track::id_type trackId, int pos) Audio::playTrack(Track::id_type trackId, int pos)
{ {
{ {
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
+5 -9
View File
@@ -47,16 +47,12 @@ KeywordSearchFilter::getConstraint(Database::SearchFilter& filter)
boost::algorithm::split(values, _lastEmittedText, boost::is_any_of(" "), boost::token_compress_on); boost::algorithm::split(values, _lastEmittedText, boost::is_any_of(" "), boost::token_compress_on);
// For each part, do a global search on all searchable fields // For each part, do a global search on all searchable fields
BOOST_FOREACH(std::string value, values) for (const std::string& value : values)
{ {
Database::SearchFilter::FieldValues likeMatch; filter.nameLikeMatch[Database::SearchFilter::Field::Artist].push_back(value);
filter.nameLikeMatch[Database::SearchFilter::Field::Release].push_back(value);
likeMatch[Database::SearchFilter::Field::Artist].push_back(value); filter.nameLikeMatch[Database::SearchFilter::Field::Genre].push_back(value);
likeMatch[Database::SearchFilter::Field::Release].push_back(value); filter.nameLikeMatch[Database::SearchFilter::Field::Track].push_back(value);
likeMatch[Database::SearchFilter::Field::Genre].push_back(value);
likeMatch[Database::SearchFilter::Field::Track].push_back(value);
filter.likeMatches.push_back(likeMatch);
} }
} }
} }
+22 -16
View File
@@ -366,12 +366,16 @@ PlayQueue::select(int rowId)
void void
PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds) PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
{ {
using namespace Database;
LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding " << trackIds.size() << " tracks to play queue";
// Add tracks to model // Add tracks to model
// for (Track::id_type trackId : trackIds)
BOOST_FOREACH(Database::Track::id_type trackId, trackIds)
{ {
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
Database::Track::pointer track (Database::Track::getById(DboSession(), trackId));
Track::pointer track (Track::getById(DboSession(), trackId));
if (track) if (track)
{ {
@@ -382,7 +386,7 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
_model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole); _model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole);
std::string coverUrl; std::string coverUrl;
if (track->getCoverType() != Database::Track::CoverType::None) if (track->getCoverType() != Track::CoverType::None)
coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64); coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
else else
coverUrl = LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(64); coverUrl = LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(64);
@@ -392,8 +396,8 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
TrackInfo trackInfo; TrackInfo trackInfo;
trackInfo.track = Wt::WString::fromUTF8(track->getName()); trackInfo.track = Wt::WString::fromUTF8(track->getName());
trackInfo.artist = Wt::WString::fromUTF8(track->getArtistName()); trackInfo.artist = Wt::WString::fromUTF8(track->getArtist()->getName());
trackInfo.release = Wt::WString::fromUTF8(track->getReleaseName()); trackInfo.release = Wt::WString::fromUTF8(track->getRelease()->getName());
_model->setData(dataRow, COLUMN_ID_NAME, trackInfo, TrackInfoRole); _model->setData(dataRow, COLUMN_ID_NAME, trackInfo, TrackInfoRole);
} }
} }
@@ -455,19 +459,21 @@ PlayQueue::playPrevious(void)
} }
} }
bool void
PlayQueue::readTrack(int rowPos) PlayQueue::readTrack(int rowPos)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos; LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount();
Database::Track::id_type trackId = boost::any_cast<Database::Track::id_type>(_model->data(rowPos, COLUMN_ID_TRACK_ID, Wt::UserRole));
setPlayingTrackPos(rowPos); if (rowPos < _model->rowCount())
{
Database::Track::id_type trackId = boost::any_cast<Database::Track::id_type>(_model->data(rowPos, COLUMN_ID_TRACK_ID, Wt::UserRole));
_sigTrackPlay.emit(trackId, rowPos); setPlayingTrackPos(rowPos);
this->scrollTo( _model->index(_trackSelector->getCurrent(), 0)); _sigTrackPlay.emit(trackId, rowPos);
return true; this->scrollTo( _model->index(_trackSelector->getCurrent(), 0));
}
} }
void void
@@ -485,7 +491,7 @@ PlayQueue::setPlayingTrackPos(int newRowPos)
if (newRowPos >= 0) if (newRowPos >= 0)
{ {
_model->setData(newRowPos, COLUMN_ID_NAME, "playqueue-playing", Wt::StyleClassRole); _model->setData(newRowPos, COLUMN_ID_NAME, std::string("playqueue-playing"), Wt::StyleClassRole);
} }
} }
@@ -527,7 +533,7 @@ PlayQueue::moveSelectedUp(void)
Wt::WModelIndexSet newIndexSet; Wt::WModelIndexSet newIndexSet;
// ordered from up to down // ordered from up to down
BOOST_FOREACH(Wt::WModelIndex index, indexSet) for (Wt::WModelIndex index : indexSet)
{ {
// Do nothing if the first selected index is on the top // Do nothing if the first selected index is on the top
if (index.row() == 0) if (index.row() == 0)
@@ -570,7 +576,7 @@ PlayQueue::moveSelectedDown(void)
Wt::WModelIndexSet newIndexSet; Wt::WModelIndexSet newIndexSet;
// ordered from up to down // ordered from up to down
BOOST_REVERSE_FOREACH(Wt::WModelIndex index, indexSet) for (Wt::WModelIndex index : indexSet)
{ {
// Do nothing if the last selected index is the last one // Do nothing if the last selected index is the last one
if (index.row() == _model->rowCount() - 1) if (index.row() == _model->rowCount() - 1)
+1 -1
View File
@@ -72,7 +72,7 @@ class PlayQueue : public Wt::WTableView
void layoutSizeChanged (int width, int height); void layoutSizeChanged (int width, int height);
bool readTrack(int rowId); void readTrack(int rowId);
void setPlayingTrackPos(int newRowPos); void setPlayingTrackPos(int newRowPos);
void renumber(int firstId, int lastId); void renumber(int firstId, int lastId);
+17 -16
View File
@@ -39,7 +39,7 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent)
SearchFilter filter; SearchFilter filter;
Genre::updateGenreQueryModel(DboSession(), _queryModel, filter, columnNames); Genre::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
this->setSelectionMode(Wt::ExtendedSelection); this->setSelectionMode(Wt::ExtendedSelection);
this->setSortingEnabled(true); this->setSortingEnabled(true);
@@ -82,7 +82,7 @@ TableFilterGenre::layoutSizeChanged (int width, int height)
void void
TableFilterGenre::refresh(SearchFilter& filter) TableFilterGenre::refresh(SearchFilter& filter)
{ {
Genre::updateGenreQueryModel(DboSession(), _queryModel, filter); Genre::updateUIQueryModel(DboSession(), _queryModel, filter);
} }
// Get constraint created by this filter // Get constraint created by this filter
@@ -91,14 +91,15 @@ TableFilterGenre::getConstraint(SearchFilter& filter)
{ {
Wt::WModelIndexSet indexSet = this->selectedIndexes(); Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet) { for (Wt::WModelIndex index : indexSet)
{
if (!index.isValid()) if (!index.isValid())
continue; continue;
std::string name = _queryModel.resultRow( index.row() ).get<0>(); // TODO set invisible track id
Database::Genre::id_type id = _queryModel.resultRow( index.row() ).get<0>();
filter.exactMatch[Database::SearchFilter::Field::Genre].push_back(name); filter.idMatch[Database::SearchFilter::Field::Genre].push_back(id);
} }
} }
@@ -109,7 +110,7 @@ TableFilterArtist::TableFilterArtist(Wt::WContainerWidget* parent)
SearchFilter filter; SearchFilter filter;
Track::updateArtistQueryModel(DboSession(), _queryModel, filter, columnNames); Artist::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
this->setSelectionMode(Wt::ExtendedSelection); this->setSelectionMode(Wt::ExtendedSelection);
this->setSortingEnabled(true); this->setSortingEnabled(true);
@@ -153,7 +154,7 @@ TableFilterArtist::layoutSizeChanged (int width, int height)
void void
TableFilterArtist::refresh(SearchFilter& filter) TableFilterArtist::refresh(SearchFilter& filter)
{ {
Track::updateArtistQueryModel(DboSession(), _queryModel, filter); Artist::updateUIQueryModel(DboSession(), _queryModel, filter);
} }
// Get constraint created by this filter // Get constraint created by this filter
@@ -167,9 +168,9 @@ TableFilterArtist::getConstraint(SearchFilter& filter)
if (!index.isValid()) if (!index.isValid())
continue; continue;
std::string name = _queryModel.resultRow( index.row() ).get<0>(); Artist::id_type id = _queryModel.resultRow( index.row() ).get<0>();
filter.exactMatch[Database::SearchFilter::Field::Artist].push_back(name); filter.idMatch[Database::SearchFilter::Field::Artist].push_back(id);
} }
} }
@@ -180,7 +181,7 @@ TableFilterRelease::TableFilterRelease(Wt::WContainerWidget* parent)
SearchFilter filter; SearchFilter filter;
Track::updateReleaseQueryModel(DboSession(), _queryModel, filter, columnNames); Release::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
this->setSelectionMode(Wt::ExtendedSelection); this->setSelectionMode(Wt::ExtendedSelection);
this->setSortingEnabled(true); this->setSortingEnabled(true);
@@ -231,7 +232,7 @@ TableFilterRelease::layoutSizeChanged (int width, int height)
void void
TableFilterRelease::refresh(SearchFilter& filter) TableFilterRelease::refresh(SearchFilter& filter)
{ {
Track::updateReleaseQueryModel(DboSession(), _queryModel, filter); Release::updateUIQueryModel(DboSession(), _queryModel, filter);
} }
// Get constraint created by this filter // Get constraint created by this filter
@@ -240,14 +241,14 @@ TableFilterRelease::getConstraint(SearchFilter& filter)
{ {
Wt::WModelIndexSet indexSet = this->selectedIndexes(); Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet) { for (Wt::WModelIndex index : indexSet)
{
if (!index.isValid()) if (!index.isValid())
continue; continue;
std::string name = _queryModel.resultRow( index.row() ).get<0>(); Release::id_type id = _queryModel.resultRow( index.row() ).get<0>();
filter.exactMatch[Database::SearchFilter::Field::Release].push_back(name); filter.idMatch[Database::SearchFilter::Field::Release].push_back(id);
} }
} }
+3 -3
View File
@@ -52,7 +52,7 @@ class TableFilterGenre : public Wt::WTableView, public Filter
SigDoubleClicked _sigDoubleClicked; SigDoubleClicked _sigDoubleClicked;
// Name, track count // Name, track count
Wt::Dbo::QueryModel<Database::Genre::GenreResult> _queryModel; Wt::Dbo::QueryModel<Database::Genre::UIQueryResult> _queryModel;
}; };
class TableFilterArtist : public Wt::WTableView, public Filter class TableFilterArtist : public Wt::WTableView, public Filter
@@ -77,7 +77,7 @@ class TableFilterArtist : public Wt::WTableView, public Filter
SigDoubleClicked _sigDoubleClicked; SigDoubleClicked _sigDoubleClicked;
// Name, track count // Name, track count
Wt::Dbo::QueryModel<Database::Track::ArtistResult> _queryModel; Wt::Dbo::QueryModel<Database::Artist::UIQueryResult> _queryModel;
}; };
class TableFilterRelease : public Wt::WTableView, public Filter class TableFilterRelease : public Wt::WTableView, public Filter
@@ -102,7 +102,7 @@ class TableFilterRelease : public Wt::WTableView, public Filter
SigDoubleClicked _sigDoubleClicked; SigDoubleClicked _sigDoubleClicked;
// Name, track count // Name, track count
Wt::Dbo::QueryModel<Database::Track::ReleaseResult> _queryModel; Wt::Dbo::QueryModel<Database::Release::UIQueryResult> _queryModel;
}; };
+15 -12
View File
@@ -50,7 +50,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent)
Database::SearchFilter filter; Database::SearchFilter filter;
Database::Track::updateTracksQueryModel(DboSession(), _queryModel, filter, columnNames); Database::Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
_queryModel.setBatchSize(300); _queryModel.setBatchSize(300);
@@ -111,7 +111,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent)
void void
TrackView::refresh(Database::SearchFilter& filter) TrackView::refresh(Database::SearchFilter& filter)
{ {
Database::Track::updateTracksQueryModel(DboSession(), _queryModel, filter); Database::Track::updateUIQueryModel(DboSession(), _queryModel, filter);
} }
void void
@@ -121,17 +121,17 @@ TrackView::getSelectedTracks(std::vector<Database::Track::id_type>& track_ids)
Wt::WModelIndexSet indexSet = this->selectedIndexes(); Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet) for (Wt::WModelIndex index : indexSet)
{ {
if (!index.isValid()) if (!index.isValid())
continue; continue;
Database::Track::pointer track = _queryModel.resultRow( index.row() ); Database::Track::id_type id = _queryModel.resultRow( index.row() ).get<0>();
track_ids.push_back(track.id()); track_ids.push_back(id);
} }
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks DONE..."; LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks: " << track_ids.size();
} }
std::size_t std::size_t
@@ -144,7 +144,8 @@ int
TrackView::getFirstSelectedTrackPosition(void) TrackView::getFirstSelectedTrackPosition(void)
{ {
Wt::WModelIndexSet indexSet = this->selectedIndexes(); Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet)
for (Wt::WModelIndex index : indexSet)
{ {
if (!index.isValid()) if (!index.isValid())
continue; continue;
@@ -160,14 +161,16 @@ TrackView::getTracks(std::vector<Database::Track::id_type>& trackIds)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks..."; LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks...";
for (int i = 0; i < _queryModel.rowCount(); ++i) Wt::Dbo::Transaction transaction(DboSession());
{ Wt::Dbo::collection<Database::Track::UIQueryResult> results = _queryModel.query();
Database::Track::pointer track = _queryModel.resultRow(i);
trackIds.push_back(track.id()); for (auto it = results.begin(); it != results.end(); ++it)
{
Database::Track::id_type id = it->get<0>();
trackIds.push_back(id);
} }
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks done!"; LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks done! " << trackIds.size() << " tracks!";
} }
} // namespace Desktop } // namespace Desktop
+1 -1
View File
@@ -62,7 +62,7 @@ class TrackView : public Wt::WTableView, public Filter
SigTrackDoubleClicked _sigTrackDoubleClicked; SigTrackDoubleClicked _sigTrackDoubleClicked;
typedef Database::Track::pointer ResultType; typedef Database::Track::UIQueryResult ResultType;
Wt::Dbo::QueryModel< ResultType > _queryModel; Wt::Dbo::QueryModel< ResultType > _queryModel;
Wt::WTableView* _tableView; Wt::WTableView* _tableView;
+7 -11
View File
@@ -28,6 +28,8 @@
namespace UserInterface { namespace UserInterface {
namespace Mobile { namespace Mobile {
using namespace Database;
ArtistSearch::ArtistSearch(Wt::WContainerWidget *parent) ArtistSearch::ArtistSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent), : Wt::WContainerWidget(parent),
_resCount(0) _resCount(0)
@@ -57,15 +59,9 @@ ArtistSearch::search(Database::SearchFilter filter, size_t nb)
void void
ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb) ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb)
{ {
Wt::Dbo::Transaction transaction(DboSession());
std::vector<std::string> artists; std::vector<Artist::pointer> artists = Artist::getByFilter(DboSession(), filter, _resCount, nb + 1);
{
Wt::Dbo::Transaction transaction(DboSession());
// Request one more to see if more results are to be expected
artists = Database::Track::getArtists(DboSession(), filter, _resCount, nb + 1);
}
bool expectMoreResults; bool expectMoreResults;
if (artists.size() == nb + 1) if (artists.size() == nb + 1)
@@ -76,16 +72,16 @@ ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb)
else else
expectMoreResults = false; expectMoreResults = false;
BOOST_FOREACH(std::string artist, artists) for (Artist::pointer artist : artists)
{ {
Wt::WTemplate* res = new Wt::WTemplate(this); Wt::WTemplate* res = new Wt::WTemplate(this);
res->setTemplateText(Wt::WString::tr("mobile-artist-res")); res->setTemplateText(Wt::WString::tr("mobile-artist-res"));
Wt::WText *text = new Wt::WText(Wt::WString::fromUTF8(artist), Wt::PlainText); Wt::WText *text = new Wt::WText(Wt::WString::fromUTF8(artist->getName()), Wt::PlainText);
res->bindWidget("name", text); res->bindWidget("name", text);
res->clicked().connect(std::bind([=] { res->clicked().connect(std::bind([=] {
_sigArtistSelected(artist); _sigArtistSelected(artist.id());
})); }));
} }
+4 -4
View File
@@ -37,13 +37,13 @@ class ArtistSearch : public Wt::WContainerWidget
void search(Database::SearchFilter filter, std::size_t nb); void search(Database::SearchFilter filter, std::size_t nb);
// Slots // Slots
Wt::Signal<std::string>& artistSelected() { return _sigArtistSelected;} Wt::Signal<Database::Artist::id_type>& artistSelected() { return _sigArtistSelected;}
Wt::Signal<void>& moreArtistsSelected() { return _sigMoreArtistsSelected;} Wt::Signal<void>& moreArtistsSelected() { return _sigMoreArtistsSelected;}
private: private:
Wt::Signal<std::string> _sigArtistSelected; Wt::Signal<Database::Artist::id_type> _sigArtistSelected;
Wt::Signal<void> _sigMoreArtistsSelected; Wt::Signal<void> _sigMoreArtistsSelected;
void clear(void); void clear(void);
void addResults(Database::SearchFilter filter, size_t nb); void addResults(Database::SearchFilter filter, size_t nb);
+31 -41
View File
@@ -41,6 +41,8 @@
namespace UserInterface { namespace UserInterface {
namespace Mobile { namespace Mobile {
using namespace Database;
Audio::Audio(Wt::WContainerWidget *parent) Audio::Audio(Wt::WContainerWidget *parent)
: UserInterface::Audio(parent) : UserInterface::Audio(parent)
{ {
@@ -72,13 +74,13 @@ Audio::Audio(Wt::WContainerWidget *parent)
switch (CurrentUser()->getAudioEncoding()) switch (CurrentUser()->getAudioEncoding())
{ {
case Database::AudioEncoding::MP3: encoding = Wt::WMediaPlayer::MP3; break; case AudioEncoding::MP3: encoding = Wt::WMediaPlayer::MP3; break;
case Database::AudioEncoding::WEBMA: encoding = Wt::WMediaPlayer::WEBMA; break; case AudioEncoding::WEBMA: encoding = Wt::WMediaPlayer::WEBMA; break;
case Database::AudioEncoding::OGA: encoding = Wt::WMediaPlayer::OGA; break; case AudioEncoding::OGA: encoding = Wt::WMediaPlayer::OGA; break;
case Database::AudioEncoding::FLA: encoding = Wt::WMediaPlayer::FLA; break; case AudioEncoding::FLA: encoding = Wt::WMediaPlayer::FLA; break;
case Database::AudioEncoding::AUTO: case AudioEncoding::AUTO:
default: default:
encoding = AudioMediaPlayer::getBestEncoding(); encoding = AudioMediaPlayer::getBestEncoding();
} }
} }
@@ -96,43 +98,28 @@ Audio::Audio(Wt::WContainerWidget *parent)
boost::algorithm::split(keywords, text, boost::is_any_of(" "), boost::token_compress_on); boost::algorithm::split(keywords, text, boost::is_any_of(" "), boost::token_compress_on);
{ {
Database::SearchFilter filter; SearchFilter filter;
BOOST_FOREACH(std::string keyword, keywords) for (std::string keyword : keywords)
{ {
Database::SearchFilter::FieldValues likeMatch; filter.nameLikeMatch[SearchFilter::Field::Artist].push_back(keyword);
filter.nameLikeMatch[SearchFilter::Field::Release].push_back(keyword);
likeMatch[Database::SearchFilter::Field::Artist].push_back(keyword);
likeMatch[Database::SearchFilter::Field::Release].push_back(keyword);
filter.likeMatches.push_back(likeMatch);
} }
releaseSearch->search(filter, 3); releaseSearch->search(filter, 3);
} }
{ {
Database::SearchFilter filter; SearchFilter filter;
BOOST_FOREACH(std::string keyword, keywords) for (std::string keyword : keywords)
{ filter.nameLikeMatch[SearchFilter::Field::Artist].push_back(keyword);
Database::SearchFilter::FieldValues likeMatch;
likeMatch[Database::SearchFilter::Field::Artist].push_back(keyword);
filter.likeMatches.push_back(likeMatch);
}
artistSearch->search(filter, 3); artistSearch->search(filter, 3);
} }
{ {
Database::SearchFilter filter; SearchFilter filter;
BOOST_FOREACH(std::string keyword, keywords) BOOST_FOREACH(std::string keyword, keywords)
{ filter.nameLikeMatch[SearchFilter::Field::Track].push_back(keyword);
Database::SearchFilter::FieldValues likeMatch;
likeMatch[Database::SearchFilter::Field::Track].push_back(keyword);
filter.likeMatches.push_back(likeMatch);
}
trackSearch->search(filter, 3); trackSearch->search(filter, 3);
} }
@@ -143,38 +130,41 @@ Audio::Audio(Wt::WContainerWidget *parent)
})); }));
artistSearch->moreArtistsSelected().connect(std::bind([=] { artistSearch->moreArtistsSelected().connect(std::bind([=]
{
releaseSearch->hide(); releaseSearch->hide();
trackSearch->hide(); trackSearch->hide();
artistSearch->show(); artistSearch->show();
})); }));
artistSearch->artistSelected().connect(std::bind([=] (std::string artist) { artistSearch->artistSelected().connect(std::bind([=] (Artist::id_type artistId)
{
artistSearch->hide(); artistSearch->hide();
trackSearch->hide(); trackSearch->hide();
releaseSearch->show(); releaseSearch->show();
Database::SearchFilter filter; SearchFilter filter;
filter.exactMatch[Database::SearchFilter::Field::Artist].push_back(artist); filter.idMatch[SearchFilter::Field::Artist].push_back(artistId);
releaseSearch->search(filter, 20); releaseSearch->search(filter, 20);
}, std::placeholders::_1)); }, std::placeholders::_1));
releaseSearch->moreReleasesSelected().connect(std::bind([=] { releaseSearch->moreReleasesSelected().connect(std::bind([=]
{
artistSearch->hide(); artistSearch->hide();
trackSearch->hide(); trackSearch->hide();
releaseSearch->show(); releaseSearch->show();
})); }));
releaseSearch->releaseSelected().connect(std::bind([=] (std::string release) releaseSearch->releaseSelected().connect(std::bind([=] (Release::id_type releaseId)
{ {
artistSearch->hide(); artistSearch->hide();
releaseSearch->hide(); releaseSearch->hide();
trackSearch->show(); trackSearch->show();
// TODO load track search with selected release // TODO load track search with selected release
Database::SearchFilter filter; SearchFilter filter;
filter.exactMatch[Database::SearchFilter::Field::Release].push_back(release); filter.idMatch[SearchFilter::Field::Release].push_back(releaseId);
trackSearch->search(filter, 20); trackSearch->search(filter, 20);
}, std::placeholders::_1)); }, std::placeholders::_1));
@@ -185,13 +175,13 @@ Audio::Audio(Wt::WContainerWidget *parent)
releaseSearch->hide(); releaseSearch->hide();
})); }));
trackSearch->trackPlay().connect(std::bind([=] (Database::Track::id_type id) trackSearch->trackPlay().connect(std::bind([=] (Track::id_type id)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing track id " << id; LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing track id " << id;
// TODO reduce transaction scope here // TODO reduce transaction scope here
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
Database::Track::pointer track = Database::Track::getById(DboSession(), id); Track::pointer track = Track::getById(DboSession(), id);
if (track) if (track)
{ {
@@ -217,7 +207,7 @@ Audio::Audio(Wt::WContainerWidget *parent)
// Initially, populate the widgets using an empty search // Initially, populate the widgets using an empty search
{ {
Database::SearchFilter filter; // empty filter SearchFilter filter; // empty filter
artistSearch->search(filter, 3); artistSearch->search(filter, 3);
releaseSearch->search(filter, 3); releaseSearch->search(filter, 3);
trackSearch->search(filter, 3); trackSearch->search(filter, 3);
@@ -98,7 +98,7 @@ AudioMediaPlayer::play(Database::Track::id_type trackId, const Transcode::Parame
Database::Track::pointer track = Database::Track::getById(DboSession(), trackId); Database::Track::pointer track = Database::Track::getById(DboSession(), trackId);
_track->setText( Wt::WString::fromUTF8(track->getName() )); _track->setText( Wt::WString::fromUTF8(track->getName() ));
_artistRelease->setText( Wt::WString::fromUTF8(track->getArtistName()) ); _artistRelease->setText( Wt::WString::fromUTF8(track->getArtist()->getName()) );
} }
} }
+8 -12
View File
@@ -30,6 +30,8 @@
namespace UserInterface { namespace UserInterface {
namespace Mobile { namespace Mobile {
using namespace Database;
ReleaseSearch::ReleaseSearch(Wt::WContainerWidget *parent) ReleaseSearch::ReleaseSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent), : Wt::WContainerWidget(parent),
_resCount(0) _resCount(0)
@@ -59,15 +61,9 @@ ReleaseSearch::search(Database::SearchFilter filter, size_t max)
void void
ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb) ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb)
{ {
Wt::Dbo::Transaction transaction(DboSession());
std::vector<std::string> releases; std::vector<Release::pointer> releases = Release::getByFilter(DboSession(), filter, _resCount, nb + 1);
{
Wt::Dbo::Transaction transaction(DboSession());
// Request one more to see if more results are to be expected
releases = Database::Track::getReleases(DboSession(), filter, _resCount, nb + 1);
}
bool expectMoreResults; bool expectMoreResults;
if (releases.size() == nb + 1) if (releases.size() == nb + 1)
@@ -78,20 +74,20 @@ ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb)
else else
expectMoreResults = false; expectMoreResults = false;
BOOST_FOREACH(std::string release, releases) for (Release::pointer release : releases)
{ {
Wt::WTemplate* releaseWidget = new Wt::WTemplate(this); Wt::WTemplate* releaseWidget = new Wt::WTemplate(this);
releaseWidget->setTemplateText(Wt::WString::tr("mobile-release-res")); releaseWidget->setTemplateText(Wt::WString::tr("mobile-release-res"));
Wt::WImage *cover = new Wt::WImage(); Wt::WImage *cover = new Wt::WImage();
cover->setStyleClass("center-block"); cover->setStyleClass("center-block");
cover->setImageLink( Wt::WLink( LmsApplication::instance()->getCoverResource()->getReleaseUrl(release, 56))); cover->setImageLink( Wt::WLink( LmsApplication::instance()->getCoverResource()->getReleaseUrl(release.id(), 56)));
releaseWidget->bindWidget("cover", cover); releaseWidget->bindWidget("cover", cover);
releaseWidget->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release), Wt::PlainText)); releaseWidget->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release->getName()), Wt::PlainText));
releaseWidget->clicked().connect(std::bind([=] { releaseWidget->clicked().connect(std::bind([=] {
_sigReleaseSelected(release); _sigReleaseSelected(release.id());
})); }));
} }
+4 -4
View File
@@ -37,13 +37,13 @@ class ReleaseSearch : public Wt::WContainerWidget
void search(Database::SearchFilter filter, size_t nb); void search(Database::SearchFilter filter, size_t nb);
// Slots // Slots
Wt::Signal<std::string>& releaseSelected() { return _sigReleaseSelected;} Wt::Signal<Database::Release::id_type>& releaseSelected() { return _sigReleaseSelected;}
Wt::Signal<void>& moreReleasesSelected() { return _sigMoreReleasesSelected;} Wt::Signal<void>& moreReleasesSelected() { return _sigMoreReleasesSelected;}
private: private:
Wt::Signal<std::string> _sigReleaseSelected; Wt::Signal<Database::Release::id_type> _sigReleaseSelected;
Wt::Signal<void> _sigMoreReleasesSelected; Wt::Signal<void> _sigMoreReleasesSelected;
void clear(void); void clear(void);
void addResults(Database::SearchFilter filter, size_t nb); void addResults(Database::SearchFilter filter, size_t nb);
+7 -5
View File
@@ -30,6 +30,8 @@
namespace UserInterface { namespace UserInterface {
namespace Mobile { namespace Mobile {
using namespace Database;
TrackSearch::TrackSearch(Wt::WContainerWidget *parent) TrackSearch::TrackSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent), : Wt::WContainerWidget(parent),
_resCount(0) _resCount(0)
@@ -62,7 +64,7 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb)
{ {
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
std::vector< Database::Track::pointer > tracks = Database::Track::getTracks(DboSession(), filter, _resCount, nb + 1); std::vector<Track::pointer > tracks = Track::getByFilter(DboSession(), filter, _resCount, nb + 1);
bool expectMoreResults; bool expectMoreResults;
if (tracks.size() == nb + 1) if (tracks.size() == nb + 1)
@@ -73,7 +75,7 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb)
else else
expectMoreResults = false; expectMoreResults = false;
BOOST_FOREACH(Database::Track::pointer track, tracks) for (Track::pointer track : tracks)
{ {
Wt::WTemplate* trackWidget = new Wt::WTemplate(this); Wt::WTemplate* trackWidget = new Wt::WTemplate(this);
trackWidget->setTemplateText(Wt::WString::tr("mobile-track-res")); trackWidget->setTemplateText(Wt::WString::tr("mobile-track-res"));
@@ -90,11 +92,11 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb)
title->setStyleClass("mobile-track"); title->setStyleClass("mobile-track");
container->addWidget(title); container->addWidget(title);
if (!track->getArtistName().empty() if (!track->getArtist()->getName().empty()
|| !track->getReleaseName().empty()) || !track->getRelease()->getName().empty())
{ {
title->setInline(false); title->setInline(false);
Wt::WText *artistRelease = new Wt::WText(Wt::WString::fromUTF8(track->getArtistName() + " - " + track->getReleaseName()), Wt::PlainText); Wt::WText *artistRelease = new Wt::WText(Wt::WString::fromUTF8(track->getArtist()->getName() + " - " + track->getRelease()->getName()), Wt::PlainText);
artistRelease->setStyleClass("mobile-artist"); artistRelease->setStyleClass("mobile-artist");
container->addWidget(artistRelease); container->addWidget(artistRelease);
} }
+62 -56
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <boost/foreach.hpp>
#include <Wt/WApplication> #include <Wt/WApplication>
#include <Wt/Http/Response> #include <Wt/Http/Response>
@@ -73,9 +71,9 @@ CoverResource::getDefaultCover(std::size_t size)
} }
std::string std::string
CoverResource::getReleaseUrl(std::string releaseName, std::size_t size) const CoverResource::getReleaseUrl(Database::Release::id_type releaseId, std::size_t size) const
{ {
return url() + "&release=" + releaseName + "&size=" + std::to_string(size); return url() + "&releaseid=" + std::to_string(releaseId) + "&size=" + std::to_string(size);
} }
std::string std::string
@@ -101,76 +99,84 @@ CoverResource::putCover(Wt::Http::Response& response, const CoverArt::CoverArt&
void void
CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{ {
// Retrieve parameters
// Get the id of the track
const std::string *trackIdStr = request.getParameter("trackid"); const std::string *trackIdStr = request.getParameter("trackid");
const std::string *releaseIdStr = request.getParameter("releaseid");
const std::string *sizeStr = request.getParameter("size"); const std::string *sizeStr = request.getParameter("size");
const std::string *releaseStr = request.getParameter("release");
std::vector<CoverArt::CoverArt> covers; try
// Mandatory parameter size
if (!sizeStr)
return;
std::size_t size = std::stol(*sizeStr);
if (size > maxSize)
return;
if (trackIdStr)
{ {
Database::Track::id_type trackId = std::stol(*trackIdStr); std::vector<CoverArt::CoverArt> covers;
boost::filesystem::path path;
Database::Track::CoverType coverType = Database::Track::CoverType::None;
// Mandatory parameter size
if (!sizeStr)
return;
std::size_t size = std::stol(*sizeStr);
if (size > maxSize)
return;
if (trackIdStr)
{ {
Database::Track::id_type trackId = std::stol(*trackIdStr);
boost::filesystem::path path;
Database::Track::CoverType coverType = Database::Track::CoverType::None;
{
// transactions are not thread safe
std::unique_lock<std::mutex> lock(_mutex);
Wt::Dbo::Transaction transaction(_db.getSession());
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
if (track)
{
coverType = track->getCoverType();
path = track->getPath();
}
}
switch (coverType)
{
case Database::Track::CoverType::Embedded:
covers = CoverArt::Grabber::instance().getFromTrack(path);
break;
case Database::Track::CoverType::ExternalFile:
covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path());
break;
case Database::Track::CoverType::None:
break;
}
}
else if (releaseIdStr)
{
Database::Release::id_type releaseId = std::stol(*releaseIdStr); // TODO try catch
// transactions are not thread safe // transactions are not thread safe
std::unique_lock<std::mutex> lock(_mutex); std::unique_lock<std::mutex> lock(_mutex);
Wt::Dbo::Transaction transaction(_db.getSession()); Wt::Dbo::Transaction transaction(_db.getSession());
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), releaseId);
if (track) }
for (CoverArt::CoverArt& cover : covers)
{
if (cover.scale(size))
{ {
coverType = track->getCoverType(); putCover(response, cover);
path = track->getPath(); return;
} }
} }
switch (coverType) // If no cover found, just send default one
{ putCover(response, getDefaultCover(size));
case Database::Track::CoverType::Embedded:
covers = CoverArt::Grabber::instance().getFromTrack(path);
break;
case Database::Track::CoverType::ExternalFile:
covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path());
break;
case Database::Track::CoverType::None:
break;
}
} }
else if (releaseStr) catch (std::invalid_argument& e)
{ {
// transactions are not thread safe LMS_LOG(MOD_UI, SEV_ERROR) << "Invalid argument: " << e.what();
std::unique_lock<std::mutex> lock(_mutex);
Wt::Dbo::Transaction transaction(_db.getSession());
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), *releaseStr);
} }
BOOST_FOREACH(CoverArt::CoverArt& cover, covers)
{
if (cover.scale(size))
{
putCover(response, cover);
return;
}
}
// If no cover found, just send default one
putCover(response, getDefaultCover(size));
} }
} // namespace UserInterface } // namespace UserInterface
+1 -1
View File
@@ -39,7 +39,7 @@ class CoverResource : public Wt::WResource
CoverResource(Database::Handler& db, Wt::WObject *parent = 0); CoverResource(Database::Handler& db, Wt::WObject *parent = 0);
~CoverResource(); ~CoverResource();
std::string getReleaseUrl(std::string releaseName, size_t size) const; std::string getReleaseUrl(Database::Release::id_type releaseId, size_t size) const;
std::string getTrackUrl(Database::Track::id_type trackId, size_t size) const; std::string getTrackUrl(Database::Track::id_type trackId, size_t size) const;
std::string getUnknownTrackUrl(size_t size) const; std::string getUnknownTrackUrl(size_t size) const;
+236
View File
@@ -0,0 +1,236 @@
/*
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
*/
#include "database/DatabaseHandler.hpp"
static const std::string trackMBID = "123e4567-e89b-12d3-a456-426655440000";
static const std::string artistMBID = "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx";
static const std::string releaseMBID = "xxxxxxxx-xxxx-9877-Nxxx-xxxxxxxxxxxx";
int main(void)
{
try
{
using namespace Database;
boost::filesystem::remove("test.db");
Handler db("test.db");
// Create
{
Wt::Dbo::Transaction transaction(db.getSession());
Track::pointer track = Track::create(db.getSession(), "test.mp2");
track.modify()->setName("track01");
track.modify()->setMBID(trackMBID);
Artist::pointer artist = Artist::create(db.getSession(), "artist01", artistMBID);
Release::pointer release = Release::create(db.getSession(), "release01", releaseMBID);
Genre::pointer genre = Genre::create(db.getSession(), "genre01");
track.modify()->setArtist(artist);
track.modify()->setRelease(release);
track.modify()->setGenres( { genre });
}
// Search
{
Wt::Dbo::Transaction transaction(db.getSession());
Track::pointer track = Track::getByMBID(db.getSession(), trackMBID);
assert(track);
assert(track->getArtist()->getMBID() == artistMBID);
Track::pointer trackNotFound = Track::getByMBID(db.getSession(), "foobar");
assert(!trackNotFound);
Artist::pointer artist = Artist::getByMBID(db.getSession(), artistMBID);
assert(artist);
}
// Search Filters
// Select track by track name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter;
filter.nameLikeMatch[SearchFilter::Field::Track].push_back("track");
std::vector<Track::pointer> res = Track::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
filter.nameLikeMatch[SearchFilter::Field::Track].clear();
filter.nameLikeMatch[SearchFilter::Field::Track].push_back("not-found");
res = Track::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 0 );
}
// Select track by artist name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter;
filter.nameLikeMatch[SearchFilter::Field::Artist].push_back("artist");
std::vector<Track::pointer> res = Track::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
}
// Select track by artist id
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter;
filter.idMatch[SearchFilter::Field::Artist].push_back(1);
std::vector<Track::pointer> res = Track::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
}
// Select track by track name + artist id
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter;
filter.idMatch[SearchFilter::Field::Artist].push_back(1);
filter.nameLikeMatch[SearchFilter::Field::Track].push_back("track");
std::vector<Track::pointer> res = Track::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
assert(res.front()->getName() == "track01" );
}
// Select track by genre name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter = SearchFilter::NameLikeMatch({{SearchFilter::Field::Genre, {"genre"}}});
std::vector<Track::pointer> res = Track::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
assert(res.front()->getName() == "track01" );
}
// Select artist by track name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter;
filter.nameLikeMatch[SearchFilter::Field::Track].push_back("track");
std::vector<Artist::pointer> res = Artist::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
filter.nameLikeMatch[SearchFilter::Field::Track].clear();
filter.nameLikeMatch[SearchFilter::Field::Track].push_back("badtrack");
res = Artist::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 0);
}
// Select artist by track id
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter;
filter.idMatch[SearchFilter::Field::Track].push_back(1);
std::vector<Artist::pointer> res = Artist::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
}
// Select Artist by name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter;
filter.nameLikeMatch[SearchFilter::Field::Artist].push_back("artist");
std::vector<Artist::pointer> res = Artist::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
}
// Select Release by name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter;
filter.nameLikeMatch[SearchFilter::Field::Release].push_back("release");
std::vector<Release::pointer> res = Release::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
}
// Select Release by track name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter = SearchFilter::NameLikeMatch({{SearchFilter::Field::Track, {"track"}}});
std::vector<Release::pointer> res = Release::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
assert(res.front()->getName() == "release01");
}
// Select genre by name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter = SearchFilter::NameLikeMatch({{SearchFilter::Field::Genre, {"genre"}}});
std::vector<Genre::pointer> res = Genre::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
assert(res.front()->getName() == "genre01");
}
// Select genre by track name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter = SearchFilter::NameLikeMatch({{SearchFilter::Field::Track, {"track"}}});
std::vector<Genre::pointer> res = Genre::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
assert(res.front()->getName() == "genre01");
}
// Select genre by track name and artist name
{
Wt::Dbo::Transaction transaction(db.getSession());
SearchFilter filter = SearchFilter::NameLikeMatch({{SearchFilter::Field::Track, {"track"}},
{SearchFilter::Field::Artist, {"artist"}}});
std::vector<Genre::pointer> res = Genre::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
assert(res.front()->getName() == "genre01");
}
}
catch(std::exception& e)
{
std::cerr << "Caught exception " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
+25 -2
View File
@@ -1,7 +1,24 @@
TESTS = database-integrity sql-query database-user remote-client TESTS = database-basics database-integrity sql-query database-user remote-client
check_PROGRAMS = database-basics database-integrity sql-query database-user remote-client
database_basics_SOURCES = \
$(srcdir)/CheckDbBasics.cpp \
$(top_srcdir)/src/logger/Logger.cpp \
$(top_srcdir)/src/database/Artist.cpp \
$(top_srcdir)/src/database/DatabaseHandler.cpp \
$(top_srcdir)/src/database/MediaDirectory.cpp \
$(top_srcdir)/src/database/Playlist.cpp \
$(top_srcdir)/src/database/Release.cpp \
$(top_srcdir)/src/database/SearchFilter.cpp \
$(top_srcdir)/src/database/SqlQuery.cpp \
$(top_srcdir)/src/database/Track.cpp \
$(top_srcdir)/src/database/User.cpp \
$(top_srcdir)/src/database/Video.cpp
database_basics_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir)/src
check_PROGRAMS = database-integrity sql-query database-user remote-client
remote_client_SOURCES = \ remote_client_SOURCES = \
$(srcdir)/RemoteClient.cpp \ $(srcdir)/RemoteClient.cpp \
@@ -30,10 +47,13 @@ remote_client_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_src
database_user_SOURCES = \ database_user_SOURCES = \
$(srcdir)/CheckDatabaseUser.cpp \ $(srcdir)/CheckDatabaseUser.cpp \
$(top_srcdir)/src/logger/Logger.cpp \ $(top_srcdir)/src/logger/Logger.cpp \
$(top_srcdir)/src/database/Artist.cpp \
$(top_srcdir)/src/database/Playlist.cpp \ $(top_srcdir)/src/database/Playlist.cpp \
$(top_srcdir)/src/database/Release.cpp \
$(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/Track.cpp \
$(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/DatabaseHandler.cpp \
$(top_srcdir)/src/database/MediaDirectory.cpp \ $(top_srcdir)/src/database/MediaDirectory.cpp \
$(top_srcdir)/src/database/SearchFilter.cpp \
$(top_srcdir)/src/database/SqlQuery.cpp \ $(top_srcdir)/src/database/SqlQuery.cpp \
$(top_srcdir)/src/database/User.cpp \ $(top_srcdir)/src/database/User.cpp \
$(top_srcdir)/src/database/Video.cpp $(top_srcdir)/src/database/Video.cpp
@@ -44,10 +64,13 @@ database_user_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_src
database_integrity_SOURCES = \ database_integrity_SOURCES = \
$(srcdir)/DatabaseIntegrity.cpp \ $(srcdir)/DatabaseIntegrity.cpp \
$(top_srcdir)/src/logger/Logger.cpp \ $(top_srcdir)/src/logger/Logger.cpp \
$(top_srcdir)/src/database/Artist.cpp \
$(top_srcdir)/src/database/Playlist.cpp \ $(top_srcdir)/src/database/Playlist.cpp \
$(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/Track.cpp \
$(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/DatabaseHandler.cpp \
$(top_srcdir)/src/database/MediaDirectory.cpp \ $(top_srcdir)/src/database/MediaDirectory.cpp \
$(top_srcdir)/src/database/Release.cpp \
$(top_srcdir)/src/database/SearchFilter.cpp \
$(top_srcdir)/src/database/SqlQuery.cpp \ $(top_srcdir)/src/database/SqlQuery.cpp \
$(top_srcdir)/src/database/User.cpp \ $(top_srcdir)/src/database/User.cpp \
$(top_srcdir)/src/database/Video.cpp $(top_srcdir)/src/database/Video.cpp
+129 -82
View File
@@ -34,40 +34,52 @@
struct GenreInfo struct GenreInfo
{ {
uint64_t id;
std::string name; std::string name;
GenreInfo() : id(0) {}
}; };
std::ostream& operator<<(std::ostream& os, const GenreInfo& info) std::ostream& operator<<(std::ostream& os, const GenreInfo& info)
{ {
os << "name = '" << info.name << "'"; os << "name = '" << info.name << "'(" << info.id << ")";
return os; return os;
} }
struct ArtistInfo struct ArtistInfo
{ {
uint64_t id;
std::string mbid;
std::string name; std::string name;
ArtistInfo() :id(0) {}
}; };
std::ostream& operator<<(std::ostream& os, const ArtistInfo& info) std::ostream& operator<<(std::ostream& os, const ArtistInfo& info)
{ {
os << "name = '" << info.name << "'"; os << "name = '" << info.name << "'(" << info.id << ")";
return os; return os;
} }
struct ReleaseInfo struct ReleaseInfo
{ {
uint64_t id;
std::string mbid;
std::string name; std::string name;
ReleaseInfo() : id(0) {}
}; };
std::ostream& operator<<(std::ostream& os, const ReleaseInfo& info) std::ostream& operator<<(std::ostream& os, const ReleaseInfo& info)
{ {
os << "name = '" << info.name; os << "name = '" << info.name << "'(" << info.id << ")";
return os; return os;
} }
struct TrackInfo struct TrackInfo
{ {
uint64_t id; uint64_t id;
std::string mbid;
uint64_t release_id; uint64_t release_id;
uint64_t artist_id; uint64_t artist_id;
@@ -103,8 +115,30 @@ struct Cover
std::vector<unsigned char> data; std::vector<unsigned char> data;
}; };
// Ugly class for testing purposes struct SearchFilter
{
std::vector<uint64_t> artistIds;
std::vector<uint64_t> genreIds;
std::vector<uint64_t> releaseIds;
std::vector<uint64_t> trackIds;
};
void SearchFilterToRequest(const SearchFilter& filter, Remote::AudioCollectionRequest_SearchFilter& request)
{
for (uint64_t id : filter.artistIds)
request.add_artist_id(id);
for (uint64_t id : filter.genreIds)
request.add_genre_id(id);
for (uint64_t id : filter.releaseIds)
request.add_release_id(id);
for (uint64_t id : filter.trackIds)
request.add_track_id(id);
}
// Ugly class for testing purposes
class TestClient class TestClient
{ {
public: public:
@@ -122,8 +156,7 @@ class TestClient
_socket.handshake(boost::asio::ssl::stream_base::client); _socket.handshake(boost::asio::ssl::stream_base::client);
} }
void getArtists(std::vector<ArtistInfo>& artists, void getArtists(std::vector<ArtistInfo>& artists, const SearchFilter& filter = SearchFilter())
const std::vector<std::string>& genres = std::vector<std::string>())
{ {
const std::size_t requestedBatchSize = 128; const std::size_t requestedBatchSize = 128;
@@ -131,12 +164,12 @@ class TestClient
std::size_t res = 0; std::size_t res = 0;
while ((res = getArtists(artists, genres, offset, requestedBatchSize) ) > 0) while ((res = getArtists(artists, filter, offset, requestedBatchSize) ) > 0)
offset += res; offset += res;
} }
std::size_t getArtists(std::vector<ArtistInfo>& artists, const std::vector<std::string>& genres, std::size_t offset, std::size_t size) std::size_t getArtists(std::vector<ArtistInfo>& artists, const SearchFilter& filter, std::size_t offset, std::size_t size)
{ {
std::size_t nbArtists = 0; std::size_t nbArtists = 0;
@@ -148,8 +181,7 @@ class TestClient
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetArtistList); 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_size(size);
request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_offset(offset); request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_offset(offset);
BOOST_FOREACH(const std::string& genre, genres) SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_artists()->mutable_search_filter());
request.mutable_audio_collection_request()->mutable_get_artists()->add_genre(genre);
sendMsg(request); sendMsg(request);
@@ -166,11 +198,18 @@ class TestClient
for (int i = 0; i < response.audio_collection_response().artist_list().artists_size(); ++i) for (int i = 0; i < response.audio_collection_response().artist_list().artists_size(); ++i)
{ {
if (!response.audio_collection_response().artist_list().artists(i).has_name()) const Remote::AudioCollectionResponse_Artist& respArtist = response.audio_collection_response().artist_list().artists(i);
if (!respArtist.has_id())
throw std::runtime_error("no id!");
if (!respArtist.has_name())
throw std::runtime_error("no artist name!"); throw std::runtime_error("no artist name!");
ArtistInfo artist; ArtistInfo artist;
artist.name = response.audio_collection_response().artist_list().artists(i).name(); artist.name = respArtist.name();
artist.id = respArtist.id();
if (respArtist.has_mbid())
artist.mbid = respArtist.mbid();
artists.push_back( artist ); artists.push_back( artist );
nbArtists++; nbArtists++;
@@ -179,19 +218,19 @@ class TestClient
return nbArtists; return nbArtists;
} }
void getGenres(std::vector<GenreInfo>& genres) void getGenres(std::vector<GenreInfo>& genres, const SearchFilter& filter = SearchFilter())
{ {
const std::size_t requestedBatchSize = 8; const std::size_t requestedBatchSize = 8;
std::size_t offset = 0; std::size_t offset = 0;
std::size_t res = 0; std::size_t res = 0;
while ((res = getGenres(genres, offset, requestedBatchSize) ) > 0) while ((res = getGenres(genres, filter, offset, requestedBatchSize) ) > 0)
offset += res; offset += res;
} }
std::size_t getGenres(std::vector<GenreInfo>& genres, std::size_t offset, std::size_t size) std::size_t getGenres(std::vector<GenreInfo>& genres, const SearchFilter& filter, std::size_t offset, std::size_t size)
{ {
std::size_t nbAdded = 0; std::size_t nbAdded = 0;
@@ -203,6 +242,7 @@ class TestClient
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetGenreList); request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetGenreList);
request.mutable_audio_collection_request()->mutable_get_genres()->mutable_batch_parameter()->set_size(size); request.mutable_audio_collection_request()->mutable_get_genres()->mutable_batch_parameter()->set_size(size);
request.mutable_audio_collection_request()->mutable_get_genres()->mutable_batch_parameter()->set_offset(offset); request.mutable_audio_collection_request()->mutable_get_genres()->mutable_batch_parameter()->set_offset(offset);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_genres()->mutable_search_filter());
sendMsg(request); sendMsg(request);
@@ -219,11 +259,16 @@ class TestClient
for (int i = 0; i < response.audio_collection_response().genre_list().genres_size(); ++i) for (int i = 0; i < response.audio_collection_response().genre_list().genres_size(); ++i)
{ {
if (!response.audio_collection_response().genre_list().genres(i).has_name()) const Remote::AudioCollectionResponse_Genre& respGenre = response.audio_collection_response().genre_list().genres(i);
if (!respGenre.has_id())
throw std::runtime_error("no genre id!");
if (!respGenre.has_name())
throw std::runtime_error("no genre name!"); throw std::runtime_error("no genre name!");
GenreInfo genre; GenreInfo genre;
genre.name = response.audio_collection_response().genre_list().genres(i).name(); genre.id = respGenre.id();
genre.name = respGenre.name();
genres.push_back( genre ); genres.push_back( genre );
nbAdded++; nbAdded++;
@@ -231,21 +276,19 @@ class TestClient
return nbAdded; return nbAdded;
} }
void getReleases(std::vector<ReleaseInfo>& releases, void getReleases(std::vector<ReleaseInfo>& releases, const SearchFilter& filter = SearchFilter())
const std::vector<std::string>& artists = std::vector<std::string>(),
const std::vector<std::string>& genres = std::vector<std::string>())
{ {
const std::size_t requestedBatchSize = 256; const std::size_t requestedBatchSize = 256;
std::size_t offset = 0; std::size_t offset = 0;
std::size_t res = 0; std::size_t res = 0;
while ((res = getReleases(releases, artists, genres, offset, requestedBatchSize) ) > 0) while ((res = getReleases(releases, filter, offset, requestedBatchSize) ) > 0)
offset += res; offset += res;
} }
std::size_t getReleases(std::vector<ReleaseInfo>& releases, const std::vector<std::string>& artists, const std::vector<std::string>& genres, std::size_t offset, std::size_t size) std::size_t getReleases(std::vector<ReleaseInfo>& releases, const SearchFilter& filter, std::size_t offset, std::size_t size)
{ {
std::size_t nbAdded = 0; std::size_t nbAdded = 0;
@@ -257,11 +300,7 @@ class TestClient
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetReleaseList); 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_size(size);
request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_offset(offset); request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_offset(offset);
BOOST_FOREACH(const std::string& artist, artists) SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_releases()->mutable_search_filter());
request.mutable_audio_collection_request()->mutable_get_releases()->add_artist(artist);
BOOST_FOREACH(const std::string& genre, genres)
request.mutable_audio_collection_request()->mutable_get_releases()->add_genre(genre);
sendMsg(request); sendMsg(request);
@@ -284,11 +323,16 @@ class TestClient
for (int i = 0; i < response.audio_collection_response().release_list().releases_size(); ++i) for (int i = 0; i < response.audio_collection_response().release_list().releases_size(); ++i)
{ {
if (!response.audio_collection_response().release_list().releases(i).has_name()) const Remote::AudioCollectionResponse_Release& respRelease = response.audio_collection_response().release_list().releases(i);
if (!respRelease.has_id())
throw std::runtime_error("no id!");
if (!respRelease.has_name())
throw std::runtime_error("no release name!"); throw std::runtime_error("no release name!");
ReleaseInfo release; ReleaseInfo release;
release.name = response.audio_collection_response().release_list().releases(i).name(); release.id = respRelease.id();
release.name = respRelease.name();
releases.push_back( release ); releases.push_back( release );
nbAdded++; nbAdded++;
@@ -296,25 +340,17 @@ class TestClient
return nbAdded; return nbAdded;
} }
void getTracks(std::vector<TrackInfo>& tracks, void getTracks(std::vector<TrackInfo>& tracks, const SearchFilter& filter = SearchFilter())
const std::vector<std::string>& artists = std::vector<std::string>(),
const std::vector<std::string>& releases = std::vector<std::string>(),
const std::vector<std::string>& genres = std::vector<std::string>())
{ {
const std::size_t requestedBatchSize = 0; const std::size_t requestedBatchSize = 0;
std::size_t offset = 0; std::size_t offset = 0;
std::size_t res = 0; std::size_t res = 0;
while ((res = getTracks(tracks, artists, releases, genres, offset, requestedBatchSize) ) > 0) while ((res = getTracks(tracks, filter, offset, requestedBatchSize) ) > 0)
offset += res; offset += res;
} }
std::size_t getTracks(std::vector<TrackInfo>& tracks, std::size_t getTracks(std::vector<TrackInfo>& tracks, const SearchFilter& filter, std::size_t offset, std::size_t size)
const std::vector<std::string>& artists,
const std::vector<std::string>& releases,
const std::vector<std::string>& genres,
std::size_t offset,
std::size_t size)
{ {
std::size_t nbAdded = 0; std::size_t nbAdded = 0;
@@ -326,13 +362,7 @@ class TestClient
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetTrackList); request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetTrackList);
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_size(size);
request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_offset(offset); request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_offset(offset);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_search_filter());
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); sendMsg(request);
@@ -349,22 +379,27 @@ class TestClient
for (int i = 0; i < response.audio_collection_response().track_list().tracks_size(); ++i) for (int i = 0; i < response.audio_collection_response().track_list().tracks_size(); ++i)
{ {
const Remote::AudioCollectionResponse_Track& respTrack = response.audio_collection_response().track_list().tracks(i);;
TrackInfo track; TrackInfo track;
track.id = response.audio_collection_response().track_list().tracks(i).id(); track.id = respTrack.id();
track.name = response.audio_collection_response().track_list().tracks(i).name(); if (respTrack.has_mbid())
track.duration = boost::posix_time::seconds(response.audio_collection_response().track_list().tracks(i).duration_secs()); track.mbid = respTrack.mbid();
if (response.audio_collection_response().track_list().tracks(i).has_track_number())
track.track_number = response.audio_collection_response().track_list().tracks(i).track_number();
if (response.audio_collection_response().track_list().tracks(i).has_disc_number()) track.name = respTrack.name();
track.disc_number = response.audio_collection_response().track_list().tracks(i).disc_number(); track.duration = boost::posix_time::seconds(respTrack.duration_secs());
if (response.audio_collection_response().track_list().tracks(i).has_release_date()) if (respTrack.has_track_number())
track.date = response.audio_collection_response().track_list().tracks(i).release_date(); track.track_number = respTrack.track_number();
if (response.audio_collection_response().track_list().tracks(i).has_original_release_date()) if (respTrack.has_disc_number())
track.original_date = response.audio_collection_response().track_list().tracks(i).original_release_date(); track.disc_number = respTrack.disc_number();
if (respTrack.has_release_date())
track.date = respTrack.release_date();
if (respTrack.has_original_release_date())
track.original_date = respTrack.original_release_date();
tracks.push_back( track ); tracks.push_back( track );
@@ -419,7 +454,7 @@ class TestClient
} }
} }
void getCoverRelease(std::vector<Cover>& coverArt, const std::string& release) void getCoverRelease(std::vector<Cover>& coverArt, uint64_t releaseId)
{ {
// Send request // Send request
Remote::ClientMessage request; Remote::ClientMessage request;
@@ -428,7 +463,7 @@ class TestClient
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetCoverArt); 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_type( Remote::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_release( release ); request.mutable_audio_collection_request()->mutable_get_cover_art()->set_release_id( releaseId );
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 ); request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 );
sendMsg(request); sendMsg(request);
@@ -772,7 +807,7 @@ int main()
TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5080)); TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5080));
// Use a dumb account in order to login TODO parametrize // Use a dumb account in order to login TODO parametrize
if (!client.login("admin", "toto")) if (!client.login("admin", "totoadmin"))
throw std::runtime_error("login failed!"); throw std::runtime_error("login failed!");
// **** REVISION *** // **** REVISION ***
@@ -786,7 +821,7 @@ int main()
client.getArtists(artists); client.getArtists(artists);
std::cout << "Got " << artists.size() << " artists!" << std::endl; std::cout << "Got " << artists.size() << " artists!" << std::endl;
BOOST_FOREACH(const ArtistInfo& artist, artists) for (const ArtistInfo& artist : artists)
std::cout << "Artist: '" << artist << "'" << std::endl; std::cout << "Artist: '" << artist << "'" << std::endl;
// ***** Genres ********* // ***** Genres *********
@@ -795,14 +830,14 @@ int main()
client.getGenres(genres); client.getGenres(genres);
std::cout << "Got " << genres.size() << " genres!" << std::endl; std::cout << "Got " << genres.size() << " genres!" << std::endl;
BOOST_FOREACH(const GenreInfo& genre, genres) for (const GenreInfo& genre : genres)
std::cout << "Genre: '" << genre << "'" << std::endl; std::cout << "Genre: '" << genre << "'" << std::endl;
// **** Releases ****** // **** Releases ******
std::cout << "Getting releases..." << std::endl; std::cout << "Getting releases..." << std::endl;
std::vector<ReleaseInfo> releases; std::vector<ReleaseInfo> releases;
client.getReleases(releases); client.getReleases(releases);
BOOST_FOREACH(const ReleaseInfo& release, releases) for (const ReleaseInfo& release : releases)
std::cout << "Release: '" << release << "'" << std::endl; std::cout << "Release: '" << release << "'" << std::endl;
// **** Tracks ****** // **** Tracks ******
@@ -811,7 +846,7 @@ int main()
client.getTracks(tracks); client.getTracks(tracks);
std::cout << "Got " << tracks.size() << " tracks!" << std::endl; std::cout << "Got " << tracks.size() << " tracks!" << std::endl;
BOOST_FOREACH(const TrackInfo& track, tracks) for (const TrackInfo& track : tracks)
std::cout << "Track: '" << track << "'" << std::endl; std::cout << "Track: '" << track << "'" << std::endl;
// Caution: long test! // Caution: long test!
@@ -819,14 +854,17 @@ int main()
{ {
std::cout << "Getting artist for each genre..." << std::endl; std::cout << "Getting artist for each genre..." << std::endl;
// Get the artists for each genre // Get the artists for each genre
BOOST_FOREACH(const GenreInfo& genre, genres) for (const GenreInfo& genre : genres)
{ {
std::cout << "Getting artists from genre '" << genre.name << "'... "; std::cout << "Getting artists from genre '" << genre.name << "'... ";
SearchFilter filter;
filter.genreIds.push_back(genre.id);
std::vector<ArtistInfo> artists; std::vector<ArtistInfo> artists;
client.getArtists(artists, std::vector<std::string>(1, genre.name)); client.getArtists(artists, filter);
std::cout << "Found " << artists.size() << " artists!" << std::endl; std::cout << "Found " << artists.size() << " artists!" << std::endl;
BOOST_FOREACH(const ArtistInfo& artist, artists) for (const ArtistInfo& artist : artists)
std::cout << "Genre '" << genre.name << "' -> Artist: " << artist << std::endl; std::cout << "Genre '" << genre.name << "' -> Artist: " << artist << std::endl;
} }
} }
@@ -834,14 +872,17 @@ int main()
if (test(Test::ReleaseFilterArtist)) if (test(Test::ReleaseFilterArtist))
{ {
std::cout << "Getting release for each artist..." << std::endl; std::cout << "Getting release for each artist..." << std::endl;
BOOST_FOREACH(const ArtistInfo& artist, artists) for (const ArtistInfo& artist : artists)
{ {
std::cout << "Getting release from artist '" << artist.name << "'... "; std::cout << "Getting release from artist '" << artist.name << "'... ";
SearchFilter filter;
filter.artistIds.push_back(artist.id);
std::vector<ReleaseInfo> releases; std::vector<ReleaseInfo> releases;
client.getReleases(releases, std::vector<std::string>(1, artist.name) ); client.getReleases(releases, filter);
std::cout << "Found " << releases.size() << " releases!" << std::endl; std::cout << "Found " << releases.size() << " releases!" << std::endl;
BOOST_FOREACH(const ReleaseInfo& release, releases) for (const ReleaseInfo& release : releases)
std::cout << "Artist '" << artist.name << "' -> Release: '" << release << "'" << std::endl; std::cout << "Artist '" << artist.name << "' -> Release: '" << release << "'" << std::endl;
} }
} }
@@ -849,11 +890,14 @@ int main()
if (test(Test::ReleaseFilterGenre)) if (test(Test::ReleaseFilterGenre))
{ {
std::cout << "Getting release for each genre..." << std::endl; std::cout << "Getting release for each genre..." << std::endl;
BOOST_FOREACH(const GenreInfo& genre, genres) for (const GenreInfo& genre : genres)
{ {
std::cout << "Getting release from genre '" << genre.name << "'... "; std::cout << "Getting release from genre '" << genre.name << "'... ";
SearchFilter filter;
filter.genreIds.push_back(genre.id);
std::vector<ReleaseInfo> releases; std::vector<ReleaseInfo> releases;
client.getReleases(releases, std::vector<std::string>(), std::vector<std::string>(1, genre.name)); client.getReleases(releases, filter);
std::cout << "Found " << releases.size() << " releases!" << std::endl; std::cout << "Found " << releases.size() << " releases!" << std::endl;
BOOST_FOREACH(const ReleaseInfo& release, releases) BOOST_FOREACH(const ReleaseInfo& release, releases)
@@ -865,13 +909,16 @@ int main()
{ {
std::cout << "Getting tracks for each artist..." << std::endl; std::cout << "Getting tracks for each artist..." << std::endl;
// Get the tracks for each artist // Get the tracks for each artist
BOOST_FOREACH(const ArtistInfo& artist, artists) for (const ArtistInfo& artist : artists)
{ {
SearchFilter filter;
filter.artistIds.push_back(artist.id);
std::vector<TrackInfo> tracks; std::vector<TrackInfo> tracks;
client.getTracks(tracks, std::vector<std::string>(1, artist.name)); client.getTracks(tracks, filter);
std::cout << "Artist '" << artist.name << "', nb tracks = " << tracks.size() << std::endl; std::cout << "Artist '" << artist.name << "', nb tracks = " << tracks.size() << std::endl;
BOOST_FOREACH(const TrackInfo& track, tracks) for (const TrackInfo& track : tracks)
std::cout << "Artist '" << artist.name << "', track: '" << track << "'" << std::endl; std::cout << "Artist '" << artist.name << "', track: '" << track << "'" << std::endl;
} }
} }
@@ -880,19 +927,19 @@ int main()
if (test(Test::CoverByRelease)) if (test(Test::CoverByRelease))
{ {
std::cout << "Getting cover for each release..." << std::endl; std::cout << "Getting cover for each release..." << std::endl;
BOOST_FOREACH(const ReleaseInfo& release, releases) for (const ReleaseInfo& release : releases)
{ {
std::vector<Cover> coverArts; std::vector<Cover> coverArts;
client.getCoverRelease(coverArts, release.name); client.getCoverRelease(coverArts, release.id);
if (writeCovers) if (writeCovers)
{ {
boost::filesystem::create_directory("cover"); boost::filesystem::create_directory("cover");
BOOST_FOREACH(const Cover coverArt, coverArts) for (const Cover coverArt : coverArts)
{ {
std::ostringstream oss; oss << "cover/" << release.name << ".jpeg"; std::ostringstream oss; oss << "cover/" << release.name << ".jpeg";
std::ofstream out(oss.str().c_str()); std::ofstream out(oss.str().c_str());
BOOST_FOREACH(unsigned char c, coverArt.data) for (unsigned char c : coverArt.data)
out.put(c); out.put(c);
} }
} }
@@ -904,7 +951,7 @@ int main()
if (test(Test::CoverByTrack)) if (test(Test::CoverByTrack))
{ {
std::cout << "Getting cover for each track..." << std::endl; std::cout << "Getting cover for each track..." << std::endl;
BOOST_FOREACH(const TrackInfo& track, tracks) for (const TrackInfo& track : tracks)
{ {
std::vector<Cover> coverArt; std::vector<Cover> coverArt;
client.getCoverTrack(coverArt, track.id); client.getCoverTrack(coverArt, track.id);
@@ -916,7 +963,7 @@ int main()
if (test(Test::Transcode)) if (test(Test::Transcode))
{ {
BOOST_FOREACH(const TrackInfo& track, tracks) for (const TrackInfo& track : tracks)
{ {
std::vector<unsigned char> data; std::vector<unsigned char> data;
client.getMediaAudio(track.id, data); client.getMediaAudio(track.id, data);
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.