From 2ea4ba7f229c979586e09dd7eb69dac57588bd50 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 28 May 2016 18:32:09 +0200 Subject: [PATCH] [DB] Reworked settings for acousticbrainz tagging --- approot/templates.xml | 24 ++ conf/lms.conf | 20 +- src/database/DatabaseClassifier.cpp | 35 ++- src/database/DatabaseFeatureExtractor.cpp | 6 +- src/database/DatabaseHandler.cpp | 27 ++- src/database/DatabaseUpdater.cpp | 92 ++++--- src/database/MediaDirectory.cpp | 82 +------ src/database/MediaDirectory.hpp | 70 +----- src/database/Setting.cpp | 152 ++++++++++++ src/database/Setting.hpp | 75 ++++++ .../cluster/DatabaseHighLevelCluster.cpp | 226 ++++++++++++++++++ .../cluster/DatabaseHighLevelCluster.hpp | 33 +++ .../cluster/DatabaseSimilarityCluster.hpp | 0 src/database/tagger/GenreTagger.cpp | 40 ++++ src/database/tagger/GenreTagger.hpp | 37 +++ src/feature/FeatureStore.cpp | 17 +- src/main/main.cpp | 25 +- src/ui/settings/Settings.cpp | 24 +- src/ui/settings/SettingsDatabaseFormView.cpp | 130 ++++------ src/utils/Path.cpp | 9 + src/utils/Path.hpp | 5 + src/utils/Utils.cpp | 8 + src/utils/Utils.hpp | 5 +- 23 files changed, 787 insertions(+), 355 deletions(-) create mode 100644 src/database/Setting.cpp create mode 100644 src/database/Setting.hpp create mode 100644 src/database/cluster/DatabaseHighLevelCluster.cpp create mode 100644 src/database/cluster/DatabaseHighLevelCluster.hpp create mode 100644 src/database/cluster/DatabaseSimilarityCluster.hpp create mode 100644 src/database/tagger/GenreTagger.cpp create mode 100644 src/database/tagger/GenreTagger.hpp 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 @@ +
+ +
+ ${tags-highlevel-acousticbrainz} +
+
+ ${tags-highlevel-acousticbrainz-info} +
+
+ +
+ +
+ ${tags-similarity-acousticbrainz} +
+
+ ${tags-similarity-acousticbrainz-info} +
+
+
${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);