[DB] Generalized Genre to Cluster
This commit is contained in:
@@ -6,7 +6,6 @@ lms_SOURCES = \
|
||||
$(srcdir)/av/AvTranscoder.cpp \
|
||||
$(srcdir)/cover/CoverArtGrabber.cpp \
|
||||
$(srcdir)/database/Artist.cpp \
|
||||
$(srcdir)/database/Classification.cpp \
|
||||
$(srcdir)/database/DatabaseClassifier.cpp \
|
||||
$(srcdir)/database/DatabaseHandler.cpp \
|
||||
$(srcdir)/database/DatabaseUpdater.cpp \
|
||||
|
||||
@@ -126,7 +126,7 @@ 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");
|
||||
= 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 cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
@@ -140,7 +140,7 @@ Artist::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
SqlQuery sqlQuery = generatePartialQuery(filter);
|
||||
|
||||
Wt::Dbo::Query<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");
|
||||
= 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 cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Database
|
||||
{
|
||||
|
||||
class Track;
|
||||
class Genre;
|
||||
class Cluster;
|
||||
class Release;
|
||||
|
||||
class Artist : public Wt::Dbo::Dbo<Artist>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Track.hpp"
|
||||
|
||||
#include "Classification.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
Feature::Feature(Wt::Dbo::ptr<Track> track, const std::string& type, const std::string& value)
|
||||
: _type(type),
|
||||
_value(value),
|
||||
_track(track)
|
||||
{
|
||||
}
|
||||
|
||||
Feature::pointer
|
||||
Feature::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, const std::string& type, const std::string& value)
|
||||
{
|
||||
return session.add(new Feature(track, type, value));
|
||||
}
|
||||
|
||||
std::vector<Feature::pointer>
|
||||
Feature::getByTrack(Wt::Dbo::Session& session, Track::id_type trackId, const std::string& type)
|
||||
{
|
||||
Wt::Dbo::collection<pointer> res = session.find<Feature>().where("track_id = ? AND type = ?").bind( trackId).bind(type);
|
||||
return std::vector<pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Wt/Dbo/Dbo>
|
||||
|
||||
namespace Database {
|
||||
|
||||
class Track;
|
||||
|
||||
class Feature
|
||||
{
|
||||
public:
|
||||
typedef Wt::Dbo::ptr<Feature> pointer;
|
||||
|
||||
Feature() {}
|
||||
Feature(Wt::Dbo::ptr<Track> track, const std::string& type, const std::string& value);
|
||||
|
||||
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, const std::string& type, const std::string& value);
|
||||
|
||||
static std::vector<pointer> getByTrack(Wt::Dbo::Session& session, Track::id_type trackId, const std::string& type);
|
||||
|
||||
std::string getType(void) const { return _type; }
|
||||
std::string getValue(void) const { return _value; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _type, "type");
|
||||
Wt::Dbo::field(a, _value, "value");
|
||||
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _type;
|
||||
std::string _value;
|
||||
|
||||
Wt::Dbo::ptr<Track> _track;
|
||||
};
|
||||
|
||||
class Classification
|
||||
{
|
||||
public:
|
||||
typedef Wt::Dbo::ptr<Classification> pointer;
|
||||
|
||||
Classification() {}
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _type, "type");
|
||||
Wt::Dbo::field(a, _value, "value");
|
||||
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::string _type;
|
||||
int _value;
|
||||
|
||||
Wt::Dbo::ptr<Track> _track;
|
||||
};
|
||||
|
||||
} // namespace Database
|
||||
|
||||
@@ -81,9 +81,8 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||
|
||||
|
||||
_session.mapClass<Database::Artist>("artist");
|
||||
_session.mapClass<Database::Genre>("genre");
|
||||
_session.mapClass<Database::Cluster>("cluster");
|
||||
_session.mapClass<Database::Track>("track");
|
||||
_session.mapClass<Database::Classification>("classification");
|
||||
_session.mapClass<Database::Feature>("feature");
|
||||
_session.mapClass<Database::Playlist>("playlist");
|
||||
_session.mapClass<Database::PlaylistEntry>("playlist_entry");
|
||||
@@ -102,10 +101,11 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||
|
||||
_session.createTables();
|
||||
_session.execute("CREATE INDEX artist_name_idx ON artist(name)");
|
||||
_session.execute("CREATE INDEX genre_name_idx ON genre(name)");
|
||||
_session.execute("CREATE INDEX cluster_name_idx ON cluster(name)");
|
||||
_session.execute("CREATE INDEX cluster_type_idx ON cluster(type)");
|
||||
_session.execute("CREATE INDEX release_name_idx ON release(name)");
|
||||
_session.execute("CREATE INDEX track_name_idx ON track(name)");
|
||||
_session.execute("CREATE INDEX feature_type ON feature(type)");
|
||||
_session.execute("CREATE INDEX feature_type_idx ON feature(type)");
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what();
|
||||
|
||||
@@ -378,22 +378,22 @@ Updater::getRelease( const boost::filesystem::path& file, const std::string& nam
|
||||
return Release::getNone( _db->getSession() );
|
||||
}
|
||||
|
||||
std::vector<Genre::pointer>
|
||||
Updater::getGenres( const std::list<std::string>& names)
|
||||
std::vector<Cluster::pointer>
|
||||
Updater::getGenreClusters( const std::list<std::string>& names)
|
||||
{
|
||||
std::vector< Genre::pointer > genres;
|
||||
std::vector< Cluster::pointer > genres;
|
||||
|
||||
for (const std::string& name : names)
|
||||
{
|
||||
Genre::pointer genre ( Genre::getByName(_db->getSession(), name) );
|
||||
Cluster::pointer genre ( Cluster::get(_db->getSession(), "Genre", name) );
|
||||
if (!genre)
|
||||
genre = Genre::create(_db->getSession(), name);
|
||||
genre = Cluster::create(_db->getSession(), "Genre", name);
|
||||
|
||||
genres.push_back( genre );
|
||||
}
|
||||
|
||||
if (genres.empty())
|
||||
genres.push_back( Genre::getNone( _db->getSession() ));
|
||||
genres.push_back( Cluster::getNone( _db->getSession() ));
|
||||
|
||||
return genres;
|
||||
}
|
||||
@@ -480,15 +480,16 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
||||
title = file.filename().string();
|
||||
}
|
||||
|
||||
// ***** Genres
|
||||
std::vector< Genre::pointer > genres;
|
||||
// ***** Clusters
|
||||
std::vector< Cluster::pointer > genres;
|
||||
{
|
||||
std::list<std::string> genreList;
|
||||
|
||||
if (items.find(MetaData::Type::Genres) != items.end())
|
||||
genreList = boost::any_cast< std::list<std::string> > (items[MetaData::Type::Genres]);
|
||||
|
||||
genres = getGenres( genreList );
|
||||
// TODO rename
|
||||
genres = getGenreClusters( genreList );
|
||||
}
|
||||
assert( !genres.empty() );
|
||||
|
||||
@@ -550,18 +551,19 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
||||
track.modify()->setAddedTime( boost::posix_time::second_clock::local_time() );
|
||||
|
||||
{
|
||||
std::string trackGenreList;
|
||||
std::string trackClusterList;
|
||||
// Product genre list
|
||||
for (Genre::pointer genre : genres)
|
||||
for (Cluster::pointer genre : genres)
|
||||
{
|
||||
if (!trackGenreList.empty())
|
||||
trackGenreList += ", ";
|
||||
trackGenreList += genre->getName();
|
||||
if (!trackClusterList.empty())
|
||||
trackClusterList += ", ";
|
||||
trackClusterList += genre->getName();
|
||||
|
||||
genre.modify()->addTrack(track);
|
||||
}
|
||||
|
||||
track.modify()->setGenres( trackGenreList );
|
||||
track.modify()->setGenres( trackClusterList );
|
||||
}
|
||||
track.modify()->setGenres( genres );
|
||||
|
||||
if (items.find(MetaData::Type::TrackNumber) != items.end())
|
||||
track.modify()->setTrackNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TrackNumber]) );
|
||||
@@ -599,11 +601,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
||||
track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None );
|
||||
}
|
||||
|
||||
Track::id_type trackId = track.id();
|
||||
|
||||
transaction.commit();
|
||||
|
||||
_sigTrackChanged.emit(true, trackId);
|
||||
_sigTrackChanged.emit(true, track.id());
|
||||
}
|
||||
|
||||
|
||||
@@ -720,12 +720,12 @@ Updater::checkAudioFiles( Stats& stats )
|
||||
}
|
||||
}
|
||||
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking Genres...";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking Clusters...";
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(_db->getSession());
|
||||
|
||||
// Now process orphan Genre (no track)
|
||||
auto genres = Genre::getAll(_db->getSession());
|
||||
// Now process orphan Cluster (no track)
|
||||
auto genres = Cluster::getAll(_db->getSession());
|
||||
for (auto genre : genres)
|
||||
{
|
||||
if (genre->getTracks().size() == 0)
|
||||
|
||||
@@ -100,7 +100,7 @@ class Updater
|
||||
// Helpers
|
||||
Database::Artist::pointer getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& MBID);
|
||||
Database::Release::pointer getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& MBID);
|
||||
std::vector<Database::Genre::pointer> getGenres( const std::list<std::string>& names);
|
||||
std::vector<Database::Cluster::pointer> getGenreClusters( const std::list<std::string>& names);
|
||||
void updateFileExtensions();
|
||||
|
||||
// Audio
|
||||
|
||||
@@ -93,7 +93,7 @@ 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");
|
||||
= 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 cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
@@ -108,7 +108,7 @@ Release::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
|
||||
// TODO DATE of RELEASE
|
||||
Wt::Dbo::Query<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");
|
||||
= 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 cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
|
||||
@@ -60,9 +60,9 @@ SqlQuery generatePartialQuery(SearchFilter& filter)
|
||||
likeWhereClause.Or( WhereClause("r.name LIKE ?") ).bind("%%" + name + "%%");
|
||||
break;
|
||||
|
||||
case SearchFilter::Field::Genre:
|
||||
case SearchFilter::Field::Cluster:
|
||||
for (const std::string& name : nameLikeMatch.second)
|
||||
likeWhereClause.Or( WhereClause("g.name LIKE ?") ).bind("%%" + name + "%%");
|
||||
likeWhereClause.Or( WhereClause("c.name LIKE ?") ).bind("%%" + name + "%%");
|
||||
break;
|
||||
|
||||
case SearchFilter::Field::Track:
|
||||
@@ -97,10 +97,10 @@ SqlQuery generatePartialQuery(SearchFilter& filter)
|
||||
idWhereClause.Or(oss.str());
|
||||
}
|
||||
break;
|
||||
case SearchFilter::Field::Genre:
|
||||
case SearchFilter::Field::Cluster:
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "g.id IN (" << idMatch.second << ")";
|
||||
oss << "c.id IN (" << idMatch.second << ")";
|
||||
idWhereClause.Or(oss.str());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -38,8 +38,8 @@ class SearchFilter
|
||||
enum class Field {
|
||||
Artist, // artist
|
||||
Release, // release
|
||||
Genre, // genre
|
||||
Track, // track
|
||||
Cluster, // cluster
|
||||
};
|
||||
|
||||
typedef std::vector<std::map<Field, std::vector<std::string> > > NameLikeMatchType;
|
||||
|
||||
+63
-50
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
* Copyright (C) 2013-2016 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
@@ -51,17 +51,6 @@ Track::getAllIds(Wt::Dbo::Session& session)
|
||||
return std::vector<Track::id_type>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
void
|
||||
Track::setGenres(std::vector<Genre::pointer> genres)
|
||||
{
|
||||
if (_genres.size())
|
||||
_genres.clear();
|
||||
|
||||
for (Genre::pointer genre : genres) {
|
||||
_genres.insert( genre );
|
||||
}
|
||||
}
|
||||
|
||||
Track::pointer
|
||||
Track::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p)
|
||||
{
|
||||
@@ -108,12 +97,12 @@ Track::getChecksumDuplicates(Wt::Dbo::Session& session)
|
||||
return std::vector<pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
std::vector< Genre::pointer >
|
||||
Track::getGenres(void) const
|
||||
std::vector< Cluster::pointer >
|
||||
Track::getClusters(void) const
|
||||
{
|
||||
std::vector< Genre::pointer > genres;
|
||||
std::copy(_genres.begin(), _genres.end(), std::back_inserter(genres));
|
||||
return genres;
|
||||
std::vector< Cluster::pointer > clusters;
|
||||
std::copy(_clusters.begin(), _clusters.end(), std::back_inserter(clusters));
|
||||
return clusters;
|
||||
}
|
||||
|
||||
std::vector< Wt::Dbo::ptr<Feature> >
|
||||
@@ -130,7 +119,7 @@ Track::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
SqlQuery sqlQuery = generatePartialQuery(filter);
|
||||
|
||||
Wt::Dbo::Query<pointer> query
|
||||
= 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");
|
||||
= session.query<pointer>( "SELECT t FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
@@ -144,7 +133,7 @@ Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
SqlQuery sqlQuery = generatePartialQuery(filter);
|
||||
|
||||
Wt::Dbo::Query<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");
|
||||
= 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 cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
@@ -157,7 +146,7 @@ Track::getStats(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
{
|
||||
SqlQuery sqlQuery = generatePartialQuery(filter);
|
||||
|
||||
Wt::Dbo::Query<StatsQueryResult> query = session.query<StatsQueryResult>( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)");
|
||||
Wt::Dbo::Query<StatsQueryResult> query = session.query<StatsQueryResult>( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
@@ -213,56 +202,58 @@ Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQuer
|
||||
}
|
||||
|
||||
|
||||
Genre::Genre()
|
||||
Cluster::Cluster()
|
||||
{
|
||||
}
|
||||
|
||||
Genre::Genre(const std::string& name)
|
||||
: _name( std::string(name, 0, _maxNameLength) )
|
||||
Cluster::Cluster(std::string type, std::string name)
|
||||
:
|
||||
_type( std::string(type, 0, _maxTypeLength)),
|
||||
_name( std::string(name, 0, _maxNameLength))
|
||||
{
|
||||
}
|
||||
|
||||
Wt::Dbo::collection<Genre::pointer>
|
||||
Genre::getAll(Wt::Dbo::Session& session)
|
||||
Wt::Dbo::collection<Cluster::pointer>
|
||||
Cluster::getAll(Wt::Dbo::Session& session)
|
||||
{
|
||||
return session.find<Genre>();
|
||||
return session.find<Cluster>();
|
||||
}
|
||||
|
||||
Genre::pointer
|
||||
Genre::getByName(Wt::Dbo::Session& session, const std::string& name)
|
||||
Cluster::pointer
|
||||
Cluster::get(Wt::Dbo::Session& session, std::string type, std::string name)
|
||||
{
|
||||
// TODO use like search
|
||||
return session.find<Genre>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
|
||||
return session.find<Cluster>().where("type = ?").where("name = ?").bind( std::string(type, 0, _maxTypeLength)).bind( std::string(name, 0, _maxNameLength));
|
||||
}
|
||||
|
||||
Genre::pointer
|
||||
Genre::getNone(Wt::Dbo::Session& session)
|
||||
Cluster::pointer
|
||||
Cluster::getNone(Wt::Dbo::Session& session)
|
||||
{
|
||||
pointer res = getByName(session, "<None>");
|
||||
pointer res = get(session, "<None>", "<None>");
|
||||
if (!res)
|
||||
res = create(session, "<None>");
|
||||
res = create(session, "<None>", "<None>");
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
Genre::isNone(void) const
|
||||
Cluster::isNone(void) const
|
||||
{
|
||||
return (_name == "<None>");
|
||||
return (_type == "<None>" && _name == "<None>");
|
||||
}
|
||||
|
||||
Genre::pointer
|
||||
Genre::create(Wt::Dbo::Session& session, const std::string& name)
|
||||
Cluster::pointer
|
||||
Cluster::create(Wt::Dbo::Session& session, std::string type, std::string name)
|
||||
{
|
||||
return session.add(new Genre(name));
|
||||
return session.add(new Cluster(type, name));
|
||||
}
|
||||
|
||||
Wt::Dbo::Query<Genre::pointer>
|
||||
Genre::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
Wt::Dbo::Query<Cluster::pointer>
|
||||
Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
{
|
||||
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");
|
||||
= session.query<pointer>( "SELECT g FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
@@ -270,13 +261,13 @@ Genre::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
return query;
|
||||
}
|
||||
|
||||
Wt::Dbo::Query<Genre::UIQueryResult>
|
||||
Genre::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
Wt::Dbo::Query<Cluster::UIQueryResult>
|
||||
Cluster::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
{
|
||||
SqlQuery sqlQuery = generatePartialQuery(filter);
|
||||
|
||||
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");
|
||||
= session.query<UIQueryResult>( "SELECT c.id, c.type, c.name, COUNT(DISTINCT t.id) FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name");
|
||||
|
||||
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
|
||||
query.bind(bindArg);
|
||||
@@ -285,26 +276,48 @@ Genre::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
|
||||
}
|
||||
|
||||
void
|
||||
Genre::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel<UIQueryResult>& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames)
|
||||
Cluster::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() == 2)
|
||||
if (columnNames.size() == 3)
|
||||
{
|
||||
model.addColumn( "g.name", columnNames[0] );
|
||||
model.addColumn( "COUNT(DISTINCT t.id)", columnNames[1] );
|
||||
model.addColumn( "c.type", columnNames[0] );
|
||||
model.addColumn( "c.name", columnNames[1] );
|
||||
model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2] );
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Genre::pointer>
|
||||
Genre::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
|
||||
std::vector<Cluster::pointer>
|
||||
Cluster::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());
|
||||
}
|
||||
|
||||
Feature::Feature(Wt::Dbo::ptr<Track> track, const std::string& type, const std::string& value)
|
||||
: _type(type),
|
||||
_value(value),
|
||||
_track(track)
|
||||
{
|
||||
}
|
||||
|
||||
Feature::pointer
|
||||
Feature::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, const std::string& type, const std::string& value)
|
||||
{
|
||||
return session.add(new Feature(track, type, value));
|
||||
}
|
||||
|
||||
std::vector<Feature::pointer>
|
||||
Feature::getByTrack(Wt::Dbo::Session& session, Track::id_type trackId, const std::string& type)
|
||||
{
|
||||
Wt::Dbo::collection<pointer> res = session.find<Feature>().where("track_id = ? AND type = ?").bind( trackId).bind(type);
|
||||
return std::vector<pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
|
||||
} // namespace Database
|
||||
|
||||
|
||||
+53
-27
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
* Copyright (C) 2013-2016 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
@@ -17,8 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _AUDIO_TYPES_HPP_
|
||||
#define _AUDIO_TYPES_HPP_
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -39,50 +38,56 @@ class Artist;
|
||||
class Release;
|
||||
class Track;
|
||||
class PlaylistEntry;
|
||||
class Classification;
|
||||
class Feature;
|
||||
|
||||
class Genre
|
||||
class Cluster
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Wt::Dbo::ptr<Genre> pointer;
|
||||
typedef Wt::Dbo::dbo_traits<Genre>::IdType id_type;
|
||||
typedef Wt::Dbo::ptr<Cluster> pointer;
|
||||
typedef Wt::Dbo::dbo_traits<Cluster>::IdType id_type;
|
||||
|
||||
Genre();
|
||||
Genre(const std::string& name);
|
||||
Cluster();
|
||||
Cluster(std::string type, std::string name);
|
||||
|
||||
// Find utility
|
||||
static pointer getByName(Wt::Dbo::Session& session, const std::string& name);
|
||||
static pointer get(Wt::Dbo::Session& session, std::string type, std::string name);
|
||||
static pointer getNone(Wt::Dbo::Session& session);
|
||||
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
|
||||
static Wt::Dbo::collection<Genre::pointer> getAll(Wt::Dbo::Session& session);
|
||||
static Wt::Dbo::collection<pointer> getAll(Wt::Dbo::Session& session);
|
||||
|
||||
// MVC models for the user interface
|
||||
// Genre ID, name, track count
|
||||
typedef boost::tuple<id_type, std::string, int> UIQueryResult;
|
||||
// ClusterID, type, name, track count
|
||||
typedef boost::tuple<id_type, std::string, 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);
|
||||
static pointer create(Wt::Dbo::Session& session, std::string type, std::string name);
|
||||
|
||||
// Accessors
|
||||
const std::string& getName(void) const { return _name; }
|
||||
const std::string& getType(void) const { return _type; }
|
||||
bool isNone(void) const;
|
||||
const Wt::Dbo::collection< Wt::Dbo::ptr<Track> >& getTracks() const { return _tracks;}
|
||||
|
||||
void addTrack(Wt::Dbo::ptr<Track> track) { _tracks.insert(track); }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::field(a, _type, "type");
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
static Wt::Dbo::Query<pointer> getQuery(Wt::Dbo::Session& session, SearchFilter filter);
|
||||
|
||||
static const std::size_t _maxNameLength = 128;
|
||||
static const std::size_t _maxTypeLength = 128;
|
||||
|
||||
std::string _type;
|
||||
std::string _name;
|
||||
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > _tracks;
|
||||
@@ -160,8 +165,6 @@ class Track
|
||||
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);
|
||||
void addClassification(Wt::Dbo::ptr<Classification> classification);
|
||||
void addFeature(Wt::Dbo::ptr<Feature> feature);
|
||||
|
||||
int getTrackNumber(void) const { return _trackNumber; }
|
||||
@@ -180,9 +183,7 @@ class Track
|
||||
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); }
|
||||
std::vector< Wt::Dbo::ptr<Classification> > getClassifications(void) const;
|
||||
std::vector< Cluster::pointer > getClusters(void) const;
|
||||
std::vector< Wt::Dbo::ptr<Feature> > getFeatures(void) const;
|
||||
|
||||
template<class Action>
|
||||
@@ -205,9 +206,8 @@ class Track
|
||||
Wt::Dbo::field(a, _MBID, "mbid");
|
||||
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _classifications, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _features, Wt::Dbo::ManyToOne, "track");
|
||||
}
|
||||
|
||||
@@ -237,17 +237,43 @@ class Track
|
||||
|
||||
Wt::Dbo::ptr<Artist> _artist;
|
||||
Wt::Dbo::ptr<Release> _release;
|
||||
Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<PlaylistEntry> > _playlistEntries;
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<Classification> > _classifications;
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<Feature> > _features;
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<Feature> > _features;
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<Cluster> > _clusters;
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<PlaylistEntry> > _playlistEntries;
|
||||
|
||||
};
|
||||
|
||||
/* A track feature */
|
||||
class Feature
|
||||
{
|
||||
public:
|
||||
typedef Wt::Dbo::ptr<Feature> pointer;
|
||||
|
||||
Feature() {}
|
||||
Feature(Wt::Dbo::ptr<Track> track, const std::string& type, const std::string& value);
|
||||
|
||||
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, const std::string& type, const std::string& value);
|
||||
|
||||
static std::vector<pointer> getByTrack(Wt::Dbo::Session& session, Track::id_type trackId, const std::string& type);
|
||||
|
||||
std::string getType(void) const { return _type; }
|
||||
std::string getValue(void) const { return _value; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _type, "type");
|
||||
Wt::Dbo::field(a, _value, "value");
|
||||
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _type;
|
||||
std::string _value;
|
||||
|
||||
Wt::Dbo::ptr<Track> _track;
|
||||
};
|
||||
|
||||
} // namespace database
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "Artist.hpp"
|
||||
#include "Track.hpp"
|
||||
#include "Playlist.hpp"
|
||||
#include "Classification.hpp"
|
||||
#include "Release.hpp"
|
||||
#include "Video.hpp"
|
||||
#include "MediaDirectory.hpp"
|
||||
|
||||
@@ -71,9 +71,9 @@ _playQueue(nullptr)
|
||||
// Filters
|
||||
Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout();
|
||||
|
||||
TableFilterGenre *filterGenre = new TableFilterGenre();
|
||||
filterLayout->addWidget(filterGenre);
|
||||
_filterChain.addFilter(filterGenre);
|
||||
TableFilterCluster *filterCluster = new TableFilterCluster();
|
||||
filterLayout->addWidget(filterCluster);
|
||||
_filterChain.addFilter(filterCluster);
|
||||
|
||||
TableFilterArtist *filterArtist = new TableFilterArtist();
|
||||
filterLayout->addWidget(filterArtist);
|
||||
@@ -213,7 +213,7 @@ _playQueue(nullptr)
|
||||
// Set the selected tracks to the play queue
|
||||
filterArtist->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||
filterRelease->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||
filterGenre->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||
filterCluster->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||
|
||||
// Play button
|
||||
// Set the selected tracks to the play queue
|
||||
|
||||
@@ -52,7 +52,7 @@ KeywordSearchFilter::getConstraint(Database::SearchFilter& filter)
|
||||
filter.nameLikeMatch.push_back({
|
||||
{Database::SearchFilter::Field::Artist, {value}},
|
||||
{Database::SearchFilter::Field::Release, {value}},
|
||||
{Database::SearchFilter::Field::Genre, {value}},
|
||||
{Database::SearchFilter::Field::Cluster, {value}},
|
||||
{Database::SearchFilter::Field::Track, {value}}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,23 +30,24 @@ namespace Desktop {
|
||||
|
||||
using namespace Database;
|
||||
|
||||
TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent)
|
||||
TableFilterCluster::TableFilterCluster(Wt::WContainerWidget* parent)
|
||||
: Wt::WTableView( parent ), Filter()
|
||||
{
|
||||
const std::vector<Wt::WString> columnNames = {"Genre", "Tracks"};
|
||||
const std::vector<Wt::WString> columnNames = {"Type", "Name", "Tracks"};
|
||||
|
||||
SearchFilter filter;
|
||||
|
||||
Genre::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
|
||||
Cluster::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
|
||||
|
||||
this->setSelectionMode(Wt::ExtendedSelection);
|
||||
this->setSortingEnabled(true);
|
||||
this->setAlternatingRowColors(true);
|
||||
this->setModel(&_queryModel);
|
||||
|
||||
this->setColumnWidth(1, 80);
|
||||
this->setColumnWidth(1, 120);
|
||||
this->setColumnWidth(2, 80);
|
||||
|
||||
this->selectionChanged().connect(this, &TableFilterGenre::emitUpdate);
|
||||
this->selectionChanged().connect(this, &TableFilterCluster::emitUpdate);
|
||||
|
||||
#if WT_VERSION >= 0X03030400
|
||||
this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal);
|
||||
@@ -74,25 +75,25 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent)
|
||||
}
|
||||
|
||||
void
|
||||
TableFilterGenre::layoutSizeChanged (int width, int height)
|
||||
TableFilterCluster::layoutSizeChanged (int width, int height)
|
||||
{
|
||||
std::size_t trackColumnSize = this->columnWidth(1).toPixels();
|
||||
std::size_t otherColumnSizes = this->columnWidth(1).toPixels() + this->columnWidth(2).toPixels();
|
||||
// Set the remaining size for the name column
|
||||
this->setColumnWidth(0, width - trackColumnSize - (7 * 2) - 2);
|
||||
this->setColumnWidth(0, width - otherColumnSizes - (7 * 3) - 2);
|
||||
}
|
||||
|
||||
// Set constraints on this filter
|
||||
void
|
||||
TableFilterGenre::refresh(SearchFilter& filter)
|
||||
TableFilterCluster::refresh(SearchFilter& filter)
|
||||
{
|
||||
this->clearSelection();
|
||||
|
||||
Genre::updateUIQueryModel(DboSession(), _queryModel, filter);
|
||||
Cluster::updateUIQueryModel(DboSession(), _queryModel, filter);
|
||||
}
|
||||
|
||||
// Get constraint created by this filter
|
||||
void
|
||||
TableFilterGenre::getConstraint(SearchFilter& filter)
|
||||
TableFilterCluster::getConstraint(SearchFilter& filter)
|
||||
{
|
||||
Wt::WModelIndexSet indexSet = this->selectedIndexes();
|
||||
|
||||
@@ -102,9 +103,9 @@ TableFilterGenre::getConstraint(SearchFilter& filter)
|
||||
continue;
|
||||
|
||||
// TODO set invisible track id
|
||||
Database::Genre::id_type id = _queryModel.resultRow( index.row() ).get<0>();
|
||||
Cluster::id_type id = _queryModel.resultRow( index.row() ).get<0>();
|
||||
|
||||
filter.idMatch[Database::SearchFilter::Field::Genre].push_back(id);
|
||||
filter.idMatch[Database::SearchFilter::Field::Cluster].push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
namespace UserInterface {
|
||||
namespace Desktop {
|
||||
|
||||
class TableFilterGenre : public Wt::WTableView, public Filter
|
||||
class TableFilterCluster : public Wt::WTableView, public Filter
|
||||
{
|
||||
public:
|
||||
TableFilterGenre(Wt::WContainerWidget* parent = 0);
|
||||
TableFilterCluster(Wt::WContainerWidget* parent = 0);
|
||||
|
||||
// Set constraints on this filter
|
||||
void refresh(Database::SearchFilter& filter);
|
||||
@@ -51,8 +51,7 @@ class TableFilterGenre : public Wt::WTableView, public Filter
|
||||
|
||||
SigDoubleClicked _sigDoubleClicked;
|
||||
|
||||
// Name, track count
|
||||
Wt::Dbo::QueryModel<Database::Genre::UIQueryResult> _queryModel;
|
||||
Wt::Dbo::QueryModel<Database::Cluster::UIQueryResult> _queryModel;
|
||||
};
|
||||
|
||||
class TableFilterArtist : public Wt::WTableView, public Filter
|
||||
|
||||
Reference in New Issue
Block a user