Further decoupled recommendation engine: it now has its own thread

This commit is contained in:
emeric
2020-02-16 12:39:27 +01:00
parent 6239064396
commit a414a80bbb
12 changed files with 280 additions and 184 deletions
+1
View File
@@ -16,6 +16,7 @@ target_include_directories(lmsrecommendation PRIVATE
target_link_libraries(lmsrecommendation PRIVATE target_link_libraries(lmsrecommendation PRIVATE
lmsdatabase lmsdatabase
lmssom lmssom
wt
) )
install(TARGETS lmsrecommendation DESTINATION lib) install(TARGETS lmsrecommendation DESTINATION lib)
+72 -31
View File
@@ -23,46 +23,51 @@
#include "recommendation/FeaturesClassifierCreator.hpp" #include "recommendation/FeaturesClassifierCreator.hpp"
#include "database/ScanSettings.hpp" #include "database/ScanSettings.hpp"
#include "database/Session.hpp"
#include "database/TrackList.hpp" #include "database/TrackList.hpp"
#include "utils/Logger.hpp"
namespace Recommendation { namespace Recommendation {
std::unique_ptr<IEngine> std::unique_ptr<IEngine>
createEngine(Database::Session& session) createEngine(Database::Db& db)
{ {
return std::make_unique<Engine>(session); return std::make_unique<Engine>(db);
} }
Engine::Engine(Database::Session& session) Engine::Engine(Database::Db& db)
: _dbSession {db}
{ {
reload(session);
} }
void 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()}; reload();
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;
}
} }
std::vector<Database::IdType> std::vector<Database::IdType>
@@ -85,7 +90,7 @@ Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::IdTy
if (trackIds.empty()) if (trackIds.empty())
return {}; return {};
std::shared_lock lock {_mutex}; std::shared_lock lock {_classifiersMutex};
for (const auto& [priority, classifier] : _classifiers) for (const auto& [priority, classifier] : _classifiers)
{ {
@@ -99,7 +104,7 @@ Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::IdTy
std::vector<Database::IdType> std::vector<Database::IdType>
Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<Database::IdType>& trackIds, std::size_t maxCount) Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<Database::IdType>& trackIds, std::size_t maxCount)
{ {
std::shared_lock lock {_mutex}; std::shared_lock lock {_classifiersMutex};
for (const auto& [priority, classifier] : _classifiers) for (const auto& [priority, classifier] : _classifiers)
{ {
@@ -113,7 +118,7 @@ Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<
std::vector<Database::IdType> std::vector<Database::IdType>
Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount) 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) for (const auto& [priority, classifier] : _classifiers)
{ {
@@ -127,7 +132,7 @@ Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releas
std::vector<Database::IdType> std::vector<Database::IdType>
Engine::getSimilarArtists(Database::Session& dbSession, Database::IdType artistId, std::size_t maxCount) 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) for (const auto& [priority, classifier] : _classifiers)
{ {
@@ -138,17 +143,53 @@ Engine::getSimilarArtists(Database::Session& dbSession, Database::IdType artistI
return {}; 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 void
Engine::clearClassifiers() Engine::clearClassifiers()
{ {
std::unique_lock lock {_mutex}; std::unique_lock lock {_classifiersMutex};
_classifiers.clear(); _classifiers.clear();
} }
void void
Engine::addClassifier(std::unique_ptr<IClassifier> classifier, unsigned priority) Engine::addClassifier(std::unique_ptr<IClassifier> classifier, unsigned priority)
{ {
std::unique_lock lock {_mutex}; std::unique_lock lock {_classifiersMutex};
_classifiers.emplace(priority, std::move(classifier)); _classifiers.emplace(priority, std::move(classifier));
} }
+17 -9
View File
@@ -22,35 +22,43 @@
#include <map> #include <map>
#include <shared_mutex> #include <shared_mutex>
#include <Wt/WIOService.h>
#include "database/Session.hpp"
#include "recommendation/IEngine.hpp" #include "recommendation/IEngine.hpp"
#include "recommendation/IClassifier.hpp" #include "recommendation/IClassifier.hpp"
namespace Database
{
class Session;
}
namespace Recommendation namespace Recommendation
{ {
class Engine : public IEngine class Engine : public IEngine
{ {
public: public:
Engine(Database::Session& session); Engine(Database::Db& db);
private: 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<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) override; std::vector<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) override;
std::vector<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) override; std::vector<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) override;
std::vector<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) override; std::vector<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) override;
std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) override; std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) override;
void reload();
void clearClassifiers(); void clearClassifiers();
void addClassifier(std::unique_ptr<IClassifier> classifier, unsigned priority); void addClassifier(std::unique_ptr<IClassifier> classifier, unsigned priority);
std::shared_mutex _mutex; bool _running {};
Wt::WIOService _ioService;
Database::Session _dbSession;
Wt::Signal<> _sigReloaded;
std::shared_mutex _classifiersMutex;
std::map<unsigned, std::unique_ptr<IClassifier>> _classifiers; std::map<unsigned, std::unique_ptr<IClassifier>> _classifiers;
}; };
@@ -19,13 +19,17 @@
#pragma once #pragma once
#include <functional>
#include <vector> #include <vector>
#include <unordered_set> #include <unordered_set>
#include <Wt/WSignal.h>
#include "database/Types.hpp" #include "database/Types.hpp"
namespace Database namespace Database
{ {
class Db;
class Session; class Session;
} }
@@ -36,7 +40,11 @@ namespace Recommendation
public: public:
virtual ~IEngine() = default; 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 // Closest results first
virtual std::vector<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) = 0; virtual std::vector<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) = 0;
@@ -45,7 +53,7 @@ namespace Recommendation
virtual std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) = 0; virtual std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) = 0;
}; };
std::unique_ptr<IEngine> createEngine(Database::Session& session); std::unique_ptr<IEngine> createEngine(Database::Db& db);
} // ns Recommendation } // ns Recommendation
+23 -32
View File
@@ -28,6 +28,7 @@
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/ScanSettings.hpp" #include "database/ScanSettings.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "metadata/TagLibParser.hpp"
#include "utils/Exception.hpp" #include "utils/Exception.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/Path.hpp" #include "utils/Path.hpp"
@@ -200,15 +201,18 @@ createMediaScanner(Database::Db& db)
MediaScanner::MediaScanner(Database::Db& db) MediaScanner::MediaScanner(Database::Db& db)
: _dbSession {db} : _dbSession {db}
{ {
// For now, always use TagLib
_metadataParser = std::make_unique<MetaData::TagLibParser>();
_ioService.setThreadCount(1); _ioService.setThreadCount(1);
refreshScanSettings(); refreshScanSettings();
} }
void MediaScanner::~MediaScanner()
MediaScanner::setAddon(MediaScannerAddon& addon)
{ {
_addons.push_back(&addon); if (_running)
stop();
} }
void void
@@ -233,9 +237,6 @@ MediaScanner::stop(void)
{ {
_running = false; _running = false;
for (auto& addon : _addons)
addon->requestStop();
_scheduleTimer.cancel(); _scheduleTimer.cancel();
_ioService.stop(); _ioService.stop();
@@ -281,7 +282,7 @@ MediaScanner::scheduleNextScan()
refreshScanSettings(); refreshScanSettings();
Wt::WDateTime now {Wt::WLocalDateTime::currentServerDateTime().toUTC()}; const Wt::WDateTime now {Wt::WLocalDateTime::currentServerDateTime().toUTC()};
Wt::WDate nextScanDate; Wt::WDate nextScanDate;
switch (_updatePeriod) 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(); 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..."; LMS_LOG(DBUPDATER, INFO) << "Optimizing db...";
_dbSession.optimize(); _dbSession.optimize();
LMS_LOG(DBUPDATER, INFO) << "Optimize db done!"; LMS_LOG(DBUPDATER, INFO) << "Optimize db done!";
@@ -457,32 +452,28 @@ MediaScanner::scan(boost::system::error_code err)
void void
MediaScanner::refreshScanSettings() 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(); _scanVersion = scanSettings->getScanVersion();
_startTime = scanSettings->getUpdateStartTime(); _startTime = scanSettings->getUpdateStartTime();
_updatePeriod = scanSettings->getUpdatePeriod(); _updatePeriod = scanSettings->getUpdatePeriod();
_fileExtensions = scanSettings->getAudioFileExtensions(); _fileExtensions = scanSettings->getAudioFileExtensions();
_mediaDirectory = scanSettings->getMediaDirectory(); _mediaDirectory = scanSettings->getMediaDirectory();
auto clusterTypes = scanSettings->getClusterTypes(); auto clusterTypes = scanSettings->getClusterTypes();
std::set<std::string> clusterTypeNames; std::set<std::string> clusterTypeNames;
std::transform(std::cbegin(clusterTypes), std::cend(clusterTypes), std::transform(std::cbegin(clusterTypes), std::cend(clusterTypes),
std::inserter(clusterTypeNames, clusterTypeNames.begin()), std::inserter(clusterTypeNames, clusterTypeNames.begin()),
[](ClusterType::pointer clusterType) { return clusterType->getName(); }); [](ClusterType::pointer clusterType) { return clusterType->getName(); });
_metadataParser.setClusterTypeNames(clusterTypeNames); _metadataParser->setClusterTypeNames(clusterTypeNames);
}
for (auto& addon : _addons)
addon->refreshSettings();
} }
void void
@@ -539,7 +530,7 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
} }
} }
std::optional<MetaData::Track> trackInfo {_metadataParser.parse(file)}; std::optional<MetaData::Track> trackInfo {_metadataParser->parse(file)};
if (!trackInfo) if (!trackInfo)
{ {
stats.errors.emplace_back(file, ScanErrorType::CannotParseFile); stats.errors.emplace_back(file, ScanErrorType::CannotParseFile);
+9 -5
View File
@@ -29,9 +29,10 @@
#include <boost/asio/system_timer.hpp> #include <boost/asio/system_timer.hpp>
#include "database/Types.hpp"
#include "database/ScanSettings.hpp" #include "database/ScanSettings.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "metadata/TagLibParser.hpp" #include "metadata/IParser.hpp"
#include "scanner/IMediaScanner.hpp" #include "scanner/IMediaScanner.hpp"
@@ -41,8 +42,12 @@ class MediaScanner : public IMediaScanner
{ {
public: public:
MediaScanner(Database::Db& db); 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 start() override;
void stop() override; void stop() override;
@@ -80,7 +85,7 @@ class MediaScanner : public IMediaScanner
void notifyInProgressIfNeeded(const ScanStats& stats); void notifyInProgressIfNeeded(const ScanStats& stats);
void notifyInProgress(const ScanStats& stats); void notifyInProgress(const ScanStats& stats);
bool _running {false}; bool _running {};
Wt::WIOService _ioService; Wt::WIOService _ioService;
boost::asio::system_timer _scheduleTimer {_ioService}; boost::asio::system_timer _scheduleTimer {_ioService};
Wt::Signal<> _sigScanComplete; Wt::Signal<> _sigScanComplete;
@@ -88,8 +93,7 @@ class MediaScanner : public IMediaScanner
std::chrono::system_clock::time_point _lastScanInProgressEmit {}; std::chrono::system_clock::time_point _lastScanInProgressEmit {};
Wt::Signal<Wt::WDateTime> _sigScheduled; Wt::Signal<Wt::WDateTime> _sigScheduled;
Database::Session _dbSession; Database::Session _dbSession;
MetaData::TagLibParser _metadataParser; std::unique_ptr<MetaData::IParser> _metadataParser;
std::vector<MediaScannerAddon*> _addons;
std::mutex _statusMutex; std::mutex _statusMutex;
State _curState {State::NotScheduled}; State _curState {State::NotScheduled};
@@ -24,7 +24,6 @@
#include <Wt/WDateTime.h> #include <Wt/WDateTime.h>
#include <Wt/WSignal.h> #include <Wt/WSignal.h>
#include "MediaScannerAddon.hpp"
#include "MediaScannerStats.hpp" #include "MediaScannerStats.hpp"
namespace Database namespace Database
@@ -39,8 +38,6 @@ class IMediaScanner
public: public:
virtual ~IMediaScanner() = default; virtual ~IMediaScanner() = default;
virtual void setAddon(MediaScannerAddon& addon) = 0;
virtual void start() = 0; virtual void start() = 0;
virtual void stop() = 0; virtual void stop() = 0;
virtual void restart() = 0; virtual void restart() = 0;
+1 -1
View File
@@ -34,7 +34,7 @@ const char* getModuleName(Module mod)
case Module::METADATA: return "METADATA"; case Module::METADATA: return "METADATA";
case Module::REMOTE: return "REMOTE"; case Module::REMOTE: return "REMOTE";
case Module::SERVICE: return "SERVICE"; case Module::SERVICE: return "SERVICE";
case Module::SIMILARITY: return "SIMILARITY"; case Module::RECOMMENDATION: return "RECOMMENDATION";
case Module::TRANSCODE: return "TRANSCODE"; case Module::TRANSCODE: return "TRANSCODE";
case Module::UI: return "UI"; case Module::UI: return "UI";
} }
+1 -1
View File
@@ -46,7 +46,7 @@ enum class Module
METADATA, METADATA,
REMOTE, REMOTE,
SERVICE, SERVICE,
SIMILARITY, RECOMMENDATION,
TRANSCODE, TRANSCODE,
UI, UI,
}; };
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 Emeric Poupon * Copyright (C) 2020 Emeric Poupon
* *
* This file is part of LMS. * This file is part of LMS.
* *
@@ -19,23 +19,40 @@
#pragma once #pragma once
#include "database/Types.hpp" #include <mutex>
#include <condition_variable>
namespace Scanner { class Semaphore
class MediaScannerAddon
{ {
public: public:
Semaphore() = default;
Semaphore(const Semaphore&) = delete;
Semaphore(Semaphore&&) = delete;
Semaphore& operator=(const Semaphore&) = delete;
Semaphore& operator=(Semaphore&&) = delete;
virtual void refreshSettings() = 0; void notify()
virtual void requestStop() = 0; {
virtual void preScanComplete() = 0; std::unique_lock<std::mutex> lock {_mutex};
virtual void trackAdded(Database::IdType trackId) = 0; _count++;
virtual void trackToRemove(Database::IdType trackId) = 0; _cv.notify_one();
virtual void trackUpdated(Database::IdType trackId) = 0; }
void wait()
{
std::unique_lock<std::mutex> lock(_mutex);
while (_count == 0)
_cv.wait(lock);
_count--;
}
private:
std::mutex _mutex;
std::condition_variable _cv;
unsigned _count {};
}; };
} // ns Scanner
+18 -17
View File
@@ -143,26 +143,21 @@ int main(int argc, char* argv[])
ServiceProvider<Auth::IPasswordService>::assign(Auth::createPasswordService(ServiceProvider<IConfig>::get()->getULong("login-throttler-max-entriees", 10000))); ServiceProvider<Auth::IPasswordService>::assign(Auth::createPasswordService(ServiceProvider<IConfig>::get()->getULong("login-throttler-max-entriees", 10000)));
Scanner::IMediaScanner& mediaScanner {ServiceProvider<Scanner::IMediaScanner>::assign(Scanner::createMediaScanner(database))}; Scanner::IMediaScanner& mediaScanner {ServiceProvider<Scanner::IMediaScanner>::assign(Scanner::createMediaScanner(database))};
Recommendation::IEngine& recommendationEngine {ServiceProvider<Recommendation::IEngine>::assign(Recommendation::createEngine(database))};
mediaScanner.scanComplete().connect([&]()
{ {
Database::Session session {database}; auto status = mediaScanner.getStatus();
Recommendation::IEngine& recommendationEngine {ServiceProvider<Recommendation::IEngine>::assign(Recommendation::createEngine(session))};
mediaScanner.scanComplete().connect([&]() if (status.lastCompleteScanStats->nbChanges() > 0)
{ {
auto status = mediaScanner.getStatus(); LMS_LOG(MAIN, INFO) << "Scanner changed some files, reloading the recommendation engine...";
recommendationEngine.requestReload();
if (status.lastCompleteScanStats->nbChanges() > 0) }
{ else
LMS_LOG(MAIN, INFO) << "Scanner changed some files, reloading the recommendation engine..."; {
Database::Session session {database}; LMS_LOG(MAIN, INFO) << "Scanner did not change files, not reloading the recommendation engine...";
recommendationEngine.reload(session); }
} });
else
{
LMS_LOG(MAIN, INFO) << "Scanner did not change files, not reloading the recommendation engine...";
}
});
}
CoverArt::IGrabber& coverArtGrabber {ServiceProvider<CoverArt::IGrabber>::assign(CoverArt::createGrabber(argv[0]))}; CoverArt::IGrabber& coverArtGrabber {ServiceProvider<CoverArt::IGrabber>::assign(CoverArt::createGrabber(argv[0]))};
coverArtGrabber.setDefaultCover(server.appRoot() + "/images/unknown-cover.jpg"); 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))); std::placeholders::_1, std::ref(database), std::ref(appGroups)));
// Start // Start
LMS_LOG(MAIN, INFO) << "Starting recommendation engine";
recommendationEngine.start();
LMS_LOG(MAIN, INFO) << "Starting media scanner..."; LMS_LOG(MAIN, INFO) << "Starting media scanner...";
mediaScanner.start(); mediaScanner.start();
@@ -196,6 +194,9 @@ int main(int argc, char* argv[])
LMS_LOG(MAIN, INFO) << "Stopping media scanner..."; LMS_LOG(MAIN, INFO) << "Stopping media scanner...";
mediaScanner.stop(); mediaScanner.stop();
LMS_LOG(MAIN, INFO) << "Stopping recommendation engine...";
recommendationEngine.stop();
LMS_LOG(MAIN, INFO) << "Clean stop!"; LMS_LOG(MAIN, INFO) << "Clean stop!";
res = EXIT_SUCCESS; res = EXIT_SUCCESS;
} }
+99 -71
View File
@@ -29,10 +29,97 @@
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "utils/IConfig.hpp" #include "utils/IConfig.hpp"
#include "utils/Semaphore.hpp"
#include "utils/Service.hpp" #include "utils/Service.hpp"
#include "utils/StreamLogger.hpp" #include "utils/StreamLogger.hpp"
#include "recommendation/IEngine.hpp" #include "recommendation/IEngine.hpp"
static
void
dumpRecommendation(Database::Session session, Recommendation::IEngine& engine)
{
const std::vector<Database::IdType> 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<Database::IdType> 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<Database::IdType> 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[]) int main(int argc, char *argv[])
{ {
try try
@@ -50,84 +137,25 @@ int main(int argc, char *argv[])
Database::Session session {db}; Database::Session session {db};
std::cout << "Creating recommendation engine..." << std::endl; std::cout << "Creating recommendation engine..." << std::endl;
const auto engine {Recommendation::createEngine(session)}; const auto engine {Recommendation::createEngine(db)};
std::cout << "DONE!" << std::endl; std::cout << "Recommendation engine created!" << std::endl;
const std::vector<Database::IdType> trackIds {[&]() Semaphore sem;
{
auto transaction {session.createSharedTransaction()};
return Database::Track::getAllIds(session);
}()};
std::cout << "*** Tracks (" << trackIds.size() << ") ***" << std::endl; engine->reloaded().connect([&]()
for (Database::IdType trackId : trackIds)
{ {
auto trackToString = [&](Database::IdType trackId) sem.notify();
{ });
std::string res;
auto transaction {session.createSharedTransaction()};
Database::Track::pointer track {Database::Track::getById(session, trackId)};
res += track->getName(); engine->start();
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 << "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; dumpRecommendation(db, *engine);
for (Database::IdType similarTrackId : engine->getSimilarTracks(session, {trackId}, 3))
std::cout << "\t- Similar track '" << trackToString(similarTrackId) << std::endl;
}
const std::vector<Database::IdType> 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<Database::IdType> 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;
}
engine->stop();
} }
catch( std::exception& e) catch( std::exception& e)
{ {