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)/cover/CoverArt.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \
$(srcdir)/database/Artist.cpp \
$(srcdir)/database/DatabaseHandler.cpp \
$(srcdir)/database/MediaDirectory.cpp \
$(srcdir)/database/Playlist.cpp \
$(srcdir)/database/Release.cpp \
$(srcdir)/database/SearchFilter.cpp \
$(srcdir)/database/SqlQuery.cpp \
$(srcdir)/database/Track.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>
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;
@@ -205,13 +205,11 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std:
// Get the first track of the release
{
SearchFilter filter;
filter.exactMatch[SearchFilter::Field::Release].push_back(releaseName);
Wt::Dbo::Transaction transaction(session);
std::vector<Track::pointer> tracks
= Track::getAll(session, filter, -1, 1 /* limit result size */);
std::vector<Track::pointer> tracks = Track::getByFilter(session,
SearchFilter::IdMatch({{SearchFilter::Field::Release, {releaseId}}}),
-1, 1 /* limit result size */);
if (tracks.empty())
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> 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> 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:
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
Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{
try {
// Check last update time
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
typedef std::list<std::string> GenreList;
GenreList genreList;
std::vector< Genre::pointer > genres;
if (items.find(MetaData::Type::Genres) != items.end())
{
genreList = (boost::any_cast<GenreList>(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 );
}
genres = getGenres( boost::any_cast< std::list<std::string> > (items[MetaData::Type::Genres]) );
}
if (genres.empty())
genres.push_back( Genre::getNone( _db.getSession() ));
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
// Otherwise, create it
if (!track)
@@ -372,6 +483,8 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
assert(track);
track.modify()->setArtist(artist);
track.modify()->setRelease(release);
track.modify()->setLastWriteTime(lastWriteTime);
track.modify()->setName(title);
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;
// Product genre list
BOOST_FOREACH(const std::string& genre, genreList) {
for (Genre::pointer genre : genres)
{
if (!trackGenreList.empty())
trackGenreList += ", ";
trackGenreList += genre;
trackGenreList += genre->getName();
}
track.modify()->setGenres( trackGenreList );
}
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())
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)
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;
Genres genres = Genre::getAll(_db.getSession());
@@ -549,7 +657,7 @@ Updater::checkAudioFiles( Stats& stats )
if (genre->getTracks().size() == 0)
genre.remove();
}
*/
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check audio files done!";
}
+4
View File
@@ -72,6 +72,10 @@ class Updater
Database::MediaDirectory::Type type,
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
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() )
{
_session.setConnection(_dbBackend);
_session.mapClass<Database::Artist>("artist");
_session.mapClass<Database::Genre>("genre");
_session.mapClass<Database::Track>("track");
_session.mapClass<Database::Playlist>("playlist");
_session.mapClass<Database::PlaylistEntry>("playlist_entry");
_session.mapClass<Database::Release>("release");
_session.mapClass<Database::Video>("video");
_session.mapClass<Database::MediaDirectory>("media_directory");
_session.mapClass<Database::MediaDirectorySettings>("media_directory_settings");
@@ -92,9 +94,10 @@ _dbBackend( db.string() )
try {
_session.createTables();
_dbBackend.executeSql("CREATE INDEX artist_name_idx ON track(artist_name)");
_dbBackend.executeSql("CREATE INDEX release_name_idx ON track(release_name)");
_dbBackend.executeSql("CREATE INDEX artist_name_idx ON artist(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) {
LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what();
+2
View File
@@ -22,6 +22,8 @@
#include <Wt/Dbo/Dbo>
#include <string>
namespace Database {
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/>.
*/
#include <boost/foreach.hpp>
#include <Wt/Dbo/QueryModel>
#include "logger/Logger.hpp"
@@ -29,104 +27,6 @@
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)
:
_trackNumber(0),
@@ -148,7 +48,7 @@ Track::setGenres(std::vector<Genre::pointer> genres)
if (_genres.size())
_genres.clear();
BOOST_FOREACH(Genre::pointer genre, genres) {
for (Genre::pointer genre : genres) {
_genres.insert( genre );
}
}
@@ -156,7 +56,7 @@ Track::setGenres(std::vector<Genre::pointer> genres)
Track::pointer
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
@@ -165,6 +65,11 @@ Track::getById(Wt::Dbo::Session& session, id_type 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::create(Wt::Dbo::Session& session, const boost::filesystem::path& p)
@@ -181,104 +86,52 @@ Track::getGenres(void) const
}
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);
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);
return query;
}
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);
return std::vector<Track::pointer>(res.begin(), res.end());
}
Wt::Dbo::collection<pointer> res = getQuery(session, filter).limit(size).offset(offset);
std::vector<Track::pointer>
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;
return std::vector<pointer>(res.begin(), res.end());
}
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);
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);
Wt::Dbo::Query< UIQueryResult > query = getUIQuery(session, filter);
model.setQuery(query, columnNames.empty() ? true : false);
// TODO do something better
if (columnNames.size() == 9)
{
model.addColumn( "t.artist_name", columnNames[0] );
model.addColumn( "t.release_name", columnNames[1] );
model.addColumn( "a.name", columnNames[0] );
model.addColumn( "r.name", columnNames[1] );
model.addColumn( "t.disc_number", columnNames[2] );
model.addColumn( "t.track_number", columnNames[3] );
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()
{
}
@@ -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::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));
}
Wt::Dbo::collection<Genre::pointer>
Genre::getAll(Wt::Dbo::Session& session, int offset, int size)
Wt::Dbo::Query<Genre::pointer>
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>
Genre::getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter)
Wt::Dbo::Query<Genre::UIQueryResult>
Genre::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter, true);
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<Genre::GenreResult> 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");
Wt::Dbo::Query<UIQueryResult> query
= 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);
return query;
}
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);
// 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
+53 -50
View File
@@ -32,25 +32,9 @@
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 PlaylistEntry;
@@ -67,10 +51,14 @@ class Genre
// Find utility
static pointer getByName(Wt::Dbo::Session& session, const std::string& name);
static pointer getNone(Wt::Dbo::Session& session);
static Wt::Dbo::collection<pointer> getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1);
typedef boost::tuple<std::string, int> GenreResult;
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>());
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);
// 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
static pointer create(Wt::Dbo::Session& session, const std::string& name);
@@ -88,6 +76,8 @@ class Genre
}
private:
static Wt::Dbo::Query<pointer> getQuery(Wt::Dbo::Session& session, SearchFilter filter);
static const std::size_t _maxNameLength = 128;
std::string _name;
@@ -114,22 +104,28 @@ class Track
// Find utility functions
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
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);
// Used for remote
static std::vector<pointer> getAll(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);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
// Utility fonctions
// 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>());
// Release name, year, track counts
typedef boost::tuple<std::string, boost::posix_time::ptime, int> ReleaseResult;
static void updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<ReleaseResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>());
// Artist name, albums, tracks
typedef boost::tuple<std::string, int, int> ArtistResult;
static void updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<ArtistResult>& 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
typedef boost::tuple<id_type, //ID
std::string, // Artist name
std::string, // Release Name
int, // Disc Number
int, // Track Number
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
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 setDiscNumber(int num) { _discNumber = num; }
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
void setArtistName(const std::string& name) { _artistName = std::string(name, 0, _maxNameLength); }
void setReleaseName(const std::string& name) { _releaseName = std::string(name, 0, _maxNameLength); }
void setDuration(boost::posix_time::time_duration duration) { _duration = duration; }
void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; }
void setAddedTime(boost::posix_time::ptime time) { _fileAdded = time; }
void setChecksum(const std::vector<unsigned char>& checksum) { _fileChecksum = checksum; }
void setDate(const boost::posix_time::ptime& date) { _date = date; }
void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; }
void setGenres(const std::string& genreList) { _genreList = genreList; }
void setGenres(std::vector<Genre::pointer> genres);
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 getDiscNumber(void) const { return _discNumber; }
std::string getName(void) const { return _name; }
std::string getArtistName(void) const { return _artistName; }
std::string getReleaseName(void) const { return _releaseName; }
boost::filesystem::path getPath(void) const { return _filePath; }
boost::posix_time::time_duration getDuration(void) const { return _duration; }
boost::posix_time::ptime getDate(void) const { return _date; }
boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; }
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 getAddedTime(void) const { return _fileAdded; }
const std::vector<unsigned char>& getChecksum(void) const { return _fileChecksum; }
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>
void persist(Action& a)
@@ -171,25 +170,25 @@ class Track
Wt::Dbo::field(a, _trackNumber, "track_number");
Wt::Dbo::field(a, _discNumber, "disc_number");
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _artistName, "artist_name");
Wt::Dbo::field(a, _releaseName, "release_name");
Wt::Dbo::field(a, _duration, "duration");
Wt::Dbo::field(a, _date, "date");
Wt::Dbo::field(a, _originalDate, "original_date");
Wt::Dbo::field(a, _genreList, "genre_list");
Wt::Dbo::field(a, _filePath, "path");
Wt::Dbo::field(a, _fileLastWrite, "last_write");
Wt::Dbo::field(a, _filePath, "file_path");
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, _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, _playlistEntries, Wt::Dbo::ManyToOne, "track");
}
private:
static Wt::Dbo::Query< pointer > getAllQuery(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 Wt::Dbo::Query< pointer > getQuery(Wt::Dbo::Session& session, SearchFilter filter);
static const std::size_t _maxNameLength = 128;
@@ -200,14 +199,18 @@ class Track
std::string _releaseName;
boost::posix_time::time_duration _duration;
boost::posix_time::ptime _date;
boost::posix_time::ptime _originalDate;
boost::posix_time::ptime _originalDate; // original date time
std::string _genreList;
std::string _filePath;
std::vector<unsigned char> _fileChecksum;
boost::posix_time::ptime _fileLastWrite;
boost::posix_time::ptime _fileAdded;
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;
};
+2
View File
@@ -19,8 +19,10 @@
// header file aimed to ease database class declarations
#include "Artist.hpp"
#include "Track.hpp"
#include "Playlist.hpp"
#include "Release.hpp"
#include "Video.hpp"
#include "MediaDirectory.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));
}
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
LMS_LOG(MOD_METADATA, SEV_DEBUG) << "key = " << it->first << ", value = " << it->second;
*/
+2
View File
@@ -43,6 +43,8 @@ namespace MetaData
AudioStreams, // vector<AudioStream>
VideoStreams, // vector<VideoStream>
SubtitleStreams, // vector<SubtitleStream>
MusicBrainzArtistID, // string
MusicBrainzAlbumID, // string
};
// Used by Streams
+34 -22
View File
@@ -5,6 +5,14 @@ package Remote;
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
{
required uint32 offset = 1; // First element requested
@@ -14,30 +22,25 @@ message AudioCollectionRequest
message GetGenreList
{
required BatchParameter batch_parameter = 1;
optional SearchFilter search_filter = 2;
}
message GetArtistList
{
required BatchParameter batch_parameter = 1;
// Search filters
repeated string genre = 2; // Artist that has at least a track of the genre
optional SearchFilter search_filter = 2;
}
message GetReleaseList
{
required BatchParameter batch_parameter = 1;
// Search filters
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
optional SearchFilter search_filter = 2;
}
message GetTrackList
{
required BatchParameter batch_parameter = 1;
// Search filters
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
optional SearchFilter search_filter = 2;
}
message GetCoverArt
@@ -50,7 +53,7 @@ message AudioCollectionRequest
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
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
{
required string name = 1;
required uint64 id = 1;
optional string mbid = 2;
required string name = 3;
}
message Artist
{
required string name = 1;
required uint64 id = 1;
optional string mbid = 2;
required string name = 3;
}
message Release
{
required string name = 1;
required uint64 id = 1;
optional string mbid = 2;
required string name = 3;
}
message Track
{
required uint64 id = 1; // Track id
optional string mbid = 2;
required string artist = 2;
required string release = 3;
repeated string genre = 4;
required uint64 artist_id = 3;
required uint64 release_id = 4;
repeated uint64 genre_id = 5;
optional uint32 disc_number = 5;
optional uint32 track_number = 6;
required string name = 7;
required uint32 duration_secs = 8;
optional string release_date = 9;
optional string original_release_date = 10;
optional string artist_mbid = 6;
optional string release_mbid = 7;
optional uint32 disc_number = 8;
optional uint32 track_number = 9;
required string name = 10;
required uint32 duration_secs = 11;
optional string release_date = 12;
optional string original_release_date = 13;
}
enum Type {
@@ -21,7 +21,6 @@
#include <boost/locale.hpp>
#include <boost/uuid/sha1.hpp>
#include <boost/foreach.hpp>
#include "logger/Logger.hpp"
@@ -35,11 +34,29 @@ namespace Server {
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)
{}
bool
AudioCollectionRequestHandler::process(const AudioCollectionRequest& request, AudioCollectionResponse& response)
{
@@ -133,17 +150,21 @@ AudioCollectionRequestHandler::processGetGenres(const AudioCollectionRequest::Ge
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::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 (Genres::const_iterator it = genres.begin(); it != genres.end(); ++it)
for (Genre::pointer genre : genres)
{
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;
@@ -164,23 +185,28 @@ AudioCollectionRequestHandler::processGetArtists(const AudioCollectionRequest::G
size = _maxListArtists;
size = std::min(size, _maxListArtists);
SearchFilter filter;
// Get filters
std::vector<std::string> genres;
for (int id = 0; id < request.genre_size(); ++id)
filter.exactMatch[SearchFilter::Field::Genre].push_back( request.genre(id) );
SearchFilter filter;
if (request.has_search_filter())
filter = SearchFilterFromRequest(request.search_filter());
// Now fetch requested data...
Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<std::string> artists
= Database::Track::getArtists(_db.getSession(), filter,
std::vector<Artist::pointer> artists
= Artist::getByFilter(_db.getSession(), filter,
request.batch_parameter().offset(), static_cast<int>(size) );
BOOST_FOREACH(const std::string& artist, artists)
response.add_artists()->set_name( std::string( boost::locale::conv::to_utf<char>(artist, "UTF-8") ) );
for (Artist::pointer artist : artists)
{
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;
}
@@ -200,25 +226,25 @@ AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest::
size = _maxListReleases;
size = std::min(size, _maxListReleases);
// Get filters
SearchFilter filter;
std::vector<std::string> artists;
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) );
if (request.has_search_filter())
filter = SearchFilterFromRequest(request.search_filter());
Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<std::string> releases
= Database::Track::getReleases( _db.getSession(), filter,
std::vector<Release::pointer> releases
= Release::getByFilter( _db.getSession(), filter,
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;
@@ -241,34 +267,24 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge
// Get filters
SearchFilter filter;
std::vector<std::string> artists;
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) );
if (request.has_search_filter())
filter = SearchFilterFromRequest(request.search_filter());
Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<Database::Track::pointer> tracks
= Database::Track::getAll( _db.getSession(), filter,
std::vector<Track::pointer> tracks
= Track::getByFilter( _db.getSession(), filter,
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();
newTrack->set_id(track.id());
newTrack->set_disc_number( track->getDiscNumber() );
newTrack->set_track_number( track->getTrackNumber() );
newTrack->set_artist( track->getArtistName() );
newTrack->set_release( track->getReleaseName() );
newTrack->set_artist_id( track->getArtist().id() );
newTrack->set_release_id( track->getRelease().id() );
newTrack->set_name( std::string( boost::locale::conv::to_utf<char>(track->getName(), "UTF-8") ) );
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()) );
if (!track->getOriginalDate().is_special())
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())
newTrack->add_genre( genre->getName() );
for (Genre::pointer genre : track->getGenres())
newTrack->add_genre_id( genre.id() );
}
@@ -299,9 +317,9 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::
switch(request.type())
{
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;
}
break;
@@ -315,7 +333,7 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::
break;
}
BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts)
for (CoverArt::CoverArt& coverArt : coverArts)
{
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
@@ -345,12 +363,12 @@ AudioCollectionRequestHandler::processGetRevision(AudioCollectionResponse::Revis
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());
boost::uuids::detail::sha1 s;
BOOST_FOREACH(const char c, hashStr)
for (const char c : hashStr)
s.process_byte(c);
unsigned int digest[5];
+1 -1
View File
@@ -199,7 +199,7 @@ AudioMediaPlayer::load(Database::Track::id_type trackId)
bitrate = CurrentUser()->getAudioBitrate();
trackPath = track->getPath();
_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)));
}
+23 -16
View File
@@ -52,6 +52,8 @@ void WPopupMenuClear(Wt::WPopupMenu* menu)
namespace UserInterface {
namespace Desktop {
using namespace Database;
// Special playlist generated each time the playque gets changed
// Restored at the beginning of the session
static const std::string CurrentQueuePlaylistName = "__current__";
@@ -269,7 +271,7 @@ Audio::playlistShowSaveDialog(std::string playlistName)
Wt::Dbo::Transaction transaction(DboSession());
// 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
("Overwrite playlist",
@@ -301,25 +303,25 @@ Audio::playlistSaveFromPlayqueue(std::string playlistName)
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)
{
LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'";
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);
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)
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";
@@ -330,16 +332,16 @@ Audio::playlistLoadToPlayqueue(std::string playlistName)
{
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());
Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), playlistName, CurrentUser());
Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser());
if (!playlist)
return;
entries = Database::PlaylistEntry::getEntries(DboSession(), playlist);
entries = PlaylistEntry::getEntries(DboSession(), playlist);
}
_playQueue->clear();
@@ -365,7 +367,7 @@ Audio::playlistShowDeleteDialog(std::string name)
{
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)
playlist.remove();
@@ -396,9 +398,9 @@ Audio::playlistRefreshMenus()
}));
_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)
continue;
@@ -431,7 +433,7 @@ Audio::search(std::string searchText)
void
Audio::addSelectedTracks(void)
{
std::vector<Database::Track::id_type> trackIds;
std::vector<Track::id_type> trackIds;
_trackView->getSelectedTracks(trackIds);
// If nothing is selected, get the whole track list
@@ -444,7 +446,9 @@ Audio::addSelectedTracks(void)
void
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();
@@ -458,6 +462,9 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
break;
case PlayQueueAddSelectedTracks:
LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding selected tracks...";
// If nothing selected, get all the track and play everything
if (_trackView->getNbSelectedTracks() == 0)
{
@@ -485,7 +492,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
}
void
Audio::playTrack(Database::Track::id_type trackId, int pos)
Audio::playTrack(Track::id_type trackId, int pos)
{
{
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);
// 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;
likeMatch[Database::SearchFilter::Field::Artist].push_back(value);
likeMatch[Database::SearchFilter::Field::Release].push_back(value);
likeMatch[Database::SearchFilter::Field::Genre].push_back(value);
likeMatch[Database::SearchFilter::Field::Track].push_back(value);
filter.likeMatches.push_back(likeMatch);
filter.nameLikeMatch[Database::SearchFilter::Field::Artist].push_back(value);
filter.nameLikeMatch[Database::SearchFilter::Field::Release].push_back(value);
filter.nameLikeMatch[Database::SearchFilter::Field::Genre].push_back(value);
filter.nameLikeMatch[Database::SearchFilter::Field::Track].push_back(value);
}
}
}
+22 -16
View File
@@ -366,12 +366,16 @@ PlayQueue::select(int rowId)
void
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
//
BOOST_FOREACH(Database::Track::id_type trackId, trackIds)
for (Track::id_type trackId : trackIds)
{
Wt::Dbo::Transaction transaction(DboSession());
Database::Track::pointer track (Database::Track::getById(DboSession(), trackId));
Track::pointer track (Track::getById(DboSession(), trackId));
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);
std::string coverUrl;
if (track->getCoverType() != Database::Track::CoverType::None)
if (track->getCoverType() != Track::CoverType::None)
coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
else
coverUrl = LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(64);
@@ -392,8 +396,8 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
TrackInfo trackInfo;
trackInfo.track = Wt::WString::fromUTF8(track->getName());
trackInfo.artist = Wt::WString::fromUTF8(track->getArtistName());
trackInfo.release = Wt::WString::fromUTF8(track->getReleaseName());
trackInfo.artist = Wt::WString::fromUTF8(track->getArtist()->getName());
trackInfo.release = Wt::WString::fromUTF8(track->getRelease()->getName());
_model->setData(dataRow, COLUMN_ID_NAME, trackInfo, TrackInfoRole);
}
}
@@ -455,19 +459,21 @@ PlayQueue::playPrevious(void)
}
}
bool
void
PlayQueue::readTrack(int rowPos)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos;
Database::Track::id_type trackId = boost::any_cast<Database::Track::id_type>(_model->data(rowPos, COLUMN_ID_TRACK_ID, Wt::UserRole));
LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount();
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
@@ -485,7 +491,7 @@ PlayQueue::setPlayingTrackPos(int newRowPos)
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;
// 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
if (index.row() == 0)
@@ -570,7 +576,7 @@ PlayQueue::moveSelectedDown(void)
Wt::WModelIndexSet newIndexSet;
// 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
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);
bool readTrack(int rowId);
void readTrack(int rowId);
void setPlayingTrackPos(int newRowPos);
void renumber(int firstId, int lastId);
+17 -16
View File
@@ -39,7 +39,7 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent)
SearchFilter filter;
Genre::updateGenreQueryModel(DboSession(), _queryModel, filter, columnNames);
Genre::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
this->setSelectionMode(Wt::ExtendedSelection);
this->setSortingEnabled(true);
@@ -82,7 +82,7 @@ TableFilterGenre::layoutSizeChanged (int width, int height)
void
TableFilterGenre::refresh(SearchFilter& filter)
{
Genre::updateGenreQueryModel(DboSession(), _queryModel, filter);
Genre::updateUIQueryModel(DboSession(), _queryModel, filter);
}
// Get constraint created by this filter
@@ -91,14 +91,15 @@ TableFilterGenre::getConstraint(SearchFilter& filter)
{
Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet) {
for (Wt::WModelIndex index : indexSet)
{
if (!index.isValid())
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;
Track::updateArtistQueryModel(DboSession(), _queryModel, filter, columnNames);
Artist::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
this->setSelectionMode(Wt::ExtendedSelection);
this->setSortingEnabled(true);
@@ -153,7 +154,7 @@ TableFilterArtist::layoutSizeChanged (int width, int height)
void
TableFilterArtist::refresh(SearchFilter& filter)
{
Track::updateArtistQueryModel(DboSession(), _queryModel, filter);
Artist::updateUIQueryModel(DboSession(), _queryModel, filter);
}
// Get constraint created by this filter
@@ -167,9 +168,9 @@ TableFilterArtist::getConstraint(SearchFilter& filter)
if (!index.isValid())
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;
Track::updateReleaseQueryModel(DboSession(), _queryModel, filter, columnNames);
Release::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
this->setSelectionMode(Wt::ExtendedSelection);
this->setSortingEnabled(true);
@@ -231,7 +232,7 @@ TableFilterRelease::layoutSizeChanged (int width, int height)
void
TableFilterRelease::refresh(SearchFilter& filter)
{
Track::updateReleaseQueryModel(DboSession(), _queryModel, filter);
Release::updateUIQueryModel(DboSession(), _queryModel, filter);
}
// Get constraint created by this filter
@@ -240,14 +241,14 @@ TableFilterRelease::getConstraint(SearchFilter& filter)
{
Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet) {
for (Wt::WModelIndex index : indexSet)
{
if (!index.isValid())
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;
// Name, track count
Wt::Dbo::QueryModel<Database::Genre::GenreResult> _queryModel;
Wt::Dbo::QueryModel<Database::Genre::UIQueryResult> _queryModel;
};
class TableFilterArtist : public Wt::WTableView, public Filter
@@ -77,7 +77,7 @@ class TableFilterArtist : public Wt::WTableView, public Filter
SigDoubleClicked _sigDoubleClicked;
// Name, track count
Wt::Dbo::QueryModel<Database::Track::ArtistResult> _queryModel;
Wt::Dbo::QueryModel<Database::Artist::UIQueryResult> _queryModel;
};
class TableFilterRelease : public Wt::WTableView, public Filter
@@ -102,7 +102,7 @@ class TableFilterRelease : public Wt::WTableView, public Filter
SigDoubleClicked _sigDoubleClicked;
// 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::Track::updateTracksQueryModel(DboSession(), _queryModel, filter, columnNames);
Database::Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
_queryModel.setBatchSize(300);
@@ -111,7 +111,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent)
void
TrackView::refresh(Database::SearchFilter& filter)
{
Database::Track::updateTracksQueryModel(DboSession(), _queryModel, filter);
Database::Track::updateUIQueryModel(DboSession(), _queryModel, filter);
}
void
@@ -121,17 +121,17 @@ TrackView::getSelectedTracks(std::vector<Database::Track::id_type>& track_ids)
Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet)
for (Wt::WModelIndex index : indexSet)
{
if (!index.isValid())
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
@@ -144,7 +144,8 @@ int
TrackView::getFirstSelectedTrackPosition(void)
{
Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet)
for (Wt::WModelIndex index : indexSet)
{
if (!index.isValid())
continue;
@@ -160,14 +161,16 @@ TrackView::getTracks(std::vector<Database::Track::id_type>& trackIds)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks...";
for (int i = 0; i < _queryModel.rowCount(); ++i)
{
Database::Track::pointer track = _queryModel.resultRow(i);
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::collection<Database::Track::UIQueryResult> results = _queryModel.query();
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
+1 -1
View File
@@ -62,7 +62,7 @@ class TrackView : public Wt::WTableView, public Filter
SigTrackDoubleClicked _sigTrackDoubleClicked;
typedef Database::Track::pointer ResultType;
typedef Database::Track::UIQueryResult ResultType;
Wt::Dbo::QueryModel< ResultType > _queryModel;
Wt::WTableView* _tableView;
+7 -11
View File
@@ -28,6 +28,8 @@
namespace UserInterface {
namespace Mobile {
using namespace Database;
ArtistSearch::ArtistSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_resCount(0)
@@ -57,15 +59,9 @@ ArtistSearch::search(Database::SearchFilter filter, size_t nb)
void
ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb)
{
Wt::Dbo::Transaction transaction(DboSession());
std::vector<std::string> artists;
{
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);
}
std::vector<Artist::pointer> artists = Artist::getByFilter(DboSession(), filter, _resCount, nb + 1);
bool expectMoreResults;
if (artists.size() == nb + 1)
@@ -76,16 +72,16 @@ ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb)
else
expectMoreResults = false;
BOOST_FOREACH(std::string artist, artists)
for (Artist::pointer artist : artists)
{
Wt::WTemplate* res = new Wt::WTemplate(this);
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->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);
// Slots
Wt::Signal<std::string>& artistSelected() { return _sigArtistSelected;}
Wt::Signal<void>& moreArtistsSelected() { return _sigMoreArtistsSelected;}
Wt::Signal<Database::Artist::id_type>& artistSelected() { return _sigArtistSelected;}
Wt::Signal<void>& moreArtistsSelected() { return _sigMoreArtistsSelected;}
private:
Wt::Signal<std::string> _sigArtistSelected;
Wt::Signal<void> _sigMoreArtistsSelected;
Wt::Signal<Database::Artist::id_type> _sigArtistSelected;
Wt::Signal<void> _sigMoreArtistsSelected;
void clear(void);
void addResults(Database::SearchFilter filter, size_t nb);
+31 -41
View File
@@ -41,6 +41,8 @@
namespace UserInterface {
namespace Mobile {
using namespace Database;
Audio::Audio(Wt::WContainerWidget *parent)
: UserInterface::Audio(parent)
{
@@ -72,13 +74,13 @@ Audio::Audio(Wt::WContainerWidget *parent)
switch (CurrentUser()->getAudioEncoding())
{
case Database::AudioEncoding::MP3: encoding = Wt::WMediaPlayer::MP3; break;
case Database::AudioEncoding::WEBMA: encoding = Wt::WMediaPlayer::WEBMA; break;
case Database::AudioEncoding::OGA: encoding = Wt::WMediaPlayer::OGA; break;
case Database::AudioEncoding::FLA: encoding = Wt::WMediaPlayer::FLA; break;
case Database::AudioEncoding::AUTO:
case AudioEncoding::MP3: encoding = Wt::WMediaPlayer::MP3; break;
case AudioEncoding::WEBMA: encoding = Wt::WMediaPlayer::WEBMA; break;
case AudioEncoding::OGA: encoding = Wt::WMediaPlayer::OGA; break;
case AudioEncoding::FLA: encoding = Wt::WMediaPlayer::FLA; break;
case AudioEncoding::AUTO:
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);
{
Database::SearchFilter filter;
BOOST_FOREACH(std::string keyword, keywords)
SearchFilter filter;
for (std::string keyword : keywords)
{
Database::SearchFilter::FieldValues likeMatch;
likeMatch[Database::SearchFilter::Field::Artist].push_back(keyword);
likeMatch[Database::SearchFilter::Field::Release].push_back(keyword);
filter.likeMatches.push_back(likeMatch);
filter.nameLikeMatch[SearchFilter::Field::Artist].push_back(keyword);
filter.nameLikeMatch[SearchFilter::Field::Release].push_back(keyword);
}
releaseSearch->search(filter, 3);
}
{
Database::SearchFilter filter;
BOOST_FOREACH(std::string keyword, keywords)
{
Database::SearchFilter::FieldValues likeMatch;
likeMatch[Database::SearchFilter::Field::Artist].push_back(keyword);
filter.likeMatches.push_back(likeMatch);
}
SearchFilter filter;
for (std::string keyword : keywords)
filter.nameLikeMatch[SearchFilter::Field::Artist].push_back(keyword);
artistSearch->search(filter, 3);
}
{
Database::SearchFilter filter;
SearchFilter filter;
BOOST_FOREACH(std::string keyword, keywords)
{
Database::SearchFilter::FieldValues likeMatch;
likeMatch[Database::SearchFilter::Field::Track].push_back(keyword);
filter.likeMatches.push_back(likeMatch);
}
filter.nameLikeMatch[SearchFilter::Field::Track].push_back(keyword);
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();
trackSearch->hide();
artistSearch->show();
}));
artistSearch->artistSelected().connect(std::bind([=] (std::string artist) {
artistSearch->artistSelected().connect(std::bind([=] (Artist::id_type artistId)
{
artistSearch->hide();
trackSearch->hide();
releaseSearch->show();
Database::SearchFilter filter;
filter.exactMatch[Database::SearchFilter::Field::Artist].push_back(artist);
SearchFilter filter;
filter.idMatch[SearchFilter::Field::Artist].push_back(artistId);
releaseSearch->search(filter, 20);
}, std::placeholders::_1));
releaseSearch->moreReleasesSelected().connect(std::bind([=] {
releaseSearch->moreReleasesSelected().connect(std::bind([=]
{
artistSearch->hide();
trackSearch->hide();
releaseSearch->show();
}));
releaseSearch->releaseSelected().connect(std::bind([=] (std::string release)
releaseSearch->releaseSelected().connect(std::bind([=] (Release::id_type releaseId)
{
artistSearch->hide();
releaseSearch->hide();
trackSearch->show();
// TODO load track search with selected release
Database::SearchFilter filter;
filter.exactMatch[Database::SearchFilter::Field::Release].push_back(release);
SearchFilter filter;
filter.idMatch[SearchFilter::Field::Release].push_back(releaseId);
trackSearch->search(filter, 20);
}, std::placeholders::_1));
@@ -185,13 +175,13 @@ Audio::Audio(Wt::WContainerWidget *parent)
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;
// TODO reduce transaction scope here
Wt::Dbo::Transaction transaction(DboSession());
Database::Track::pointer track = Database::Track::getById(DboSession(), id);
Track::pointer track = Track::getById(DboSession(), id);
if (track)
{
@@ -217,7 +207,7 @@ Audio::Audio(Wt::WContainerWidget *parent)
// Initially, populate the widgets using an empty search
{
Database::SearchFilter filter; // empty filter
SearchFilter filter; // empty filter
artistSearch->search(filter, 3);
releaseSearch->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);
_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 Mobile {
using namespace Database;
ReleaseSearch::ReleaseSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_resCount(0)
@@ -59,15 +61,9 @@ ReleaseSearch::search(Database::SearchFilter filter, size_t max)
void
ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb)
{
Wt::Dbo::Transaction transaction(DboSession());
std::vector<std::string> releases;
{
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);
}
std::vector<Release::pointer> releases = Release::getByFilter(DboSession(), filter, _resCount, nb + 1);
bool expectMoreResults;
if (releases.size() == nb + 1)
@@ -78,20 +74,20 @@ ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb)
else
expectMoreResults = false;
BOOST_FOREACH(std::string release, releases)
for (Release::pointer release : releases)
{
Wt::WTemplate* releaseWidget = new Wt::WTemplate(this);
releaseWidget->setTemplateText(Wt::WString::tr("mobile-release-res"));
Wt::WImage *cover = new Wt::WImage();
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("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([=] {
_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);
// Slots
Wt::Signal<std::string>& releaseSelected() { return _sigReleaseSelected;}
Wt::Signal<void>& moreReleasesSelected() { return _sigMoreReleasesSelected;}
Wt::Signal<Database::Release::id_type>& releaseSelected() { return _sigReleaseSelected;}
Wt::Signal<void>& moreReleasesSelected() { return _sigMoreReleasesSelected;}
private:
Wt::Signal<std::string> _sigReleaseSelected;
Wt::Signal<void> _sigMoreReleasesSelected;
Wt::Signal<Database::Release::id_type> _sigReleaseSelected;
Wt::Signal<void> _sigMoreReleasesSelected;
void clear(void);
void addResults(Database::SearchFilter filter, size_t nb);
+7 -5
View File
@@ -30,6 +30,8 @@
namespace UserInterface {
namespace Mobile {
using namespace Database;
TrackSearch::TrackSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_resCount(0)
@@ -62,7 +64,7 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb)
{
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;
if (tracks.size() == nb + 1)
@@ -73,7 +75,7 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb)
else
expectMoreResults = false;
BOOST_FOREACH(Database::Track::pointer track, tracks)
for (Track::pointer track : tracks)
{
Wt::WTemplate* trackWidget = new Wt::WTemplate(this);
trackWidget->setTemplateText(Wt::WString::tr("mobile-track-res"));
@@ -90,11 +92,11 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb)
title->setStyleClass("mobile-track");
container->addWidget(title);
if (!track->getArtistName().empty()
|| !track->getReleaseName().empty())
if (!track->getArtist()->getName().empty()
|| !track->getRelease()->getName().empty())
{
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");
container->addWidget(artistRelease);
}
+62 -56
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <Wt/WApplication>
#include <Wt/Http/Response>
@@ -73,9 +71,9 @@ CoverResource::getDefaultCover(std::size_t size)
}
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
@@ -101,76 +99,84 @@ CoverResource::putCover(Wt::Http::Response& response, const CoverArt::CoverArt&
void
CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{
// Get the id of the track
// Retrieve parameters
const std::string *trackIdStr = request.getParameter("trackid");
const std::string *releaseIdStr = request.getParameter("releaseid");
const std::string *sizeStr = request.getParameter("size");
const std::string *releaseStr = request.getParameter("release");
std::vector<CoverArt::CoverArt> covers;
// Mandatory parameter size
if (!sizeStr)
return;
std::size_t size = std::stol(*sizeStr);
if (size > maxSize)
return;
if (trackIdStr)
try
{
Database::Track::id_type trackId = std::stol(*trackIdStr);
boost::filesystem::path path;
Database::Track::CoverType coverType = Database::Track::CoverType::None;
std::vector<CoverArt::CoverArt> covers;
// 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
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)
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), releaseId);
}
for (CoverArt::CoverArt& cover : covers)
{
if (cover.scale(size))
{
coverType = track->getCoverType();
path = track->getPath();
putCover(response, cover);
return;
}
}
switch (coverType)
{
case Database::Track::CoverType::Embedded:
covers = CoverArt::Grabber::instance().getFromTrack(path);
break;
// If no cover found, just send default one
putCover(response, getDefaultCover(size));
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
std::unique_lock<std::mutex> lock(_mutex);
Wt::Dbo::Transaction transaction(_db.getSession());
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), *releaseStr);
LMS_LOG(MOD_UI, SEV_ERROR) << "Invalid argument: " << e.what();
}
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
+1 -1
View File
@@ -39,7 +39,7 @@ class CoverResource : public Wt::WResource
CoverResource(Database::Handler& db, Wt::WObject *parent = 0);
~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 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 = \
$(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 = \
$(srcdir)/CheckDatabaseUser.cpp \
$(top_srcdir)/src/logger/Logger.cpp \
$(top_srcdir)/src/database/Artist.cpp \
$(top_srcdir)/src/database/Playlist.cpp \
$(top_srcdir)/src/database/Release.cpp \
$(top_srcdir)/src/database/Track.cpp \
$(top_srcdir)/src/database/DatabaseHandler.cpp \
$(top_srcdir)/src/database/MediaDirectory.cpp \
$(top_srcdir)/src/database/SearchFilter.cpp \
$(top_srcdir)/src/database/SqlQuery.cpp \
$(top_srcdir)/src/database/User.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 = \
$(srcdir)/DatabaseIntegrity.cpp \
$(top_srcdir)/src/logger/Logger.cpp \
$(top_srcdir)/src/database/Artist.cpp \
$(top_srcdir)/src/database/Playlist.cpp \
$(top_srcdir)/src/database/Track.cpp \
$(top_srcdir)/src/database/DatabaseHandler.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/User.cpp \
$(top_srcdir)/src/database/Video.cpp
+129 -82
View File
@@ -34,40 +34,52 @@
struct GenreInfo
{
uint64_t id;
std::string name;
GenreInfo() : id(0) {}
};
std::ostream& operator<<(std::ostream& os, const GenreInfo& info)
{
os << "name = '" << info.name << "'";
os << "name = '" << info.name << "'(" << info.id << ")";
return os;
}
struct ArtistInfo
{
uint64_t id;
std::string mbid;
std::string name;
ArtistInfo() :id(0) {}
};
std::ostream& operator<<(std::ostream& os, const ArtistInfo& info)
{
os << "name = '" << info.name << "'";
os << "name = '" << info.name << "'(" << info.id << ")";
return os;
}
struct ReleaseInfo
{
uint64_t id;
std::string mbid;
std::string name;
ReleaseInfo() : id(0) {}
};
std::ostream& operator<<(std::ostream& os, const ReleaseInfo& info)
{
os << "name = '" << info.name;
os << "name = '" << info.name << "'(" << info.id << ")";
return os;
}
struct TrackInfo
{
uint64_t id;
std::string mbid;
uint64_t release_id;
uint64_t artist_id;
@@ -103,8 +115,30 @@ struct Cover
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
{
public:
@@ -122,8 +156,7 @@ class TestClient
_socket.handshake(boost::asio::ssl::stream_base::client);
}
void getArtists(std::vector<ArtistInfo>& artists,
const std::vector<std::string>& genres = std::vector<std::string>())
void getArtists(std::vector<ArtistInfo>& artists, const SearchFilter& filter = SearchFilter())
{
const std::size_t requestedBatchSize = 128;
@@ -131,12 +164,12 @@ class TestClient
std::size_t res = 0;
while ((res = getArtists(artists, genres, offset, requestedBatchSize) ) > 0)
while ((res = getArtists(artists, filter, offset, requestedBatchSize) ) > 0)
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;
@@ -148,8 +181,7 @@ class TestClient
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetArtistList);
request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_size(size);
request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_offset(offset);
BOOST_FOREACH(const std::string& genre, genres)
request.mutable_audio_collection_request()->mutable_get_artists()->add_genre(genre);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_artists()->mutable_search_filter());
sendMsg(request);
@@ -166,11 +198,18 @@ class TestClient
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!");
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 );
nbArtists++;
@@ -179,19 +218,19 @@ class TestClient
return nbArtists;
}
void getGenres(std::vector<GenreInfo>& genres)
void getGenres(std::vector<GenreInfo>& genres, const SearchFilter& filter = SearchFilter())
{
const std::size_t requestedBatchSize = 8;
std::size_t offset = 0;
std::size_t res = 0;
while ((res = getGenres(genres, offset, requestedBatchSize) ) > 0)
while ((res = getGenres(genres, filter, offset, requestedBatchSize) ) > 0)
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;
@@ -203,6 +242,7 @@ class TestClient
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_offset(offset);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_genres()->mutable_search_filter());
sendMsg(request);
@@ -219,11 +259,16 @@ class TestClient
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!");
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 );
nbAdded++;
@@ -231,21 +276,19 @@ class TestClient
return nbAdded;
}
void getReleases(std::vector<ReleaseInfo>& releases,
const std::vector<std::string>& artists = std::vector<std::string>(),
const std::vector<std::string>& genres = std::vector<std::string>())
void getReleases(std::vector<ReleaseInfo>& releases, const SearchFilter& filter = SearchFilter())
{
const std::size_t requestedBatchSize = 256;
std::size_t offset = 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;
}
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;
@@ -257,11 +300,7 @@ class TestClient
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetReleaseList);
request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_size(size);
request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_offset(offset);
BOOST_FOREACH(const std::string& artist, artists)
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);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_releases()->mutable_search_filter());
sendMsg(request);
@@ -284,11 +323,16 @@ class TestClient
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!");
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 );
nbAdded++;
@@ -296,25 +340,17 @@ class TestClient
return nbAdded;
}
void getTracks(std::vector<TrackInfo>& tracks,
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>())
void getTracks(std::vector<TrackInfo>& tracks, const SearchFilter& filter = SearchFilter())
{
const std::size_t requestedBatchSize = 0;
std::size_t offset = 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;
}
std::size_t getTracks(std::vector<TrackInfo>& tracks,
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 getTracks(std::vector<TrackInfo>& tracks, const SearchFilter& filter, std::size_t offset, std::size_t size)
{
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()->mutable_get_tracks()->mutable_batch_parameter()->set_size(size);
request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_offset(offset);
BOOST_FOREACH(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);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_search_filter());
sendMsg(request);
@@ -349,22 +379,27 @@ class TestClient
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;
track.id = response.audio_collection_response().track_list().tracks(i).id();
track.name = response.audio_collection_response().track_list().tracks(i).name();
track.duration = boost::posix_time::seconds(response.audio_collection_response().track_list().tracks(i).duration_secs());
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();
track.id = respTrack.id();
if (respTrack.has_mbid())
track.mbid = respTrack.mbid();
if (response.audio_collection_response().track_list().tracks(i).has_disc_number())
track.disc_number = response.audio_collection_response().track_list().tracks(i).disc_number();
track.name = respTrack.name();
track.duration = boost::posix_time::seconds(respTrack.duration_secs());
if (response.audio_collection_response().track_list().tracks(i).has_release_date())
track.date = response.audio_collection_response().track_list().tracks(i).release_date();
if (respTrack.has_track_number())
track.track_number = respTrack.track_number();
if (response.audio_collection_response().track_list().tracks(i).has_original_release_date())
track.original_date = response.audio_collection_response().track_list().tracks(i).original_release_date();
if (respTrack.has_disc_number())
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 );
@@ -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
Remote::ClientMessage request;
@@ -428,7 +463,7 @@ class TestClient
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetCoverArt);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( Remote::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_release( 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 );
sendMsg(request);
@@ -772,7 +807,7 @@ int main()
TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5080));
// 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!");
// **** REVISION ***
@@ -786,7 +821,7 @@ int main()
client.getArtists(artists);
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;
// ***** Genres *********
@@ -795,14 +830,14 @@ int main()
client.getGenres(genres);
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;
// **** Releases ******
std::cout << "Getting releases..." << std::endl;
std::vector<ReleaseInfo> releases;
client.getReleases(releases);
BOOST_FOREACH(const ReleaseInfo& release, releases)
for (const ReleaseInfo& release : releases)
std::cout << "Release: '" << release << "'" << std::endl;
// **** Tracks ******
@@ -811,7 +846,7 @@ int main()
client.getTracks(tracks);
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;
// Caution: long test!
@@ -819,14 +854,17 @@ int main()
{
std::cout << "Getting artist for each genre..." << std::endl;
// 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 << "'... ";
SearchFilter filter;
filter.genreIds.push_back(genre.id);
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;
BOOST_FOREACH(const ArtistInfo& artist, artists)
for (const ArtistInfo& artist : artists)
std::cout << "Genre '" << genre.name << "' -> Artist: " << artist << std::endl;
}
}
@@ -834,14 +872,17 @@ int main()
if (test(Test::ReleaseFilterArtist))
{
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 << "'... ";
SearchFilter filter;
filter.artistIds.push_back(artist.id);
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;
BOOST_FOREACH(const ReleaseInfo& release, releases)
for (const ReleaseInfo& release : releases)
std::cout << "Artist '" << artist.name << "' -> Release: '" << release << "'" << std::endl;
}
}
@@ -849,11 +890,14 @@ int main()
if (test(Test::ReleaseFilterGenre))
{
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 << "'... ";
SearchFilter filter;
filter.genreIds.push_back(genre.id);
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;
BOOST_FOREACH(const ReleaseInfo& release, releases)
@@ -865,13 +909,16 @@ int main()
{
std::cout << "Getting tracks for each artist..." << std::endl;
// 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;
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;
BOOST_FOREACH(const TrackInfo& track, tracks)
for (const TrackInfo& track : tracks)
std::cout << "Artist '" << artist.name << "', track: '" << track << "'" << std::endl;
}
}
@@ -880,19 +927,19 @@ int main()
if (test(Test::CoverByRelease))
{
std::cout << "Getting cover for each release..." << std::endl;
BOOST_FOREACH(const ReleaseInfo& release, releases)
for (const ReleaseInfo& release : releases)
{
std::vector<Cover> coverArts;
client.getCoverRelease(coverArts, release.name);
client.getCoverRelease(coverArts, release.id);
if (writeCovers)
{
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::ofstream out(oss.str().c_str());
BOOST_FOREACH(unsigned char c, coverArt.data)
for (unsigned char c : coverArt.data)
out.put(c);
}
}
@@ -904,7 +951,7 @@ int main()
if (test(Test::CoverByTrack))
{
std::cout << "Getting cover for each track..." << std::endl;
BOOST_FOREACH(const TrackInfo& track, tracks)
for (const TrackInfo& track : tracks)
{
std::vector<Cover> coverArt;
client.getCoverTrack(coverArt, track.id);
@@ -916,7 +963,7 @@ int main()
if (test(Test::Transcode))
{
BOOST_FOREACH(const TrackInfo& track, tracks)
for (const TrackInfo& track : tracks)
{
std::vector<unsigned char> 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.