From a414a80bbb875d68151f1479f31ca1c6dda54c06 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 16 Feb 2020 12:39:27 +0100 Subject: [PATCH] Further decoupled recommendation engine: it now has its own thread --- src/libs/recommendation/CMakeLists.txt | 1 + src/libs/recommendation/impl/Engine.cpp | 103 +++++++---- src/libs/recommendation/impl/Engine.hpp | 26 ++- .../include/recommendation/IEngine.hpp | 12 +- src/libs/scanner/impl/MediaScanner.cpp | 55 +++--- src/libs/scanner/impl/MediaScanner.hpp | 14 +- .../scanner/include/scanner/IMediaScanner.hpp | 3 - src/libs/utils/impl/Logger.cpp | 2 +- src/libs/utils/include/utils/Logger.hpp | 2 +- .../include/utils/Semaphore.hpp} | 41 +++-- src/lms/main.cpp | 35 ++-- .../recommendation/LmsRecommendation.cpp | 170 ++++++++++-------- 12 files changed, 280 insertions(+), 184 deletions(-) rename src/libs/{scanner/include/scanner/MediaScannerAddon.hpp => utils/include/utils/Semaphore.hpp} (53%) diff --git a/src/libs/recommendation/CMakeLists.txt b/src/libs/recommendation/CMakeLists.txt index 260f137b..26e29757 100644 --- a/src/libs/recommendation/CMakeLists.txt +++ b/src/libs/recommendation/CMakeLists.txt @@ -16,6 +16,7 @@ target_include_directories(lmsrecommendation PRIVATE target_link_libraries(lmsrecommendation PRIVATE lmsdatabase lmssom + wt ) install(TARGETS lmsrecommendation DESTINATION lib) diff --git a/src/libs/recommendation/impl/Engine.cpp b/src/libs/recommendation/impl/Engine.cpp index d53d8645..5b3d818f 100644 --- a/src/libs/recommendation/impl/Engine.cpp +++ b/src/libs/recommendation/impl/Engine.cpp @@ -23,46 +23,51 @@ #include "recommendation/FeaturesClassifierCreator.hpp" #include "database/ScanSettings.hpp" -#include "database/Session.hpp" #include "database/TrackList.hpp" +#include "utils/Logger.hpp" namespace Recommendation { std::unique_ptr -createEngine(Database::Session& session) +createEngine(Database::Db& db) { - return std::make_unique(session); + return std::make_unique(db); } -Engine::Engine(Database::Session& session) +Engine::Engine(Database::Db& db) +: _dbSession {db} { - reload(session); } void -Engine::reload(Database::Session& session) +Engine::start() { - using namespace Database; + assert(!_running); + _running = true; - const ScanSettings::RecommendationEngineType engineType {[&]() + requestReload(); + + _ioService.start(); +} + +void +Engine::stop() +{ + assert(_running); + _running = false; + + _ioService.stop(); +} + +void +Engine::requestReload() +{ + LMS_LOG(RECOMMENDATION, DEBUG) << "Reload requested..."; + + _ioService.post([&]() { - auto transaction {session.createSharedTransaction()}; - return ScanSettings::get(session)->getRecommendationEngineType(); - }()}; - - // TODO: just replace the classifier once it is ready - clearClassifiers(); - - switch (engineType) - { - case ScanSettings::RecommendationEngineType::Features: -// addClassifier(createFeaturesClassifier(), 0); // higher priority -// [[fallthrough]]; - - case ScanSettings::RecommendationEngineType::Clusters: - addClassifier(createClustersClassifier(session), 1); // lower priority - break; - } + reload(); + }); } std::vector @@ -85,7 +90,7 @@ Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::IdTy if (trackIds.empty()) return {}; - std::shared_lock lock {_mutex}; + std::shared_lock lock {_classifiersMutex}; for (const auto& [priority, classifier] : _classifiers) { @@ -99,7 +104,7 @@ Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::IdTy std::vector Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set& trackIds, std::size_t maxCount) { - std::shared_lock lock {_mutex}; + std::shared_lock lock {_classifiersMutex}; for (const auto& [priority, classifier] : _classifiers) { @@ -113,7 +118,7 @@ Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set< std::vector Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount) { - std::shared_lock lock {_mutex}; + std::shared_lock lock {_classifiersMutex}; for (const auto& [priority, classifier] : _classifiers) { @@ -127,7 +132,7 @@ Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releas std::vector Engine::getSimilarArtists(Database::Session& dbSession, Database::IdType artistId, std::size_t maxCount) { - std::shared_lock lock {_mutex}; + std::shared_lock lock {_classifiersMutex}; for (const auto& [priority, classifier] : _classifiers) { @@ -138,17 +143,53 @@ Engine::getSimilarArtists(Database::Session& dbSession, Database::IdType artistI return {}; } +void +Engine::reload() +{ + using namespace Database; + + LMS_LOG(RECOMMENDATION, DEBUG) << "Reloading recommendation engines..."; + + const ScanSettings::RecommendationEngineType engineType {[&]() + { + auto transaction {_dbSession.createSharedTransaction()}; + + return ScanSettings::get(_dbSession)->getRecommendationEngineType(); + }()}; + + // TODO: just replace the classifier once it is ready + clearClassifiers(); + + switch (engineType) + { + case ScanSettings::RecommendationEngineType::Features: +// addClassifier(createFeaturesClassifier(), 0); // higher priority +// [[fallthrough]]; + + case ScanSettings::RecommendationEngineType::Clusters: + addClassifier(createClustersClassifier(_dbSession), 1); // lower priority + break; + } + + LMS_LOG(RECOMMENDATION, DEBUG) << "Recommendation engines reloaded!"; + + _sigReloaded.emit(); +} + + void Engine::clearClassifiers() { - std::unique_lock lock {_mutex}; + std::unique_lock lock {_classifiersMutex}; + _classifiers.clear(); } void Engine::addClassifier(std::unique_ptr classifier, unsigned priority) { - std::unique_lock lock {_mutex}; + std::unique_lock lock {_classifiersMutex}; + _classifiers.emplace(priority, std::move(classifier)); } diff --git a/src/libs/recommendation/impl/Engine.hpp b/src/libs/recommendation/impl/Engine.hpp index 17b9787c..bc6eea96 100644 --- a/src/libs/recommendation/impl/Engine.hpp +++ b/src/libs/recommendation/impl/Engine.hpp @@ -22,35 +22,43 @@ #include #include +#include + +#include "database/Session.hpp" #include "recommendation/IEngine.hpp" #include "recommendation/IClassifier.hpp" -namespace Database -{ - class Session; -} - namespace Recommendation { class Engine : public IEngine { public: - Engine(Database::Session& session); + Engine(Database::Db& db); private: - void reload(Database::Session& session) override; + void start() override; + void stop() override; + + void requestReload() override; + Wt::Signal<>& reloaded() override { return _sigReloaded; } - // Closest results first std::vector getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) override; std::vector getSimilarTracks(Database::Session& session, const std::unordered_set& tracksId, std::size_t maxCount) override; std::vector getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) override; std::vector getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) override; + + void reload(); void clearClassifiers(); void addClassifier(std::unique_ptr classifier, unsigned priority); - std::shared_mutex _mutex; + bool _running {}; + Wt::WIOService _ioService; + Database::Session _dbSession; + Wt::Signal<> _sigReloaded; + + std::shared_mutex _classifiersMutex; std::map> _classifiers; }; diff --git a/src/libs/recommendation/include/recommendation/IEngine.hpp b/src/libs/recommendation/include/recommendation/IEngine.hpp index 4561f19d..37f7cb8c 100644 --- a/src/libs/recommendation/include/recommendation/IEngine.hpp +++ b/src/libs/recommendation/include/recommendation/IEngine.hpp @@ -19,13 +19,17 @@ #pragma once +#include #include #include +#include + #include "database/Types.hpp" namespace Database { + class Db; class Session; } @@ -36,7 +40,11 @@ namespace Recommendation public: virtual ~IEngine() = default; - virtual void reload(Database::Session& session) = 0; + virtual void start() = 0; + virtual void stop() = 0; + + virtual void requestReload() = 0; + virtual Wt::Signal<>& reloaded() = 0; // Closest results first virtual std::vector getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) = 0; @@ -45,7 +53,7 @@ namespace Recommendation virtual std::vector getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) = 0; }; - std::unique_ptr createEngine(Database::Session& session); + std::unique_ptr createEngine(Database::Db& db); } // ns Recommendation diff --git a/src/libs/scanner/impl/MediaScanner.cpp b/src/libs/scanner/impl/MediaScanner.cpp index 6a606773..84e78d92 100644 --- a/src/libs/scanner/impl/MediaScanner.cpp +++ b/src/libs/scanner/impl/MediaScanner.cpp @@ -28,6 +28,7 @@ #include "database/Release.hpp" #include "database/ScanSettings.hpp" #include "database/Track.hpp" +#include "metadata/TagLibParser.hpp" #include "utils/Exception.hpp" #include "utils/Logger.hpp" #include "utils/Path.hpp" @@ -200,15 +201,18 @@ createMediaScanner(Database::Db& db) MediaScanner::MediaScanner(Database::Db& db) : _dbSession {db} { + // For now, always use TagLib + _metadataParser = std::make_unique(); + _ioService.setThreadCount(1); refreshScanSettings(); } -void -MediaScanner::setAddon(MediaScannerAddon& addon) +MediaScanner::~MediaScanner() { - _addons.push_back(&addon); + if (_running) + stop(); } void @@ -233,9 +237,6 @@ MediaScanner::stop(void) { _running = false; - for (auto& addon : _addons) - addon->requestStop(); - _scheduleTimer.cancel(); _ioService.stop(); @@ -281,7 +282,7 @@ MediaScanner::scheduleNextScan() refreshScanSettings(); - Wt::WDateTime now {Wt::WLocalDateTime::currentServerDateTime().toUTC()}; + const Wt::WDateTime now {Wt::WLocalDateTime::currentServerDateTime().toUTC()}; Wt::WDate nextScanDate; switch (_updatePeriod) @@ -421,12 +422,6 @@ MediaScanner::scan(boost::system::error_code err) LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.errors.size() << "), duplicates = " << stats.duplicates.size(); - if (_running) - { - for (auto& addon : _addons) - addon->preScanComplete(); - } - LMS_LOG(DBUPDATER, INFO) << "Optimizing db..."; _dbSession.optimize(); LMS_LOG(DBUPDATER, INFO) << "Optimize db done!"; @@ -457,32 +452,28 @@ MediaScanner::scan(boost::system::error_code err) void MediaScanner::refreshScanSettings() { - { - auto transaction {_dbSession.createSharedTransaction()}; + auto transaction {_dbSession.createSharedTransaction()}; - ScanSettings::pointer scanSettings {ScanSettings::get(_dbSession)}; + ScanSettings::pointer scanSettings {ScanSettings::get(_dbSession)}; - LMS_LOG(DBUPDATER, INFO) << "Using scan settings version " << scanSettings->getScanVersion(); + LMS_LOG(DBUPDATER, INFO) << "Using scan settings version " << scanSettings->getScanVersion(); - _scanVersion = scanSettings->getScanVersion(); - _startTime = scanSettings->getUpdateStartTime(); - _updatePeriod = scanSettings->getUpdatePeriod(); + _scanVersion = scanSettings->getScanVersion(); + _startTime = scanSettings->getUpdateStartTime(); + _updatePeriod = scanSettings->getUpdatePeriod(); - _fileExtensions = scanSettings->getAudioFileExtensions(); - _mediaDirectory = scanSettings->getMediaDirectory(); + _fileExtensions = scanSettings->getAudioFileExtensions(); + _mediaDirectory = scanSettings->getMediaDirectory(); - auto clusterTypes = scanSettings->getClusterTypes(); - std::set clusterTypeNames; + auto clusterTypes = scanSettings->getClusterTypes(); + std::set clusterTypeNames; - std::transform(std::cbegin(clusterTypes), std::cend(clusterTypes), - std::inserter(clusterTypeNames, clusterTypeNames.begin()), - [](ClusterType::pointer clusterType) { return clusterType->getName(); }); + std::transform(std::cbegin(clusterTypes), std::cend(clusterTypes), + std::inserter(clusterTypeNames, clusterTypeNames.begin()), + [](ClusterType::pointer clusterType) { return clusterType->getName(); }); - _metadataParser.setClusterTypeNames(clusterTypeNames); - } + _metadataParser->setClusterTypeNames(clusterTypeNames); - for (auto& addon : _addons) - addon->refreshSettings(); } void @@ -539,7 +530,7 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S } } - std::optional trackInfo {_metadataParser.parse(file)}; + std::optional trackInfo {_metadataParser->parse(file)}; if (!trackInfo) { stats.errors.emplace_back(file, ScanErrorType::CannotParseFile); diff --git a/src/libs/scanner/impl/MediaScanner.hpp b/src/libs/scanner/impl/MediaScanner.hpp index c6452156..54bb70ce 100644 --- a/src/libs/scanner/impl/MediaScanner.hpp +++ b/src/libs/scanner/impl/MediaScanner.hpp @@ -29,9 +29,10 @@ #include +#include "database/Types.hpp" #include "database/ScanSettings.hpp" #include "database/Session.hpp" -#include "metadata/TagLibParser.hpp" +#include "metadata/IParser.hpp" #include "scanner/IMediaScanner.hpp" @@ -41,8 +42,12 @@ class MediaScanner : public IMediaScanner { public: MediaScanner(Database::Db& db); + ~MediaScanner(); - void setAddon(MediaScannerAddon& addon) override; + MediaScanner(const MediaScanner&) = delete; + MediaScanner(MediaScanner&&) = delete; + MediaScanner& operator=(const MediaScanner&) = delete; + MediaScanner& operator=(MediaScanner&&) = delete; void start() override; void stop() override; @@ -80,7 +85,7 @@ class MediaScanner : public IMediaScanner void notifyInProgressIfNeeded(const ScanStats& stats); void notifyInProgress(const ScanStats& stats); - bool _running {false}; + bool _running {}; Wt::WIOService _ioService; boost::asio::system_timer _scheduleTimer {_ioService}; Wt::Signal<> _sigScanComplete; @@ -88,8 +93,7 @@ class MediaScanner : public IMediaScanner std::chrono::system_clock::time_point _lastScanInProgressEmit {}; Wt::Signal _sigScheduled; Database::Session _dbSession; - MetaData::TagLibParser _metadataParser; - std::vector _addons; + std::unique_ptr _metadataParser; std::mutex _statusMutex; State _curState {State::NotScheduled}; diff --git a/src/libs/scanner/include/scanner/IMediaScanner.hpp b/src/libs/scanner/include/scanner/IMediaScanner.hpp index 49b68048..e50be005 100644 --- a/src/libs/scanner/include/scanner/IMediaScanner.hpp +++ b/src/libs/scanner/include/scanner/IMediaScanner.hpp @@ -24,7 +24,6 @@ #include #include -#include "MediaScannerAddon.hpp" #include "MediaScannerStats.hpp" namespace Database @@ -39,8 +38,6 @@ class IMediaScanner public: virtual ~IMediaScanner() = default; - virtual void setAddon(MediaScannerAddon& addon) = 0; - virtual void start() = 0; virtual void stop() = 0; virtual void restart() = 0; diff --git a/src/libs/utils/impl/Logger.cpp b/src/libs/utils/impl/Logger.cpp index b0d99437..602fdbc3 100644 --- a/src/libs/utils/impl/Logger.cpp +++ b/src/libs/utils/impl/Logger.cpp @@ -34,7 +34,7 @@ const char* getModuleName(Module mod) case Module::METADATA: return "METADATA"; case Module::REMOTE: return "REMOTE"; case Module::SERVICE: return "SERVICE"; - case Module::SIMILARITY: return "SIMILARITY"; + case Module::RECOMMENDATION: return "RECOMMENDATION"; case Module::TRANSCODE: return "TRANSCODE"; case Module::UI: return "UI"; } diff --git a/src/libs/utils/include/utils/Logger.hpp b/src/libs/utils/include/utils/Logger.hpp index 9597aa5c..9f5b2ec5 100644 --- a/src/libs/utils/include/utils/Logger.hpp +++ b/src/libs/utils/include/utils/Logger.hpp @@ -46,7 +46,7 @@ enum class Module METADATA, REMOTE, SERVICE, - SIMILARITY, + RECOMMENDATION, TRANSCODE, UI, }; diff --git a/src/libs/scanner/include/scanner/MediaScannerAddon.hpp b/src/libs/utils/include/utils/Semaphore.hpp similarity index 53% rename from src/libs/scanner/include/scanner/MediaScannerAddon.hpp rename to src/libs/utils/include/utils/Semaphore.hpp index fdfd9577..7b82879d 100644 --- a/src/libs/scanner/include/scanner/MediaScannerAddon.hpp +++ b/src/libs/utils/include/utils/Semaphore.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Emeric Poupon + * Copyright (C) 2020 Emeric Poupon * * This file is part of LMS. * @@ -19,23 +19,40 @@ #pragma once -#include "database/Types.hpp" +#include +#include -namespace Scanner { - -class MediaScannerAddon +class Semaphore { public: + Semaphore() = default; + Semaphore(const Semaphore&) = delete; + Semaphore(Semaphore&&) = delete; + Semaphore& operator=(const Semaphore&) = delete; + Semaphore& operator=(Semaphore&&) = delete; - virtual void refreshSettings() = 0; - virtual void requestStop() = 0; - virtual void preScanComplete() = 0; + void notify() + { + std::unique_lock lock {_mutex}; - virtual void trackAdded(Database::IdType trackId) = 0; - virtual void trackToRemove(Database::IdType trackId) = 0; - virtual void trackUpdated(Database::IdType trackId) = 0; + _count++; + _cv.notify_one(); + } + void wait() + { + std::unique_lock lock(_mutex); + + while (_count == 0) + _cv.wait(lock); + + _count--; + } + + private: + std::mutex _mutex; + std::condition_variable _cv; + unsigned _count {}; }; -} // ns Scanner diff --git a/src/lms/main.cpp b/src/lms/main.cpp index b82a7917..1b6e504e 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -143,26 +143,21 @@ int main(int argc, char* argv[]) ServiceProvider::assign(Auth::createPasswordService(ServiceProvider::get()->getULong("login-throttler-max-entriees", 10000))); Scanner::IMediaScanner& mediaScanner {ServiceProvider::assign(Scanner::createMediaScanner(database))}; + Recommendation::IEngine& recommendationEngine {ServiceProvider::assign(Recommendation::createEngine(database))}; + mediaScanner.scanComplete().connect([&]() { - Database::Session session {database}; - Recommendation::IEngine& recommendationEngine {ServiceProvider::assign(Recommendation::createEngine(session))}; + auto status = mediaScanner.getStatus(); - mediaScanner.scanComplete().connect([&]() + if (status.lastCompleteScanStats->nbChanges() > 0) { - auto status = mediaScanner.getStatus(); - - if (status.lastCompleteScanStats->nbChanges() > 0) - { - LMS_LOG(MAIN, INFO) << "Scanner changed some files, reloading the recommendation engine..."; - Database::Session session {database}; - recommendationEngine.reload(session); - } - else - { - LMS_LOG(MAIN, INFO) << "Scanner did not change files, not reloading the recommendation engine..."; - } - }); - } + LMS_LOG(MAIN, INFO) << "Scanner changed some files, reloading the recommendation engine..."; + recommendationEngine.requestReload(); + } + else + { + LMS_LOG(MAIN, INFO) << "Scanner did not change files, not reloading the recommendation engine..."; + } + }); CoverArt::IGrabber& coverArtGrabber {ServiceProvider::assign(CoverArt::createGrabber(argv[0]))}; coverArtGrabber.setDefaultCover(server.appRoot() + "/images/unknown-cover.jpg"); @@ -179,6 +174,9 @@ int main(int argc, char* argv[]) std::placeholders::_1, std::ref(database), std::ref(appGroups))); // Start + LMS_LOG(MAIN, INFO) << "Starting recommendation engine"; + recommendationEngine.start(); + LMS_LOG(MAIN, INFO) << "Starting media scanner..."; mediaScanner.start(); @@ -196,6 +194,9 @@ int main(int argc, char* argv[]) LMS_LOG(MAIN, INFO) << "Stopping media scanner..."; mediaScanner.stop(); + LMS_LOG(MAIN, INFO) << "Stopping recommendation engine..."; + recommendationEngine.stop(); + LMS_LOG(MAIN, INFO) << "Clean stop!"; res = EXIT_SUCCESS; } diff --git a/src/tools/recommendation/LmsRecommendation.cpp b/src/tools/recommendation/LmsRecommendation.cpp index 2ff53459..b5ea5aa4 100644 --- a/src/tools/recommendation/LmsRecommendation.cpp +++ b/src/tools/recommendation/LmsRecommendation.cpp @@ -29,10 +29,97 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "utils/IConfig.hpp" +#include "utils/Semaphore.hpp" #include "utils/Service.hpp" #include "utils/StreamLogger.hpp" #include "recommendation/IEngine.hpp" + +static +void +dumpRecommendation(Database::Session session, Recommendation::IEngine& engine) +{ + const std::vector trackIds {[&]() + { + auto transaction {session.createSharedTransaction()}; + return Database::Track::getAllIds(session); + }()}; + + std::cout << "*** Tracks (" << trackIds.size() << ") ***" << std::endl; + for (Database::IdType trackId : trackIds) + { + auto trackToString = [&](Database::IdType trackId) + { + std::string res; + auto transaction {session.createSharedTransaction()}; + Database::Track::pointer track {Database::Track::getById(session, trackId)}; + + res += track->getName(); + if (track->getRelease()) + res += " [" + track->getRelease()->getName() + "]"; + for (auto artist : track->getArtists()) + res += " - " + artist->getName(); + for (auto cluster : track->getClusters()) + res += " {" + cluster->getType()->getName() + "-"+ cluster->getName() + "}"; + + return res; + }; + + std::cout << "Processing track '" << trackToString(trackId) << std::endl; + for (Database::IdType similarTrackId : engine.getSimilarTracks(session, {trackId}, 3)) + std::cout << "\t- Similar track '" << trackToString(similarTrackId) << std::endl; + } + + const std::vector releaseIds = std::invoke([&]() + { + auto transaction {session.createSharedTransaction()}; + return Database::Release::getAllIds(session); + }); + + std::cout << "*** Releases ***" << std::endl; + for (Database::IdType releaseId : releaseIds) + { + auto releaseToString = [&](Database::IdType releaseId) + { + auto transaction {session.createSharedTransaction()}; + + Database::Release::pointer release {Database::Release::getById(session, releaseId)}; + return release->getName(); + }; + + std::cout << "Processing release '" << releaseToString(releaseId) << "'" << std::endl; + for (Database::IdType similarReleaseId : engine.getSimilarReleases(session, {releaseId}, 3)) + std::cout << "\t- Similar release '" << releaseToString(similarReleaseId) << "'" << std::endl; + } + + const std::vector artistIds = std::invoke([&]() + { + auto transaction {session.createSharedTransaction()}; + return Database::Artist::getAllIds(session); + }); + + std::cout << "*** Artists ***" << std::endl; + for (Database::IdType artistId : artistIds) + { + auto artistToString = [&](Database::IdType artistId) + { + auto transaction {session.createSharedTransaction()}; + + Database::Artist::pointer artist {Database::Artist::getById(session, artistId)}; + return artist->getName(); + }; + + std::cout << "Processing artist '" << artistToString(artistId) << "'" << std::endl; + for (Database::IdType similarArtistId : engine.getSimilarArtists(session, {artistId}, 3)) + std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl; + } + + + + +} + + int main(int argc, char *argv[]) { try @@ -50,84 +137,25 @@ int main(int argc, char *argv[]) Database::Session session {db}; std::cout << "Creating recommendation engine..." << std::endl; - const auto engine {Recommendation::createEngine(session)}; - std::cout << "DONE!" << std::endl; + const auto engine {Recommendation::createEngine(db)}; + std::cout << "Recommendation engine created!" << std::endl; - const std::vector trackIds {[&]() - { - auto transaction {session.createSharedTransaction()}; - return Database::Track::getAllIds(session); - }()}; + Semaphore sem; - std::cout << "*** Tracks (" << trackIds.size() << ") ***" << std::endl; - for (Database::IdType trackId : trackIds) + engine->reloaded().connect([&]() { - auto trackToString = [&](Database::IdType trackId) - { - std::string res; - auto transaction {session.createSharedTransaction()}; - Database::Track::pointer track {Database::Track::getById(session, trackId)}; + sem.notify(); + }); - res += track->getName(); - if (track->getRelease()) - res += " [" + track->getRelease()->getName() + "]"; - for (auto artist : track->getArtists()) - res += " - " + artist->getName(); - for (auto cluster : track->getClusters()) - res += " {" + cluster->getType()->getName() + "-"+ cluster->getName() + "}"; + engine->start(); - return res; - }; + std::cout << "Wating for the recommendation engine to be loaded..." << std::endl; + sem.wait(); + std::cout << "Recommendation engine loaded!" << std::endl; - std::cout << "Processing track '" << trackToString(trackId) << std::endl; - for (Database::IdType similarTrackId : engine->getSimilarTracks(session, {trackId}, 3)) - std::cout << "\t- Similar track '" << trackToString(similarTrackId) << std::endl; - } - - const std::vector releaseIds = std::invoke([&]() - { - auto transaction {session.createSharedTransaction()}; - return Database::Release::getAllIds(session); - }); - - std::cout << "*** Releases ***" << std::endl; - for (Database::IdType releaseId : releaseIds) - { - auto releaseToString = [&](Database::IdType releaseId) - { - auto transaction {session.createSharedTransaction()}; - - Database::Release::pointer release {Database::Release::getById(session, releaseId)}; - return release->getName(); - }; - - std::cout << "Processing release '" << releaseToString(releaseId) << "'" << std::endl; - for (Database::IdType similarReleaseId : engine->getSimilarReleases(session, {releaseId}, 3)) - std::cout << "\t- Similar release '" << releaseToString(similarReleaseId) << "'" << std::endl; - } - - const std::vector artistIds = std::invoke([&]() - { - auto transaction {session.createSharedTransaction()}; - return Database::Artist::getAllIds(session); - }); - - std::cout << "*** Artists ***" << std::endl; - for (Database::IdType artistId : artistIds) - { - auto artistToString = [&](Database::IdType artistId) - { - auto transaction {session.createSharedTransaction()}; - - Database::Artist::pointer artist {Database::Artist::getById(session, artistId)}; - return artist->getName(); - }; - - std::cout << "Processing artist '" << artistToString(artistId) << "'" << std::endl; - for (Database::IdType similarArtistId : engine->getSimilarArtists(session, {artistId}, 3)) - std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl; - } + dumpRecommendation(db, *engine); + engine->stop(); } catch( std::exception& e) {