diff --git a/approot/templates.xml b/approot/templates.xml
index 82790cf2..80afcd58 100644
--- a/approot/templates.xml
+++ b/approot/templates.xml
@@ -347,6 +347,30 @@
+
${apply-button} ${discard-button} ${immediate-scan-button}
diff --git a/conf/lms.conf b/conf/lms.conf
index bc42e092..b482a6f1 100644
--- a/conf/lms.conf
+++ b/conf/lms.conf
@@ -4,10 +4,11 @@
# Must have write privileges in order to create and modify the file
db-path = "/storage/emeric/lms/lms.dev.db";
-# Listen port of the web server
-listen-port = 5082;
+# Cache directory for downloaded contents
+cache-dir-path = "/var/lms/cache";
-# Listen address of the web server
+# Listen port/addr of the web server
+listen-port = 5082;
listen-addr = "0.0.0.0";
# If enabled, these files have to exist and have correct permissions
@@ -23,16 +24,3 @@ approot = "/usr/share/lms/approot";
# Path to the wt_config.xml file
wt-config = "/etc/wt/wt_config.xml";
-# -- Settings to be moved in the web interface --
-
-features-dir-path = "/var/lms/features/";
-
-# Create tags from genres in metadata
-# tag-genres = true;
-
-# Create tags from the high level data on AcousticBrainz
-tag-highlevel-acousticbrainz = true;
-
-# Create similarity tags from the low level data on AcousticBrainz
-tag-similarity-acousticbrainz = true;
-
diff --git a/src/database/DatabaseClassifier.cpp b/src/database/DatabaseClassifier.cpp
index 45b8cb98..5aff1b00 100644
--- a/src/database/DatabaseClassifier.cpp
+++ b/src/database/DatabaseClassifier.cpp
@@ -42,7 +42,7 @@ Classifier::processTrackUpdate(bool added, Track::id_type trackId, std::string m
if (mbid.empty())
{
// TODO compute from file
- LMS_LOG(CLASSIFICATION, INFO) << "File '" << path << "' has no MBID: skipping feature extraction";
+ LMS_LOG(DBUPDATER, INFO) << "File '" << path << "' has no MBID: skipping feature extraction";
return;
}
@@ -332,7 +332,7 @@ static std::vector features =
{ "lowlevel.spectral_centroid.mean", 1, 1.0 },
// { "lowlevel.spectral_centroid.median", 1, 1.0 },
// { "lowlevel.spectral_centroid.min", 1, 1.0 },
-// { "lowlevel.spectral_centroid.var", 1, 1.0 },
+ { "lowlevel.spectral_centroid.var", 1, 1.0 },
// { "lowlevel.spectral_complexity.dmean", 1, 1.0 },
// { "lowlevel.spectral_complexity.dmean2", 1, 1.0 },
// { "lowlevel.spectral_complexity.dvar", 1, 1.0 },
@@ -341,7 +341,7 @@ static std::vector features =
{ "lowlevel.spectral_complexity.mean", 1, 1.0 },
// { "lowlevel.spectral_complexity.median", 1, 1.0 },
// { "lowlevel.spectral_complexity.min", 1, 1.0 },
-// { "lowlevel.spectral_complexity.var", 1, 1.0 },
+ { "lowlevel.spectral_complexity.var", 1, 1.0 },
// { "lowlevel.spectral_decrease.dmean", 1, 1.0 },
// { "lowlevel.spectral_decrease.dmean2", 1, 1.0 },
// { "lowlevel.spectral_decrease.dvar", 1, 1.0 },
@@ -350,7 +350,7 @@ static std::vector features =
{ "lowlevel.spectral_decrease.mean", 1, 1.0 },
// { "lowlevel.spectral_decrease.median", 1, 1.0 },
// { "lowlevel.spectral_decrease.min", 1, 1.0 },
-// { "lowlevel.spectral_decrease.var", 1, 1.0 },
+ { "lowlevel.spectral_decrease.var", 1, 1.0 },
// { "lowlevel.spectral_energy.dmean", 1, 1.0 },
// { "lowlevel.spectral_energy.dmean2", 1, 1.0 },
// { "lowlevel.spectral_energy.dvar", 1, 1.0 },
@@ -359,7 +359,7 @@ static std::vector features =
{ "lowlevel.spectral_energy.mean", 1, 1.0 },
// { "lowlevel.spectral_energy.median", 1, 1.0 },
// { "lowlevel.spectral_energy.min", 1, 1.0 },
-// { "lowlevel.spectral_energy.var", 1, 1.0 },
+ { "lowlevel.spectral_energy.var", 1, 1.0 },
// { "lowlevel.spectral_energyband_low.dmean", 1, 1.0 },
// { "lowlevel.spectral_energyband_low.dmean2", 1, 1.0 },
// { "lowlevel.spectral_energyband_low.dvar", 1, 1.0 },
@@ -368,7 +368,7 @@ static std::vector features =
{ "lowlevel.spectral_energyband_low.mean", 1, 1.0 },
// { "lowlevel.spectral_energyband_low.median", 1, 1.0 },
// { "lowlevel.spectral_energyband_low.min", 1, 1.0 },
-// { "lowlevel.spectral_energyband_low.var", 1, 1.0 },
+ { "lowlevel.spectral_energyband_low.var", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_high.dmean", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_high.dmean2", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_high.dvar", 1, 1.0 },
@@ -377,7 +377,7 @@ static std::vector features =
{ "lowlevel.spectral_energyband_middle_high.mean", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_high.median", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_high.min", 1, 1.0 },
-// { "lowlevel.spectral_energyband_middle_high.var", 1, 1.0 },
+ { "lowlevel.spectral_energyband_middle_high.var", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_low.dmean", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_low.dmean2", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_low.dvar", 1, 1.0 },
@@ -386,7 +386,7 @@ static std::vector features =
{ "lowlevel.spectral_energyband_middle_low.mean", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_low.median", 1, 1.0 },
// { "lowlevel.spectral_energyband_middle_low.min", 1, 1.0 },
-// { "lowlevel.spectral_energyband_middle_low.var", 1, 1.0 },
+ { "lowlevel.spectral_energyband_middle_low.var", 1, 1.0 },
// { "lowlevel.spectral_entropy.dmean", 1, 1.0 },
// { "lowlevel.spectral_entropy.dmean2", 1, 1.0 },
// { "lowlevel.spectral_entropy.dvar", 1, 1.0 },
@@ -395,7 +395,7 @@ static std::vector features =
{ "lowlevel.spectral_entropy.mean", 1, 1.0 },
// { "lowlevel.spectral_entropy.median", 1, 1.0 },
// { "lowlevel.spectral_entropy.min", 1, 1.0 },
-// { "lowlevel.spectral_entropy.var", 1, 1.0 },
+ { "lowlevel.spectral_entropy.var", 1, 1.0 },
// { "lowlevel.spectral_flux.dmean", 1, 1.0 },
// { "lowlevel.spectral_flux.dmean2", 1, 1.0 },
// { "lowlevel.spectral_flux.dvar", 1, 1.0 },
@@ -422,7 +422,7 @@ static std::vector features =
{ "lowlevel.spectral_rms.mean", 1, 1.0 },
// { "lowlevel.spectral_rms.median", 1, 1.0 },
// { "lowlevel.spectral_rms.min", 1, 1.0 },
-// { "lowlevel.spectral_rms.var", 1, 1.0 },
+ { "lowlevel.spectral_rms.var", 1, 1.0 },
// { "lowlevel.spectral_rolloff.dmean", 1, 1.0 },
// { "lowlevel.spectral_rolloff.dmean2", 1, 1.0 },
// { "lowlevel.spectral_rolloff.dvar", 1, 1.0 },
@@ -449,7 +449,7 @@ static std::vector features =
{ "lowlevel.spectral_spread.mean", 1, 1.0 },
// { "lowlevel.spectral_spread.median", 1, 1.0 },
// { "lowlevel.spectral_spread.min", 1, 1.0 },
-// { "lowlevel.spectral_spread.var", 1, 1.0 },
+ { "lowlevel.spectral_spread.var", 1, 1.0 },
// { "lowlevel.spectral_strongpeak.dmean", 1, 1.0 },
// { "lowlevel.spectral_strongpeak.dmean2", 1, 1.0 },
// { "lowlevel.spectral_strongpeak.dvar", 1, 1.0 },
@@ -458,7 +458,7 @@ static std::vector features =
{ "lowlevel.spectral_strongpeak.mean", 1, 1.0 },
// { "lowlevel.spectral_strongpeak.median", 1, 1.0 },
// { "lowlevel.spectral_strongpeak.min", 1, 1.0 },
-// { "lowlevel.spectral_strongpeak.var", 1, 1.0 },
+ { "lowlevel.spectral_strongpeak.var", 1, 1.0 },
// { "lowlevel.zerocrossingrate.dmean", 1, 1.0 },
// { "lowlevel.zerocrossingrate.dmean2", 1, 1.0 },
// { "lowlevel.zerocrossingrate.dvar", 1, 1.0 },
@@ -467,7 +467,7 @@ static std::vector features =
{ "lowlevel.zerocrossingrate.mean", 1, 1.0 },
// { "lowlevel.zerocrossingrate.median", 1, 1.0 },
// { "lowlevel.zerocrossingrate.min", 1, 1.0 },
-// { "lowlevel.zerocrossingrate.var", 1, 1.0 },
+ { "lowlevel.zerocrossingrate.var", 1, 1.0 },
// { "rhythm.beats_count", 1, 1.0 },
// { "rhythm.beats_loudness.dmean", 1, 1.0 },
// { "rhythm.beats_loudness.dmean2", 1, 1.0 },
@@ -477,7 +477,7 @@ static std::vector features =
{ "rhythm.beats_loudness.mean", 1, 1.0 },
// { "rhythm.beats_loudness.median", 1, 1.0 },
// { "rhythm.beats_loudness.min", 1, 1.0 },
-// { "rhythm.beats_loudness.var", 1, 1.0 },
+ { "rhythm.beats_loudness.var", 1, 1.0 },
// { "rhythm.bpm", 1, 1.0 },
// { "rhythm.bpm_histogram_first_peak_bpm.dmean", 1, 1.0 },
// { "rhythm.bpm_histogram_first_peak_bpm.dmean2", 1, 1.0 },
@@ -664,12 +664,7 @@ Classifier::processDatabaseUpdate(Updater::Stats stats)
LMS_LOG(DBUPDATER, DEBUG) << "Getting track ids";
- std::vector trackIdsAll;
- {
- Wt::Dbo::Transaction transaction(_db.getSession());
-
- trackIdsAll = Database::Track::getAllIds(_db.getSession());
- }
+ std::vector trackIdsAll = Database::Track::getAllIds(_db.getSession());
::std::random_shuffle ( trackIdsAll.begin(), trackIdsAll.end() );
diff --git a/src/database/DatabaseFeatureExtractor.cpp b/src/database/DatabaseFeatureExtractor.cpp
index 35574170..c4239b39 100644
--- a/src/database/DatabaseFeatureExtractor.cpp
+++ b/src/database/DatabaseFeatureExtractor.cpp
@@ -17,12 +17,12 @@
* along with LMS. If not, see .
*/
-#include "config/Config.hpp"
#include "logger/Logger.hpp"
#include "feature/FeatureExtractor.hpp"
#include "feature/FeatureStore.hpp"
+#include "Setting.hpp"
#include "DatabaseFeatureExtractor.hpp"
namespace Database {
@@ -37,8 +37,8 @@ static std::string getMBID(Track::id_type trackId)
void
FeatureExtractor::processDatabaseUpdate(Updater::Stats stats)
{
- bool fetchHighLevel = Config::instance().getBool("tag-highlevel-acousticbrainz", false);
- bool fetchLowLevel = Config::instance().getBool("tag-similarity-acousticbrainz", false);
+ bool fetchHighLevel = Setting::getBool(UpdaterDboSession(), "tags_highlevel_acousticbrainz");
+ bool fetchLowLevel = Setting::getBool(UpdaterDboSession(), "tags_similarity_acousticbrainz");
if (!fetchHighLevel && !fetchLowLevel)
{
diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp
index e6caf88f..270e4fe1 100644
--- a/src/database/DatabaseHandler.cpp
+++ b/src/database/DatabaseHandler.cpp
@@ -28,6 +28,8 @@
#include
#include
+#include "Setting.hpp"
+
#include "logger/Logger.hpp"
#include "DatabaseHandler.hpp"
@@ -79,7 +81,6 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
{
_session.setConnectionPool(connectionPool);
-
_session.mapClass("artist");
_session.mapClass("cluster");
_session.mapClass("track");
@@ -99,11 +100,6 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
Wt::Dbo::Transaction transaction(_session);
_session.createTables();
- _session.execute("CREATE INDEX artist_name_idx ON artist(name)");
- _session.execute("CREATE INDEX release_name_idx ON release(name)");
- _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();
@@ -112,7 +108,26 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
{
Wt::Dbo::Transaction transaction(_session);
+ // Indexes
_session.execute("PRAGMA journal_mode=WAL");
+ _session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)");
+ _session.execute("CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)");
+ _session.execute("CREATE INDEX IF NOT EXISTS track_artist_idx ON track(artist_id)");
+ _session.execute("CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)");
+ _session.execute("CREATE INDEX IF NOT EXISTS cluster_type_idx ON cluster(type)");
+
+ // Default values
+ if (!Setting::exists(_session, "audio_file_extensions"))
+ Setting::setString(_session, "audio_file_extensions", ".mp3 .ogg .oga .aac .m4a .flac .wav .wma .aif .aiff .ape .mpc .shn" );
+
+ if (!Setting::exists(_session, "video_file_extensions"))
+ Setting::setString(_session, "video_file_extensions", ".flv .avi .mpg .mpeg .mp4 .m4v .mkv .mov .wmv .ogv .divx .m2ts");
+
+ if (!Setting::exists(_session, "tags_highlevel_acousticbrainz"))
+ Setting::setBool(_session, "tags_highlevel_acousticbrainz", true);
+
+ if (!Setting::exists(_session, "tags_similarity_acousticbrainz"))
+ Setting::setBool(_session, "tags_similarity_acousticbrain", false);
}
_users = new UserDatabase(_session);
diff --git a/src/database/DatabaseUpdater.cpp b/src/database/DatabaseUpdater.cpp
index 35b62a4e..7a1fa6b7 100644
--- a/src/database/DatabaseUpdater.cpp
+++ b/src/database/DatabaseUpdater.cpp
@@ -28,6 +28,7 @@
#include "utils/Utils.hpp"
#include "utils/Path.hpp"
+#include "Setting.hpp"
#include "Types.hpp"
#include "DatabaseUpdater.hpp"
@@ -70,7 +71,6 @@ getNextFirstOfMonth(const boost::gregorian::date& current)
bool
isFileSupported(const boost::filesystem::path& file, const std::vector extensions)
{
-
boost::filesystem::path fileExtension = file.extension();
for (auto& extension : extensions)
@@ -87,9 +87,9 @@ getRootDirectoriesByType(Wt::Dbo::Session& session, Database::MediaDirectory::Ty
{
Wt::Dbo::Transaction transaction(session);
- std::vector res;
std::vector rootDirs = Database::MediaDirectory::getByType(session, type);
+ std::vector res;
for (auto rootDir : rootDirs)
res.push_back(rootDir->getPath());
@@ -170,48 +170,43 @@ Updater::stop(void)
void
Updater::processNextJob(void)
{
- Wt::Dbo::Transaction transaction(_db->getSession());
-
- MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db->getSession());
-
- if (settings->getManualScanRequested()) {
+ if (Setting::getBool(_db->getSession(), "manual_scan_requested", false))
+ {
LMS_LOG(DBUPDATER, INFO) << "Manual scan requested!";
scheduleScan( boost::posix_time::seconds(0) );
}
else
{
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
- boost::posix_time::time_duration startTime = settings->getUpdateStartTime();
+ boost::posix_time::time_duration startTime = Setting::getDuration(_db->getSession(), "update_start_time");
boost::gregorian::date nextScanDate;
- switch( settings->getUpdatePeriod() )
+ std::string updatePeriod = Setting::getString(_db->getSession(), "update_period", "never");
+ if (updatePeriod == "daily")
{
- case Database::MediaDirectorySettings::Never:
- // Nothing to do
- break;
- case Database::MediaDirectorySettings::Daily:
- if (now.time_of_day() < startTime)
- nextScanDate = now.date();
- else
- nextScanDate = getNextDay(now.date());
- break;
- case Database::MediaDirectorySettings::Weekly:
- if (now.time_of_day() < startTime && now.date().day_of_week() == 1)
- nextScanDate = now.date();
- else
- nextScanDate = getNextMonday(now.date());
- break;
- case Database::MediaDirectorySettings::Monthly:
- if (now.time_of_day() < startTime && now.date().day() == 1)
- nextScanDate = now.date();
- else
- nextScanDate = getNextFirstOfMonth(now.date());
- break;
+ if (now.time_of_day() < startTime)
+ nextScanDate = now.date();
+ else
+ nextScanDate = getNextDay(now.date());
+ }
+ else if (updatePeriod == "weekly")
+ {
+ if (now.time_of_day() < startTime && now.date().day_of_week() == 1)
+ nextScanDate = now.date();
+ else
+ nextScanDate = getNextMonday(now.date());
+ }
+ else if (updatePeriod == "monthly")
+ {
+ if (now.time_of_day() < startTime && now.date().day() == 1)
+ nextScanDate = now.date();
+ else
+ nextScanDate = getNextFirstOfMonth(now.date());
}
if (!nextScanDate.is_special())
- scheduleScan( boost::posix_time::ptime (nextScanDate, settings->getUpdateStartTime() ) );
+ scheduleScan( boost::posix_time::ptime (nextScanDate, startTime) );
}
}
@@ -274,27 +269,17 @@ Updater::process(boost::system::error_code err)
// 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 (stats.nbChanges() > 0)
+ Setting::setTime(_db->getSession(), "last_update", now);
+ // Save the last scan only if it has been completed
if (_running)
+ {
+ Setting::setTime(_db->getSession(), "last_scan", now);
+ Setting::setBool(_db->getSession(), "manual_scan_requested", false);
+
processNextJob();
+ }
}
void
@@ -302,8 +287,13 @@ Updater::updateFileExtensions()
{
Wt::Dbo::Transaction transaction(_db->getSession());
- _audioFileExtensions = MediaDirectorySettings::get(_db->getSession())->getAudioFileExtensions();
- _videoFileExtensions = MediaDirectorySettings::get(_db->getSession())->getVideoFileExtensions();
+ _audioFileExtensions.clear();
+ for (auto extension : splitString(Setting::getString(_db->getSession(), "audio_file_extensions"), " "))
+ _audioFileExtensions.push_back( extension );
+
+ _videoFileExtensions.clear();
+ for (auto extension : splitString(Setting::getString(_db->getSession(), "video_file_extensions"), " "))
+ _videoFileExtensions.push_back( extension );
}
Artist::pointer
diff --git a/src/database/MediaDirectory.cpp b/src/database/MediaDirectory.cpp
index 3bbf1ab2..cd2a0cc2 100644
--- a/src/database/MediaDirectory.cpp
+++ b/src/database/MediaDirectory.cpp
@@ -17,89 +17,18 @@
* along with LMS. If not, see .
*/
+#include "utils/Utils.hpp"
+
#include "Types.hpp"
-static std::string pathsToString(const std::vector& paths)
-{
- std::ostringstream oss;
-
- bool first = true;
- for (auto& path : paths)
- {
- if (!first)
- oss << " ";
-
- oss << path.string();
- first = false;
- }
-
- return oss.str();
-}
-
-static std::vector stringToPaths(const std::string value)
-{
- std::vector res;
- std::istringstream iss(value);
-
- std::copy(std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(res));
-
- return std::vector(res.begin(), res.end());
-}
-
namespace Database {
-MediaDirectorySettings::MediaDirectorySettings()
-: _manualScanRequested(false),
-_updatePeriod(Never),
-_audioFileExtensions(".mp3 .ogg .oga .aac .m4a .flac .wav .wma .aif .aiff .ape .mpc .shn"),
-_videoFileExtensions(".flv .avi .mpg .mpeg .mp4 .m4v .mkv .mov .wmv .ogv .divx .m2ts")
-{
-}
-
MediaDirectory::MediaDirectory(boost::filesystem::path p, Type type)
: _type(type),
- _path(p.string())
+ _path(stringTrimEnd(p.string(), "/\\"))
{
}
-MediaDirectorySettings::pointer
-MediaDirectorySettings::get(Wt::Dbo::Session& session)
-{
- MediaDirectorySettings::pointer res;
-
- res = session.find().where("id = ?").bind(1);
- // TODO bind necessary?
- if (!res)
- res = session.add( new MediaDirectorySettings());
-
- return res;
-}
-
-std::vector
-MediaDirectorySettings::getAudioFileExtensions(void) const
-{
- return stringToPaths(_audioFileExtensions);
-}
-
-std::vector
-MediaDirectorySettings::getVideoFileExtensions(void) const
-{
- return stringToPaths(_videoFileExtensions);
-}
-
-void
-MediaDirectorySettings::setAudioFileExtensions(std::vector extensions)
-{
- _audioFileExtensions = pathsToString(extensions);
-}
-
-void
-MediaDirectorySettings::setVideoFileExtensions(std::vector extensions)
-{
- _videoFileExtensions = pathsToString(extensions);
-}
-
-
MediaDirectory::pointer
MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type)
{
@@ -135,5 +64,10 @@ MediaDirectory::get(Wt::Dbo::Session& session, boost::filesystem::path p, Type t
return session.find().where("path = ?").where("type = ?").bind( p.string()).bind(type);
}
+boost::filesystem::path
+MediaDirectory::getPath(void) const
+{
+ return boost::filesystem::path(stringTrimEnd(_path, "/\\"));
+}
} // namespace Database
diff --git a/src/database/MediaDirectory.hpp b/src/database/MediaDirectory.hpp
index f4085be5..c77b6b0c 100644
--- a/src/database/MediaDirectory.hpp
+++ b/src/database/MediaDirectory.hpp
@@ -26,73 +26,8 @@
#include
#include
-
namespace Database {
-class MediaDirectory;
-
-class MediaDirectorySettings
-{
- public:
-
- enum UpdatePeriod {
- Never,
- Daily,
- Weekly,
- Monthly
- };
-
- typedef Wt::Dbo::ptr pointer;
-
- MediaDirectorySettings();
-
- // accessors
- static pointer get(Wt::Dbo::Session& session);
-
- // write accessors
- void setManualScanRequested(bool value) { _manualScanRequested = value;}
- void setUpdatePeriod(UpdatePeriod period) { _updatePeriod = period;}
- void setUpdateStartTime(boost::posix_time::time_duration dur) { _updateStartTime = dur;}
- void setLastUpdate(boost::posix_time::ptime time) { _lastUpdate = time; }
- void setLastScan(boost::posix_time::ptime time) { _lastScan = time; }
- void setAudioFileExtensions(std::vector extensions);
- void setVideoFileExtensions(std::vector extensions);
-
- // Read accessors
- bool getManualScanRequested(void) const { return _manualScanRequested; }
- UpdatePeriod getUpdatePeriod(void) const { return _updatePeriod; }
- boost::posix_time::time_duration getUpdateStartTime(void) const { return _updateStartTime; }
- boost::posix_time::ptime getLastUpdated(void) const { return _lastUpdate; }
- boost::posix_time::ptime getLastScan(void) const { return _lastScan; }
- std::vector getAudioFileExtensions(void) const;
- std::vector getVideoFileExtensions(void) const;
-
- template
- void persist(Action& a)
- {
- Wt::Dbo::field(a, _manualScanRequested, "manual_scan_requested");
- Wt::Dbo::field(a, _updatePeriod, "update_period");
- Wt::Dbo::field(a, _updateStartTime, "update_start_time");
- Wt::Dbo::field(a, _audioFileExtensions, "audio_file_extensions");
- Wt::Dbo::field(a, _videoFileExtensions, "video_file_extensions");
- Wt::Dbo::field(a, _lastUpdate, "last_update");
- Wt::Dbo::field(a, _lastScan, "last_scan");
- Wt::Dbo::hasMany(a, _mediaDirectories, Wt::Dbo::ManyToOne, "media_directory_settings");
- }
-
- private:
-
- bool _manualScanRequested; // Immadiate scan has been requested by user
- UpdatePeriod _updatePeriod; // How long between updates
- boost::posix_time::time_duration _updateStartTime; // Time of day to begin the update
- std::string _audioFileExtensions; // Extension of the audio files to be scanned
- std::string _videoFileExtensions; // Extension of the video files to be scanned
- boost::posix_time::ptime _lastUpdate; // last time the database has changed
- boost::posix_time::ptime _lastScan; // last time the database has been scanned
- Wt::Dbo::collection< Wt::Dbo::ptr > _mediaDirectories; // list of media directories
-};
-
-
class MediaDirectory
{
public:
@@ -116,14 +51,13 @@ class MediaDirectory
static void eraseAll(Wt::Dbo::Session& session);
Type getType(void) const { return _type; }
- boost::filesystem::path getPath(void) const { return boost::filesystem::path(_path); }
+ boost::filesystem::path getPath(void) const;
template
void persist(Action& a)
{
Wt::Dbo::field(a, _type, "type");
Wt::Dbo::field(a, _path, "path");
- Wt::Dbo::belongsTo(a, _settings, "media_directory_settings", Wt::Dbo::OnDeleteCascade);
}
private:
@@ -131,8 +65,6 @@ class MediaDirectory
Type _type;
std::string _path;
- MediaDirectorySettings::pointer _settings; // back pointer
-
};
} // namespace Database
diff --git a/src/database/Setting.cpp b/src/database/Setting.cpp
new file mode 100644
index 00000000..c5dc5200
--- /dev/null
+++ b/src/database/Setting.cpp
@@ -0,0 +1,152 @@
+/*
+ * 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 .
+ */
+
+#include "Setting.hpp"
+
+namespace Database {
+
+bool
+Setting::exists(Wt::Dbo::Session& session, std::string setting)
+{
+ Wt::Dbo::Transaction transaction(session);
+ return (getByName(session, setting) != Setting::pointer());
+}
+
+std::string
+Setting::getString(Wt::Dbo::Session& session, std::string setting, std::string defaultValue)
+{
+ Wt::Dbo::Transaction transaction(session);
+
+ pointer res = getByName(session, setting);
+ if (!res)
+ return defaultValue;
+
+ return res->_value;
+}
+
+bool
+Setting::getBool(Wt::Dbo::Session& session, std::string setting, bool defaultValue)
+{
+ Wt::Dbo::Transaction transaction(session);
+
+ pointer res = getByName(session, setting);
+ if (!res)
+ return defaultValue;
+
+ return (res->_value == "true");
+}
+
+boost::posix_time::time_duration
+Setting::getDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration defaultValue)
+{
+ Wt::Dbo::Transaction transaction(session);
+
+ pointer res = getByName(session, setting);
+ if (!res)
+ return defaultValue;
+
+ return boost::posix_time::duration_from_string(res->_value);
+}
+
+boost::posix_time::ptime
+Setting::getTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime defaultValue)
+{
+ Wt::Dbo::Transaction transaction(session);
+
+ pointer res = getByName(session, setting);
+ if (!res)
+ return defaultValue;
+
+ return boost::posix_time::time_from_string(res->_value);
+}
+
+int
+Setting::getInt(Wt::Dbo::Session& session, std::string setting, int defaultValue)
+{
+ Wt::Dbo::Transaction transaction(session);
+
+ pointer res = getByName(session, setting);
+ if (!res)
+ return defaultValue;
+
+ return std::stoi(res->_value);
+}
+
+
+
+Setting::pointer
+Setting::create(Wt::Dbo::Session& session, std::string name)
+{
+ return session.add(new Setting(name));
+}
+
+Setting::pointer
+Setting::getByName(Wt::Dbo::Session& session, std::string name)
+{
+ return session.find().where("name = ?").bind(name);
+}
+
+Setting::pointer
+Setting::getOrCreateByName(Wt::Dbo::Session& session, std::string name)
+{
+ pointer res = getByName(session, name);
+ if (!res)
+ res = create(session, name);
+
+ return res;
+}
+
+void
+Setting::setString(Wt::Dbo::Session& session, std::string setting, std::string value)
+{
+ Wt::Dbo::Transaction transaction(session);
+ getOrCreateByName(session, setting).modify()->_value = value;
+}
+
+void
+Setting::setBool(Wt::Dbo::Session& session, std::string setting, bool value)
+{
+ Wt::Dbo::Transaction transaction(session);
+ getOrCreateByName(session, setting).modify()->_value = (value ? "true" : "false");
+}
+
+void
+Setting::setDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration value)
+{
+ Wt::Dbo::Transaction transaction(session);
+ getOrCreateByName(session, setting).modify()->_value = boost::posix_time::to_simple_string(value);
+}
+
+void
+Setting::setTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime value)
+{
+ Wt::Dbo::Transaction transaction(session);
+ getOrCreateByName(session, setting).modify()->_value = boost::posix_time::to_simple_string(value);
+}
+
+void
+Setting::setInt(Wt::Dbo::Session& session, std::string setting, int value)
+{
+ Wt::Dbo::Transaction transaction(session);
+ getOrCreateByName(session, setting).modify()->_value = std::to_string(value);
+}
+
+
+} // namespace Database
+
diff --git a/src/database/Setting.hpp b/src/database/Setting.hpp
new file mode 100644
index 00000000..394fc7bc
--- /dev/null
+++ b/src/database/Setting.hpp
@@ -0,0 +1,75 @@
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include
+
+#include
+
+namespace Database {
+
+// class meant to store general settings
+class Setting
+{
+ public:
+ Setting() {}
+
+ // check if a setting exists or not
+ static bool exists(Wt::Dbo::Session& session, std::string setting);
+
+ // Getters
+ // Nested transactions
+ static std::string getString(Wt::Dbo::Session& session, std::string setting, std::string defaultValue = "");
+ static bool getBool(Wt::Dbo::Session& session, std::string setting, bool defaultValue = false);
+ static boost::posix_time::time_duration getDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration defaultDuration = boost::posix_time::seconds(0) );
+ static boost::posix_time::ptime getTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime defaultTime = boost::posix_time::ptime());
+ static int getInt(Wt::Dbo::Session& session, std::string setting, int defaultValue = 0);
+
+ // Setters
+ // Nested transactions
+ static void setString(Wt::Dbo::Session& session, std::string setting, std::string value);
+ static void setBool(Wt::Dbo::Session& session, std::string setting, bool value);
+ static void setDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration value);
+ static void setTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime time);
+ static void setInt(Wt::Dbo::Session& session, std::string setting, int value);
+
+ template
+ void persist(Action& a)
+ {
+ Wt::Dbo::field(a, _name, "name");
+ Wt::Dbo::field(a, _value, "value");
+ }
+
+ private:
+ Setting(std::string name) : _name(name) {}
+
+ typedef Wt::Dbo::ptr pointer;
+
+ static pointer getByName(Wt::Dbo::Session& session, std::string name);
+ static pointer create(Wt::Dbo::Session& session, std::string name);
+ static pointer getOrCreateByName(Wt::Dbo::Session& session, std::string name);
+
+ std::string _name;
+ std::string _value;
+};
+
+
+} // namespace Database
+
diff --git a/src/database/cluster/DatabaseHighLevelCluster.cpp b/src/database/cluster/DatabaseHighLevelCluster.cpp
new file mode 100644
index 00000000..c517da3d
--- /dev/null
+++ b/src/database/cluster/DatabaseHighLevelCluster.cpp
@@ -0,0 +1,226 @@
+/*
+ * 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 .
+ */
+
+#include "DatabaseHighLevelCluster.hpp"
+
+#include "config/Config.hpp"
+#include "logger/Logger.hpp"
+#include "feature/FeatureStore.hpp"
+
+namespace Database {
+
+static Cluster::pointer getCluster(std::string type, std::string value)
+{
+ Cluster::pointer cluster = ( Cluster::get(UpdaterDboSession(), type, value) );
+ if (!cluster)
+ cluster = Cluster::create(UpdaterDboSession(), type, value);
+
+ return cluster;
+}
+
+static std::list getClustersFromFeature(Feature::Type& feature)
+{
+ struct HighLevelNodeDesc
+ {
+ std::string node;
+ std::map valueMapping;
+ };
+
+ static const std::vector nodes =
+ {
+ {
+ "highlevel.danceability",
+ {
+ {"not_danceable", "Not danceable"},
+ {"danceable", "Danceable"},
+ },
+ },
+ {
+ "highlevel.gender",
+ {
+ {"male", "Male"},
+ {"female", "Female"},
+ },
+ },
+ {
+ "highlevel.mood_acoustic",
+ {
+ {"not_acoustic", "Not acoustic"},
+ {"acoustic", "Acoustic"},
+ },
+ },
+ {
+ "highlevel.mood_happy",
+ {
+ {"not_happy", "Not happy"},
+ {"happy", "Happy"},
+ },
+ },
+ {
+ "highlevel.mood_aggressive",
+ {
+ {"not_aggressive", "Not aggressive"},
+ {"aggressive", "Aggressive"},
+ },
+ },
+ {
+ "highlevel.mood_electronic",
+ {
+ {"not_electronic", "Not electronic"},
+ {"electronic", "Electronic"},
+ },
+ },
+ {
+ "highlevel.mood_party",
+ {
+ {"not_party", "Not party"},
+ {"party", "Party"},
+ },
+ },
+ {
+ "highlevel.mood_relaxed",
+ {
+ {"not_relaxed", "Not relaxed"},
+ {"relaxed", "Relaxed"},
+ },
+ },
+ {
+ "highlevel.mood_sad",
+ {
+ {"not_sad", "Not sad"},
+ {"sad", "Sad"},
+ },
+ },
+ {
+ "highlevel.timbre",
+ {
+ {"bright", "Bright"},
+ {"dark", "Dark"},
+ },
+ },
+ {
+ "highlevel.tonal_atonal",
+ {
+ {"atonal", "Atonal"},
+ {"tonal", "Tonal"},
+ },
+ },
+ {
+ "highlevel.voice_instrumental",
+ {
+ {"instrumental", "Instrumental"},
+ {"voice", "Voice"},
+ },
+ },
+ };
+
+
+ // Extract info and build clusters
+ std::list newClusterNames;
+
+ for (auto node : nodes)
+ {
+ auto value = feature.get_child_optional(node.node + ".value");
+ auto probability = feature.get_child_optional(node.node + ".probability");
+
+ if (!probability || !value)
+ {
+ LMS_LOG(DBUPDATER, DEBUG) << "Missing " << node.node;
+ continue;
+ }
+
+ if (std::stod(probability->data()) < 0.90)
+ {
+ LMS_LOG(DBUPDATER, DEBUG) << "Probability too low for " << node.node << "(" << std::stod(probability->data()) << ")";
+ continue;
+ }
+
+ if (node.valueMapping[value->data()] == "")
+ {
+ LMS_LOG(DBUPDATER, DEBUG) << "Unknown value '" << value->data() << "'";
+ continue;
+ }
+
+ newClusterNames.push_back(node.valueMapping[value->data()]);
+ }
+
+ return newClusterNames;
+}
+
+void
+HighLevelCluster::processDatabaseUpdate(Updater::Stats stats)
+{
+ bool createTags = Config::instance().getBool("tag-highlevel-acousticbrainz", false);
+
+
+ LMS_LOG(DBUPDATER, INFO) << "Creating high level based clusters...";
+
+ std::vector trackIds = Track::getAllIds(UpdaterDboSession());
+
+ LMS_LOG(DBUPDATER, DEBUG) << "Got " << trackIds.size() << " tracks";
+ for (auto trackId : trackIds)
+ {
+ if (UpdaterQuitRequested())
+ return;
+
+ LMS_LOG(DBUPDATER, DEBUG) << "Processing track " << trackId;
+
+ // Get current cluster names
+ std::list newClusterNames;
+ if (createTags)
+ {
+ Feature::Type feature;
+ if (!Feature::Store::instance().get(UpdaterDboSession(), trackId, "high_level", feature))
+ continue;
+
+ newClusterNames = getClustersFromFeature(feature);
+ }
+
+ Wt::Dbo::Transaction transaction(UpdaterDboSession());
+
+ auto track = Track::getById(UpdaterDboSession(), trackId);
+
+ auto clusters = track->getClusters();
+ for (auto cluster : clusters)
+ {
+ // Check if removed
+ if (cluster->getType() != "high_level")
+ continue;
+
+ auto it = std::find(newClusterNames.begin(), newClusterNames.end(), cluster->getName());
+ if (it == newClusterNames.end())
+ cluster.remove();
+ else
+ newClusterNames.erase(it);
+
+ }
+
+ // Add previsouly missing clusters
+ for (auto newName : newClusterNames)
+ {
+ auto cluster = getCluster("high_level", newName);
+ cluster.modify()->addTrack(track);
+ }
+ }
+
+ LMS_LOG(DBUPDATER, INFO) << "High level based clusters processed";
+}
+
+
+} // namespace Database
diff --git a/src/database/cluster/DatabaseHighLevelCluster.hpp b/src/database/cluster/DatabaseHighLevelCluster.hpp
new file mode 100644
index 00000000..25f91d86
--- /dev/null
+++ b/src/database/cluster/DatabaseHighLevelCluster.hpp
@@ -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 .
+ */
+
+#pragma once
+
+#include "database/DatabaseUpdater.hpp"
+
+namespace Database {
+
+class HighLevelCluster
+{
+ public:
+ void processDatabaseUpdate(Updater::Stats stats);
+
+};
+
+} // namespace Database
diff --git a/src/database/cluster/DatabaseSimilarityCluster.hpp b/src/database/cluster/DatabaseSimilarityCluster.hpp
new file mode 100644
index 00000000..e69de29b
diff --git a/src/database/tagger/GenreTagger.cpp b/src/database/tagger/GenreTagger.cpp
new file mode 100644
index 00000000..ec40da6d
--- /dev/null
+++ b/src/database/tagger/GenreTagger.cpp
@@ -0,0 +1,40 @@
+
+/*
+ * 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 .
+ */
+
+#include "GenreTagger.hpp"
+
+namespace Database {
+
+void
+GenreTagger::processTrackUpdate(bool added, Track::id_type trackId, std::string mbid, boost::filesystem::path p)
+{
+
+
+}
+
+void
+GenreTagger::processDatabaseUpdate(Updater::Stats stats)
+{
+
+ // TODO: if disabled, delete all the genre tags
+ // TODO: if enabled, reconstruct all the genre tags from tracks
+}
+
+} // namespace Database
diff --git a/src/database/tagger/GenreTagger.hpp b/src/database/tagger/GenreTagger.hpp
new file mode 100644
index 00000000..16118c48
--- /dev/null
+++ b/src/database/tagger/GenreTagger.hpp
@@ -0,0 +1,37 @@
+
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include "database/DatabaseUpdater.hpp"
+
+namespace Database {
+
+class GenreTagger
+{
+ public:
+ void processTrackUpdate(bool added, Track::id_type trackId, std::string mbid, boost::filesystem::path p);
+ void processDatabaseUpdate(Updater::Stats stats);
+
+ private:
+
+};
+
+} // namespace Database
diff --git a/src/feature/FeatureStore.cpp b/src/feature/FeatureStore.cpp
index e5fff4dc..f58c51ab 100644
--- a/src/feature/FeatureStore.cpp
+++ b/src/feature/FeatureStore.cpp
@@ -21,6 +21,7 @@
#include "logger/Logger.hpp"
#include "config/Config.hpp"
+#include "utils/Path.hpp"
#include "FeatureStore.hpp"
@@ -40,12 +41,20 @@ Store::instance(void)
void
Store::reload(void)
{
- _storePath = Config::instance().getString("features-dir-path", "");
+ boost::filesystem::path cacheDir = _storePath = Config::instance().getString("cache-dir-path", "");
- if (!boost::filesystem::is_directory(_storePath))
+ if (!ensureDirectory(cacheDir))
{
- LMS_LOG(DBUPDATER, ERROR) << "Feature directory '" << _storePath << "' not valid!";
- throw std::runtime_error("Invalid feature directory '" + _storePath.string());
+ LMS_LOG(DBUPDATER, ERROR) << "Cache directory '" << cacheDir << "' not valid";
+ throw std::runtime_error("Cache directory '" + cacheDir.string() + "' not valid!");
+ }
+
+ _storePath = cacheDir / "features";
+
+ if (!ensureDirectory(_storePath))
+ {
+ LMS_LOG(DBUPDATER, ERROR) << "Features directory '" << _storePath << "' not valid";
+ throw std::runtime_error("Features directory '" + _storePath.string() + "' not valid");
}
}
diff --git a/src/main/main.cpp b/src/main/main.cpp
index 67671166..f22afc8f 100644
--- a/src/main/main.cpp
+++ b/src/main/main.cpp
@@ -26,10 +26,12 @@
#include "av/AvTranscoder.hpp"
#include "logger/Logger.hpp"
#include "image/Image.hpp"
+#include "feature/FeatureExtractor.hpp"
#include "database/DatabaseUpdater.hpp"
-#include "database/DatabaseClassifier.hpp"
-#include "feature/FeatureExtractor.hpp"
+#include "database/DatabaseFeatureExtractor.hpp"
+#include "database/cluster/DatabaseHighLevelCluster.hpp"
+
#include "ui/LmsApplication.hpp"
@@ -44,16 +46,16 @@ static std::vector getWtArgs(std::string path)
if (Config::instance().getBool("tls-enable", false))
{
- args.push_back("--https-port=" + std::to_string( Config::instance().getULong("listen-port")));
- args.push_back("--https-address=" + Config::instance().getString("listen-addr"));
+ args.push_back("--https-port=" + std::to_string( Config::instance().getULong("listen-port", 5081)));
+ args.push_back("--https-address=" + Config::instance().getString("listen-addr", "0.0.0.0"));
args.push_back("--ssl-certificate=" + Config::instance().getString("tls-cert"));
args.push_back("--ssl-private-key=" + Config::instance().getString("tls-key"));
args.push_back("--ssl-tmp-dh=" + Config::instance().getString("tls-dh"));
}
else
{
- args.push_back("--http-port=" + std::to_string( Config::instance().getULong("listen-port")));
- args.push_back("--http-address=" + Config::instance().getString("listen-addr"));
+ args.push_back("--http-port=" + std::to_string( Config::instance().getULong("listen-port", 5081)));
+ args.push_back("--http-address=" + Config::instance().getString("listen-addr", "0.0.0.0"));
}
return args;
@@ -104,13 +106,12 @@ int main(int argc, char* argv[])
Database::Updater& dbUpdater = Database::Updater::instance();
dbUpdater.setConnectionPool(*connectionPool);
- Database::Classifier dbClassifier(*connectionPool);
+ Database::FeatureExtractor dbFeatureExtractor;
+ Database::HighLevelCluster dbHighLevelCluster;
- // Connect the classifier to the update events
- dbUpdater.trackChanged().connect(std::bind(&Database::Classifier::processTrackUpdate, &dbClassifier,
- std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
- dbUpdater.scanComplete().connect(std::bind(&Database::Classifier::processDatabaseUpdate, &dbClassifier,
- std::placeholders::_1));
+ // Connect to the update events
+ dbUpdater.scanComplete().connect(std::bind(&Database::HighLevelCluster::processDatabaseUpdate, &dbHighLevelCluster, std::placeholders::_1));
+ dbUpdater.scanComplete().connect(std::bind(&Database::FeatureExtractor::processDatabaseUpdate, &dbFeatureExtractor, std::placeholders::_1));
// bind entry point
server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create,
diff --git a/src/ui/settings/Settings.cpp b/src/ui/settings/Settings.cpp
index 3c68c6fc..308664c8 100644
--- a/src/ui/settings/Settings.cpp
+++ b/src/ui/settings/Settings.cpp
@@ -31,6 +31,7 @@
#include "logger/Logger.hpp"
#include "database/DatabaseUpdater.hpp"
+#include "database/Setting.hpp"
#include "LmsApplication.hpp"
@@ -39,6 +40,8 @@
namespace UserInterface {
namespace Settings {
+using namespace Database;
+
Settings::Settings(Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent)
{
@@ -65,7 +68,7 @@ Settings::Settings(Wt::WContainerWidget* parent)
{
Wt::Dbo::Transaction transaction(DboSession());
- userId = Database::User::getId(CurrentUser());
+ userId = User::getId(CurrentUser());
userIsAdmin = CurrentUser()->isAdmin();
}
@@ -77,24 +80,19 @@ Settings::Settings(Wt::WContainerWidget* parent)
{
LMS_LOG(UI, INFO) << "Media directories have changed: requesting imediate scan";
- // On directory add or delete, request an immediate scan
- //
- {
- Wt::Dbo::Transaction transaction(DboSession());
- Database::MediaDirectorySettings::get(DboSession()).modify()->setManualScanRequested(true);
- }
- {
- std::lock_guard lock(Database::Updater::instance().getMutex());
- Database::Updater::instance().restart();
- }
+ // On directory change, request an immediate scan
+ std::lock_guard lock(Updater::instance().getMutex());
+
+ Setting::setBool(DboSession(), "manual_scan_requested", true);
+ Updater::instance().restart();
}));
menu->addItem("Media Folders", mediaDirectories)->setPathComponent("mediadirectories");
DatabaseFormView* databaseFormView = new DatabaseFormView();
databaseFormView->changed().connect(std::bind([=]
{
- std::lock_guard lock(Database::Updater::instance().getMutex());
- Database::Updater::instance().restart();
+ std::lock_guard lock(Updater::instance().getMutex());
+ Updater::instance().restart();
}));
menu->addItem("Database", databaseFormView)->setPathComponent("database");
diff --git a/src/ui/settings/SettingsDatabaseFormView.cpp b/src/ui/settings/SettingsDatabaseFormView.cpp
index f9ad0ccf..1c93f1d8 100644
--- a/src/ui/settings/SettingsDatabaseFormView.cpp
+++ b/src/ui/settings/SettingsDatabaseFormView.cpp
@@ -29,7 +29,7 @@
#include
#include "logger/Logger.hpp"
-#include "database/MediaDirectory.hpp"
+#include "database/Setting.hpp"
#include "common/DirectoryValidator.hpp"
#include "LmsApplication.hpp"
@@ -49,6 +49,8 @@ class DatabaseFormModel : public Wt::WFormModel
static const Field UpdateStartTimeField;
static const Field AudioFileExtensionsField;
static const Field VideoFileExtensionsField;
+ static const Field TagsHighLevelAcousticBrainz;
+ static const Field TagsSimilarityAcousticBrainz;
DatabaseFormModel(Wt::WObject *parent = 0)
: Wt::WFormModel(parent)
@@ -59,6 +61,8 @@ class DatabaseFormModel : public Wt::WFormModel
addField(UpdateStartTimeField);
addField(AudioFileExtensionsField);
addField(VideoFileExtensionsField);
+ addField(TagsHighLevelAcousticBrainz);
+ addField(TagsSimilarityAcousticBrainz);
setValidator(UpdatePeriodField, createUpdatePeriodValidator());
setValidator(UpdateStartTimeField, createStartTimeValidator());
@@ -74,117 +78,70 @@ class DatabaseFormModel : public Wt::WFormModel
void loadData()
{
- Wt::Dbo::Transaction transaction(DboSession());
-
- // Get refresh settings
- MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(DboSession());
-
- int periodRow = getUpdatePeriodModelRow( settings->getUpdatePeriod() );
+ int periodRow = getUpdatePeriodModelRow( Setting::getString(DboSession(), "update_period"));
if (periodRow != -1)
- setValue(UpdatePeriodField, updatePeriod(periodRow));
+ setValue(UpdatePeriodField, updatePeriodDisplay(periodRow));
- int startTimeRow = getUpdateStartTimeModelRow( settings->getUpdateStartTime() );
+ int startTimeRow = getUpdateStartTimeModelRow( Setting::getDuration(DboSession(), "update_start_time") );
if (startTimeRow != -1)
setValue(UpdateStartTimeField, updateStartTime( startTimeRow ) );
- std::vector audioFileExtensions = settings->getAudioFileExtensions();
- {
- std::ostringstream oss;
- for (auto& fileExtension : audioFileExtensions)
- oss << fileExtension.string() << " ";
+ setValue(AudioFileExtensionsField, Setting::getString(DboSession(), "audio_file_extensions"));
+ setValue(VideoFileExtensionsField, Setting::getString(DboSession(), "video_file_extensions"));
- setValue(AudioFileExtensionsField, oss.str());
- }
-
- std::vector videoFileExtensions = settings->getVideoFileExtensions();
- {
- std::ostringstream oss;
- for (auto& fileExtension : videoFileExtensions)
- oss << fileExtension.string() << " ";
-
- setValue(VideoFileExtensionsField, oss.str());
- }
+ setValue(TagsHighLevelAcousticBrainz, Setting::getBool(DboSession(), "tags_highlevel_acousticbrainz"));
+ setValue(TagsSimilarityAcousticBrainz, Setting::getBool(DboSession(), "tags_similarity_acousticbrainz"));
}
void saveData()
{
- Wt::Dbo::Transaction transaction(DboSession());
-
- MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(DboSession());
-
int periodRow = getUpdatePeriodModelRow( boost::any_cast(value(UpdatePeriodField)));
assert(periodRow != -1);
- settings.modify()->setUpdatePeriod( updatePeriodDuration( periodRow ) );
+ Setting::setString(DboSession(), "update_period", updatePeriodSetting( periodRow ) );
int startTimeRow = getUpdateStartTimeModelRow( boost::any_cast(value(UpdateStartTimeField)));
assert(startTimeRow != -1);
- settings.modify()->setUpdateStartTime( updateStartTimeDuration( startTimeRow ) );
+ Setting::setDuration(DboSession(), "update_start_time", updateStartTimeDuration( startTimeRow ) );
- {
- std::vector res;
- std::istringstream iss(boost::any_cast(value(AudioFileExtensionsField)).toUTF8());
+ Setting::setString(DboSession(), "audio_file_extensions", boost::any_cast(value(AudioFileExtensionsField)).toUTF8());
+ Setting::setString(DboSession(), "video_file_extensions", boost::any_cast(value(VideoFileExtensionsField)).toUTF8());
- std::copy(std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(res));
-
- settings.modify()->setAudioFileExtensions( std::vector(res.begin(), res.end()) );
- }
-
- {
- std::vector res;
- std::istringstream iss(boost::any_cast(value(VideoFileExtensionsField)).toUTF8());
-
- std::copy(std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(res));
-
- settings.modify()->setVideoFileExtensions( std::vector(res.begin(), res.end()) );
- }
+ Setting::setBool(DboSession(), "tags_highlevel_acousticbrainz", boost::any_cast(value(TagsHighLevelAcousticBrainz)));
+ Setting::setBool(DboSession(), "tags_similarity_acousticbrainz", boost::any_cast(value(TagsSimilarityAcousticBrainz)));
}
- bool setImmediateScan(Wt::WString& error)
+ void setImmediateScan()
{
- try
- {
- Wt::Dbo::Transaction transaction( DboSession());
-
- MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(DboSession() );
-
- settings.modify()->setManualScanRequested( true );
- }
- catch(Wt::Dbo::Exception& exception)
- {
- LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
- return false;
- }
-
- return true;
+ Setting::setBool(DboSession(), "manual_scan_requested", true);
}
int getUpdatePeriodModelRow(Wt::WString value)
{
for (int i = 0; i < _updatePeriodModel->rowCount(); ++i)
{
- if (updatePeriod(i) == value)
+ if (updatePeriodDisplay(i) == value)
return i;
}
return -1;
}
- int getUpdatePeriodModelRow(MediaDirectorySettings::UpdatePeriod duration)
+ int getUpdatePeriodModelRow(std::string value)
{
for (int i = 0; i < _updatePeriodModel->rowCount(); ++i)
{
- if (updatePeriodDuration(i) == duration)
+ if (updatePeriodSetting(i) == value)
return i;
}
return -1;
}
- MediaDirectorySettings::UpdatePeriod updatePeriodDuration(int row) {
- return boost::any_cast
+ std::string updatePeriodSetting(int row) {
+ return boost::any_cast
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::UserRole));
}
- Wt::WString updatePeriod(int row) {
+ Wt::WString updatePeriodDisplay(int row) {
return boost::any_cast
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::DisplayRole));
}
@@ -230,16 +187,16 @@ class DatabaseFormModel : public Wt::WFormModel
_updatePeriodModel = new Wt::WStringListModel(this);
_updatePeriodModel->addString("Never");
- _updatePeriodModel->setData(0, 0, MediaDirectorySettings::Never, Wt::UserRole);
+ _updatePeriodModel->setData(0, 0, std::string("never"), Wt::UserRole);
_updatePeriodModel->addString("Daily");
- _updatePeriodModel->setData(1, 0, MediaDirectorySettings::Daily, Wt::UserRole);
+ _updatePeriodModel->setData(1, 0, std::string("daily"), Wt::UserRole);
_updatePeriodModel->addString("Weekly");
- _updatePeriodModel->setData(2, 0, MediaDirectorySettings::Weekly, Wt::UserRole);
+ _updatePeriodModel->setData(2, 0, std::string("weekly"), Wt::UserRole);
_updatePeriodModel->addString("Monthly");
- _updatePeriodModel->setData(3, 0, MediaDirectorySettings::Monthly, Wt::UserRole);
+ _updatePeriodModel->setData(3, 0, std::string("monthly"), Wt::UserRole);
_updateStartTimeModel = new Wt::WStringListModel(this);
@@ -290,6 +247,8 @@ const Wt::WFormModel::Field DatabaseFormModel::UpdatePeriodField = "update-peri
const Wt::WFormModel::Field DatabaseFormModel::UpdateStartTimeField = "update-start-time";
const Wt::WFormModel::Field DatabaseFormModel::AudioFileExtensionsField = "audio-file-extensions";
const Wt::WFormModel::Field DatabaseFormModel::VideoFileExtensionsField = "video-file-extensions";
+const Wt::WFormModel::Field DatabaseFormModel::TagsHighLevelAcousticBrainz = "tags-highlevel-acousticbrainz";
+const Wt::WFormModel::Field DatabaseFormModel::TagsSimilarityAcousticBrainz = "tags-similarity-acousticbrainz";
DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent)
@@ -328,6 +287,12 @@ DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent)
setFormWidget(DatabaseFormModel::VideoFileExtensionsField, videoFileExtensionsEdit);
videoFileExtensionsEdit->changed().connect(_applyInfo, &Wt::WWidget::hide);
+ // Tags from AB high level
+ setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainz, new Wt::WCheckBox());
+
+ // Tags from AB similarity
+ setFormWidget(DatabaseFormModel::TagsSimilarityAcousticBrainz, new Wt::WCheckBox());
+
// Title & Buttons
bindString("title", "Database settings");
@@ -353,21 +318,14 @@ DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent)
void
DatabaseFormView::processImmediateScan()
{
- Wt::WString error;
+
+ _model->setImmediateScan();
+
+ _applyInfo->setText( Wt::WString::fromUTF8("Media folder scan has been started!" ) );
+ _applyInfo->setStyleClass("alert alert-warning");
_applyInfo->show();
- if (_model->setImmediateScan(error))
- {
- _applyInfo->setText( Wt::WString::fromUTF8("Media folder scan has been started!" ) );
- _applyInfo->setStyleClass("alert alert-warning");
-
- _sigChanged.emit();
- }
- else
- {
- _applyInfo->setText( error );
- _applyInfo->setStyleClass("alert alert-danger");
- }
+ _sigChanged.emit();
}
void
diff --git a/src/utils/Path.cpp b/src/utils/Path.cpp
index 9921bd65..b7685416 100644
--- a/src/utils/Path.cpp
+++ b/src/utils/Path.cpp
@@ -87,3 +87,12 @@ void computeCrc(const boost::filesystem::path& p, std::vector& cr
crc.push_back(data[i]);
}
}
+
+bool ensureDirectory(boost::filesystem::path dir)
+{
+ if (boost::filesystem::exists(dir))
+ return boost::filesystem::is_directory(dir);
+ else
+ return boost::filesystem::create_directory(dir);
+}
+
diff --git a/src/utils/Path.hpp b/src/utils/Path.hpp
index 96e4726e..8ebcf95a 100644
--- a/src/utils/Path.hpp
+++ b/src/utils/Path.hpp
@@ -27,3 +27,8 @@
boost::filesystem::path searchExecPath(std::string filename);
void computeCrc(const boost::filesystem::path& p, std::vector& checksum);
+
+// Make sure the given path is a directory
+// Create it if needed
+bool ensureDirectory(boost::filesystem::path dir);
+
diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp
index 3f968730..8b71586a 100644
--- a/src/utils/Utils.cpp
+++ b/src/utils/Utils.cpp
@@ -112,6 +112,14 @@ stringTrim(const std::string& str, const std::string& whitespace)
return str.substr(strBegin, strRange);
}
+std::string
+stringTrimEnd(const std::string& str, const std::string& whitespace)
+{
+ return str.substr(0, str.find_last_not_of(whitespace)+1);
+}
+
+
+
std::string
stringToUTF8(const std::string& str)
{
diff --git a/src/utils/Utils.hpp b/src/utils/Utils.hpp
index e8c71ea6..a7e89054 100644
--- a/src/utils/Utils.hpp
+++ b/src/utils/Utils.hpp
@@ -39,7 +39,10 @@ std::vector
splitString(std::string string, std::string separators);
std::string
-stringTrim(const std::string& str, const std::string& whitespace = " \t");
+stringTrim(const std::string& str, const std::string& whitespaces = " \t");
+
+std::string
+stringTrimEnd(const std::string& str, const std::string& whitespaces = " \t");
std::string
stringToUTF8(const std::string& str);