Merge branch 'database' into develop
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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!";
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <Wt/Dbo/Dbo>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Database {
|
||||
|
||||
class PlaylistEntry;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace MetaData
|
||||
AudioStreams, // vector<AudioStream>
|
||||
VideoStreams, // vector<VideoStream>
|
||||
SubtitleStreams, // vector<SubtitleStream>
|
||||
MusicBrainzArtistID, // string
|
||||
MusicBrainzAlbumID, // string
|
||||
};
|
||||
|
||||
// Used by Streams
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user