[DB] Reworked feature extraction

This commit is contained in:
emeric
2016-05-23 20:06:13 +02:00
parent d211c76cf8
commit af97d6af4d
16 changed files with 444 additions and 105 deletions
+4 -1
View File
@@ -7,18 +7,21 @@ lms_SOURCES = \
$(srcdir)/config/Config.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \
$(srcdir)/database/Artist.cpp \
$(srcdir)/database/DatabaseClassifier.cpp \
$(srcdir)/database/DatabaseFeatureExtractor.cpp \
$(srcdir)/database/DatabaseHandler.cpp \
$(srcdir)/database/DatabaseUpdater.cpp \
$(srcdir)/database/MediaDirectory.cpp \
$(srcdir)/database/Playlist.cpp \
$(srcdir)/database/Release.cpp \
$(srcdir)/database/SearchFilter.cpp \
$(srcdir)/database/Setting.cpp \
$(srcdir)/database/SqlQuery.cpp \
$(srcdir)/database/Track.cpp \
$(srcdir)/database/User.cpp \
$(srcdir)/database/Video.cpp \
$(srcdir)/database/cluster/DatabaseHighLevelCluster.cpp \
$(srcdir)/feature/FeatureExtractor.cpp \
$(srcdir)/feature/FeatureStore.cpp \
$(srcdir)/image/Image.cpp \
$(srcdir)/logger/Logger.cpp \
$(srcdir)/metadata/AvFormat.cpp \
+1
View File
@@ -22,6 +22,7 @@
#include <boost/filesystem.hpp>
#include <libconfig.h++>
// Used to get config values from configuration files
class Config
{
public:
+82
View File
@@ -0,0 +1,82 @@
/*
* 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 "config/Config.hpp"
#include "logger/Logger.hpp"
#include "feature/FeatureExtractor.hpp"
#include "feature/FeatureStore.hpp"
#include "DatabaseFeatureExtractor.hpp"
namespace Database {
static std::string getMBID(Track::id_type trackId)
{
Wt::Dbo::Transaction transaction(UpdaterDboSession());
Track::pointer track = Track::getById(UpdaterDboSession(), trackId);
return track->getMBID();
}
void
FeatureExtractor::processDatabaseUpdate(Updater::Stats stats)
{
bool fetchHighLevel = Config::instance().getBool("tag-highlevel-acousticbrainz", false);
bool fetchLowLevel = Config::instance().getBool("tag-similarity-acousticbrainz", false);
if (!fetchHighLevel && !fetchLowLevel)
{
LMS_LOG(DBUPDATER, INFO) << "No need to extract features";
return;
}
LMS_LOG(DBUPDATER, INFO) << "Processing tracks in order to extract features...";
std::vector<Track::id_type> trackIds = Track::getAllIds(UpdaterDboSession());
for (auto trackId : trackIds)
{
if (UpdaterQuitRequested())
return;
std::string mbid = getMBID(trackId);
if (mbid.empty())
{
LMS_LOG(DBUPDATER, DEBUG) << "No MBID for track " << trackId << ", skipping";
continue;
}
if (fetchLowLevel && !Feature::Store::instance().exists(UpdaterDboSession(), trackId, "low_level"))
{
boost::property_tree::ptree feature;
if (::Feature::Extractor::getLowLevel(feature, mbid))
Feature::Store::instance().set(UpdaterDboSession(), trackId, "low_level", feature);
}
if (fetchHighLevel && !Feature::Store::instance().exists(UpdaterDboSession(), trackId, "high_level"))
{
boost::property_tree::ptree feature;
if (::Feature::Extractor::getHighLevel(feature, mbid))
Feature::Store::instance().set(UpdaterDboSession(), trackId, "high_level", feature);
}
}
LMS_LOG(DBUPDATER, INFO) << "Features have been extracted";
}
} // namespace Database
+33
View File
@@ -0,0 +1,33 @@
/*
* 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 "database/DatabaseUpdater.hpp"
namespace Database {
class FeatureExtractor
{
public:
void processDatabaseUpdate(Updater::Stats stats);
};
} // namespace Database
+4 -8
View File
@@ -83,13 +83,12 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
_session.mapClass<Database::Artist>("artist");
_session.mapClass<Database::Cluster>("cluster");
_session.mapClass<Database::Track>("track");
_session.mapClass<Database::Feature>("feature");
_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");
_session.mapClass<Database::Setting>("setting");
_session.mapClass<Database::User>("user");
_session.mapClass<Database::AuthInfo>("auth_info");
@@ -101,13 +100,10 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
_session.createTables();
_session.execute("CREATE INDEX artist_name_idx ON artist(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 cluster_name_type_idx ON cluster(name, 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_idx ON feature(type)");
_session.execute("CREATE INDEX feature_track_type_idx ON feature(track_id,type)");
_session.execute("CREATE INDEX track_artist_idx ON track(artist_id)");
_session.execute("CREATE INDEX track_release_idx ON track(release_id)");
_session.execute("CREATE INDEX cluster_type_idx ON cluster(type)");
}
catch(std::exception& e) {
LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what();
+62 -64
View File
@@ -162,7 +162,6 @@ Updater::stop(void)
{
_running = false;
// TODO cancel all jobs (timer, ...)
_scheduleTimer.cancel();
_ioService.stop();
@@ -235,64 +234,67 @@ Updater::scheduleScan( boost::posix_time::ptime time)
void
Updater::process(boost::system::error_code err)
{
if (!err)
if (err)
return;
updateFileExtensions();
Stats stats;
checkAudioFiles(stats);
checkVideoFiles(stats);
std::vector<RootDirectory> rootDirectories;
{
updateFileExtensions();
Wt::Dbo::Transaction transaction(_db->getSession());
Stats stats;
checkAudioFiles(stats);
checkVideoFiles(stats);
std::vector<RootDirectory> rootDirectories;
{
Wt::Dbo::Transaction transaction(_db->getSession());
for (MediaDirectory::pointer directory : MediaDirectory::getAll(_db->getSession()))
rootDirectories.push_back( RootDirectory( directory->getType(), directory->getPath() ));
}
for (RootDirectory rootDirectory : rootDirectories)
{
if (!_running)
break;
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "'...";
processRootDirectory(rootDirectory, stats);
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "' DONE";
}
if (_running)
checkDuplicatedAudioFiles(stats);
LMS_LOG(DBUPDATER, INFO) << "Scan complete. Scanned = " << stats.nbScanned << ", Skipped = " << stats.nbSkipped << ", Changes = " << stats.nbChanges() << " (added = " << stats.nbAdded << ", nbRemoved = " << stats.nbRemoved << ", nbModified = " << stats.nbModified << "), Scan errors = " << stats.nbScanErrors << ", Not imported = " << stats.nbNotImported;
// Update database stats
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
{
Wt::Dbo::Transaction transaction(_db->getSession());
Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get(_db->getSession());
if (stats.nbChanges() > 0)
settings.modify()->setLastUpdate(now);
// Save the last scan only if it has been completed
if (_running)
settings.modify()->setLastScan(now);
// If the manual scan was required we can now set it to done
// Update only if the scan is complete!
if (settings->getManualScanRequested() && _running)
settings.modify()->setManualScanRequested(false);
}
scanComplete().emit(stats);
if (_running)
processNextJob();
for (MediaDirectory::pointer directory : MediaDirectory::getAll(_db->getSession()))
rootDirectories.push_back( RootDirectory( directory->getType(), directory->getPath() ));
}
for (RootDirectory rootDirectory : rootDirectories)
{
if (!_running)
break;
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "'...";
processRootDirectory(rootDirectory, stats);
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "' DONE";
}
if (_running)
{
checkDuplicatedAudioFiles(stats);
LMS_LOG(DBUPDATER, INFO) << "Processed all files, now calling listeners...";
scanComplete().emit(stats);
}
LMS_LOG(DBUPDATER, INFO) << "Scan complete. Scanned = " << stats.nbScanned << ", Skipped = " << stats.nbSkipped << ", Changes = " << stats.nbChanges() << " (added = " << stats.nbAdded << ", nbRemoved = " << stats.nbRemoved << ", nbModified = " << stats.nbModified << "), Scan errors = " << stats.nbScanErrors << ", Not imported = " << stats.nbNotImported;
// Update database stats
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
{
Wt::Dbo::Transaction transaction(_db->getSession());
Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get(_db->getSession());
if (stats.nbChanges() > 0)
settings.modify()->setLastUpdate(now);
// Save the last scan only if it has been completed
if (_running)
settings.modify()->setLastScan(now);
// If the manual scan was required we can now set it to done
// Update only if the scan is complete!
if (settings->getManualScanRequested() && _running)
settings.modify()->setManualScanRequested(false);
}
if (_running)
processNextJob();
}
void
@@ -538,12 +540,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{
LMS_LOG(DBUPDATER, INFO) << "Updating '" << file << "'";
// TODO Remove the songs from its clusters
// TODO Remove the features of this song
track.remove();
track.flush();
track = Track::create(_db->getSession(), file);
// Remove the songs from its clusters
for (auto cluster : track->getClusters())
cluster.remove();
stats.nbModified++;
}
@@ -614,8 +613,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
_sigTrackChanged.emit(true, track.id(), track->getMBID(), track->getPath());
}
void
void
Updater::processRootDirectory(RootDirectory rootDirectory, Stats& stats)
{
boost::system::error_code ec;
+14 -1
View File
@@ -73,6 +73,9 @@ class Updater
std::mutex& getMutex(void) { return _mutex; }
Database::Handler& getDb(void) { return *_db; }
bool quitRequested(void) const { return !_running;}
private:
Updater();
@@ -133,8 +136,18 @@ class Updater
MetaData::TagLibParser _metadataParser;
}; // class Updater
// Helper to get the updater session data
static inline Wt::Dbo::Session& UpdaterDboSession()
{
return Updater::instance().getDb().getSession();
}
static inline bool UpdaterQuitRequested()
{
return Updater::instance().quitRequested();
}
} // Database
+1 -4
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include "Types.hpp"
static std::string pathsToString(const std::vector<boost::filesystem::path>& paths)
@@ -111,8 +109,7 @@ MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p, Typ
void
MediaDirectory::eraseAll(Wt::Dbo::Session& session)
{
std::vector<MediaDirectory::pointer> dirs = getAll(session);
BOOST_FOREACH(MediaDirectory::pointer dir, dirs)
for (auto dir : getAll(session))
dir.remove();
}
+19 -13
View File
@@ -42,7 +42,7 @@ Extractor::init(void)
if (extractorPath.empty())
{
LMS_LOG(CLASSIFICATION, ERROR) << "Failed to find path to " << execName;
LMS_LOG(FEATURE, ERROR) << "Failed to find path to " << execName;
return false;
}
@@ -71,17 +71,17 @@ static bool fetchJSONData(boost::property_tree::ptree& pt, std::string url)
}
catch( curlpp::RuntimeError &e )
{
LMS_LOG(CLASSIFICATION, ERROR) << "curlpp error: " << e.what();
LMS_LOG(FEATURE, ERROR) << "curlpp error: " << e.what();
return false;
}
catch( curlpp::LogicError &e )
{
LMS_LOG(CLASSIFICATION, ERROR) << "curlpp error: " << e.what();
LMS_LOG(FEATURE, ERROR) << "curlpp error: " << e.what();
return false;
}
catch ( boost::property_tree::ptree_error& e)
{
LMS_LOG(CLASSIFICATION, ERROR) << "JSON paring failed: " << e.what();
LMS_LOG(FEATURE, ERROR) << "JSON paring failed: " << e.what();
return false;
}
@@ -92,7 +92,7 @@ static bool fetchJSONData(boost::property_tree::ptree& pt, std::string url)
bool
Extractor::getLowLevel(boost::property_tree::ptree& pt, std::string mbid)
{
LMS_LOG(CLASSIFICATION, DEBUG) << "Trying to fetch low level metadata for track '" << mbid << "' on AcousticBrainz";
LMS_LOG(FEATURE, DEBUG) << "Trying to fetch low level metadata for track '" << mbid << "' on AcousticBrainz";
boost::property_tree::ptree res;
if (!fetchJSONData(res, "https://acousticbrainz.org/" + mbid + "/low-level"))
@@ -101,14 +101,14 @@ Extractor::getLowLevel(boost::property_tree::ptree& pt, std::string mbid)
auto message = res.get_child_optional("message");
if (message)
{
LMS_LOG(CLASSIFICATION, ERROR) << "Cannot get data on AcousticBrainz: " << message->data();
LMS_LOG(FEATURE, ERROR) << "Track '" << mbid << "': cannot get data on AcousticBrainz: " << message->data();
return false;
}
auto lowlevel = res.get_child_optional("lowlevel");
if (!lowlevel)
{
LMS_LOG(CLASSIFICATION, ERROR) << "Low level data not found!";
LMS_LOG(FEATURE, ERROR) << "Track '" << mbid << "': low level data not found!";
return false;
}
@@ -122,7 +122,11 @@ Extractor::getLowLevel(boost::property_tree::ptree& pt, std::string mbid)
bool
Extractor::getHighLevel(boost::property_tree::ptree& pt, std::string mbid)
{
LMS_LOG(CLASSIFICATION, DEBUG) << "Trying to fetch high level metadata for track '" << mbid << "' on AcousticBrainz";
LMS_LOG(FEATURE, DEBUG) << "Trying to fetch high level metadata for track '" << mbid << "' on AcousticBrainz";
// TODO check MBID
if (mbid.empty())
return false;
boost::property_tree::ptree res;
if (!fetchJSONData(res, "https://acousticbrainz.org/" + mbid + "/high-level"))
@@ -131,17 +135,19 @@ Extractor::getHighLevel(boost::property_tree::ptree& pt, std::string mbid)
auto message = res.get_child_optional("message");
if (message)
{
LMS_LOG(CLASSIFICATION, ERROR) << "Cannot get data on AcousticBrainz: " << message->data();
LMS_LOG(FEATURE, ERROR) << "Track '" << mbid << "': cannot get data on AcousticBrainz: " << message->data();
return false;
}
auto lowlevel = res.get_child_optional("highlevel");
if (!lowlevel)
{
LMS_LOG(CLASSIFICATION, ERROR) << "High level data not found!";
LMS_LOG(FEATURE, ERROR) << "Track '" << mbid << "': high level data not found!";
return false;
}
res.erase("metadata");
pt = res;
return true;
@@ -151,7 +157,7 @@ Extractor::getHighLevel(boost::property_tree::ptree& pt, std::string mbid)
bool
Extractor::getLowLevel(boost::property_tree::ptree& pt, boost::filesystem::path path)
{
LMS_LOG(CLASSIFICATION, DEBUG) << "Extracting low level data from '" << path << "'";
LMS_LOG(FEATURE, DEBUG) << "Extracting low level data from '" << path << "'";
if (extractorPath.empty())
return false;
@@ -168,7 +174,7 @@ Extractor::getLowLevel(boost::property_tree::ptree& pt, boost::filesystem::path
in.open(extractorPath.string(), args);
if (!in.is_open())
{
LMS_LOG(CLASSIFICATION, ERROR) << "Exec failed!";
LMS_LOG(FEATURE, ERROR) << "Exec failed!";
return false;
}
@@ -198,7 +204,7 @@ Extractor::getLowLevel(boost::property_tree::ptree& pt, boost::filesystem::path
}
catch ( boost::property_tree::ptree_error& e)
{
LMS_LOG(CLASSIFICATION, ERROR) << "JSON paring failed: " << e.what();
LMS_LOG(FEATURE, ERROR) << "JSON parsing failed: " << e.what();
return false;
}
+2
View File
@@ -25,6 +25,8 @@
namespace Feature {
typedef boost::property_tree::ptree Type;
class Extractor
{
public:
+145
View File
@@ -0,0 +1,145 @@
/*
* 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 <boost/property_tree/json_parser.hpp>
#include "logger/Logger.hpp"
#include "config/Config.hpp"
#include "FeatureStore.hpp"
namespace Feature {
Store::Store()
{
}
Store&
Store::instance(void)
{
static Store instance;
return instance;
}
void
Store::reload(void)
{
_storePath = Config::instance().getString("features-dir-path", "");
if (!boost::filesystem::is_directory(_storePath))
{
LMS_LOG(DBUPDATER, ERROR) << "Feature directory '" << _storePath << "' not valid!";
throw std::runtime_error("Invalid feature directory '" + _storePath.string());
}
}
boost::filesystem::path getPath(boost::filesystem::path root, std::string mbid, std::string type)
{
return root / (mbid + "_" + type);
}
bool
Store::exists(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type)
{
if (_storePath.empty())
reload();
Wt::Dbo::Transaction transaction(session);
auto track = Database::Track::getById(session, trackId);
std::string mbid = track->getMBID();
transaction.commit();
if (mbid.empty())
return false;
boost::filesystem::path path = getPath(_storePath, mbid, type);
return boost::filesystem::exists(path);
}
bool
Store::get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, Type& feature)
{
if (_storePath.empty())
reload();
Wt::Dbo::Transaction transaction(session);
auto track = Database::Track::getById(session, trackId);
std::string mbid = track->getMBID();
transaction.commit();
if (mbid.empty())
return false;
boost::filesystem::path path = getPath(_storePath, mbid, type);
if (!boost::filesystem::exists(path))
return false;
try
{
std::ifstream iss(path.string().c_str(), std::ios::in);
boost::property_tree::json_parser::read_json(iss, feature);
}
catch (boost::property_tree::ptree_error& e)
{
LMS_LOG(FEATURE, ERROR) << "JSON parsing failed: " << e.what();
return false;
}
return true;
}
bool
Store::set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const Type& feature)
{
if (_storePath.empty())
reload();
Wt::Dbo::Transaction transaction(session);
auto track = Database::Track::getById(session, trackId);
std::string mbid = track->getMBID();
transaction.commit();
if (mbid.empty())
return false;
boost::filesystem::path path = getPath(_storePath, mbid, type);
try
{
std::ofstream oss(path.string().c_str(), std::ios::out);
boost::property_tree::json_parser::write_json(oss, feature);
}
catch (boost::property_tree::ptree_error& e)
{
LMS_LOG(FEATURE, ERROR) << "JSON writing failed: " << e.what();
return false;
}
return true;
}
} // namespace CoverArt
+50
View File
@@ -0,0 +1,50 @@
/*
* 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 <boost/filesystem/path.hpp>
#include "database/Types.hpp"
#include "FeatureExtractor.hpp"
namespace Feature {
class Store
{
public:
Store(const Store&) = delete;
Store& operator=(const Store&) = delete;
static Store& instance();
bool exists(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type);
bool get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, Type& feature);
bool set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const Type& feature);
void reload();
private:
Store();
boost::filesystem::path _storePath;
};
} // namespace CoverArt
+6 -6
View File
@@ -26,13 +26,13 @@ std::string getModuleName(Module mod)
case Module::AV: return "AV";
case Module::COVER: return "COVER";
case Module::DB: return "DB";
case Module::CLASSIFICATION: return "CLASSIFICATION";
case Module::DBUPDATER: return "DB UPDATER";
case Module::DBUPDATER: return "DB UPDATER";
case Module::FEATURE: return "FEATURE";
case Module::MAIN: return "MAIN";
case Module::METADATA: return "METADATA";
case Module::REMOTE: return "REMOTE";
case Module::SERVICE: return "SERVICE";
case Module::TRANSCODE: return "TRANSCODE";
case Module::METADATA: return "METADATA";
case Module::REMOTE: return "REMOTE";
case Module::SERVICE: return "SERVICE";
case Module::TRANSCODE: return "TRANSCODE";
case Module::UI: return "UI";
}
return "";
+1 -1
View File
@@ -38,10 +38,10 @@ enum class Severity
enum class Module
{
AV,
CLASSIFICATION,
COVER,
DB,
DBUPDATER,
FEATURE,
MAIN,
METADATA,
REMOTE,
+4 -5
View File
@@ -33,7 +33,7 @@ using namespace Database;
TableFilterCluster::TableFilterCluster(Wt::WContainerWidget* parent)
: Wt::WTableView( parent ), Filter()
{
const std::vector<Wt::WString> columnNames = {"Type", "Name", "Tracks"};
const std::vector<Wt::WString> columnNames = {"Tag", "Tracks"};
SearchFilter filter;
@@ -44,8 +44,7 @@ TableFilterCluster::TableFilterCluster(Wt::WContainerWidget* parent)
this->setAlternatingRowColors(true);
this->setModel(&_queryModel);
this->setColumnWidth(1, 120);
this->setColumnWidth(2, 80);
this->setColumnWidth(1, 80);
this->selectionChanged().connect(this, &TableFilterCluster::emitUpdate);
@@ -77,9 +76,9 @@ TableFilterCluster::TableFilterCluster(Wt::WContainerWidget* parent)
void
TableFilterCluster::layoutSizeChanged (int width, int height)
{
std::size_t otherColumnSizes = this->columnWidth(1).toPixels() + this->columnWidth(2).toPixels();
std::size_t trackColumnSize = this->columnWidth(1).toPixels();
// Set the remaining size for the name column
this->setColumnWidth(0, width - otherColumnSizes - (7 * 3) - 2);
this->setColumnWidth(0, width - trackColumnSize - (7 * 2) - 2);
}
// Set constraints on this filter