Further decoupled recommendation engine: it now has its own thread
This commit is contained in:
@@ -16,6 +16,7 @@ target_include_directories(lmsrecommendation PRIVATE
|
||||
target_link_libraries(lmsrecommendation PRIVATE
|
||||
lmsdatabase
|
||||
lmssom
|
||||
wt
|
||||
)
|
||||
|
||||
install(TARGETS lmsrecommendation DESTINATION lib)
|
||||
|
||||
@@ -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<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
|
||||
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<Database::IdType>
|
||||
@@ -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<Database::IdType>
|
||||
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)
|
||||
{
|
||||
@@ -113,7 +118,7 @@ Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<
|
||||
std::vector<Database::IdType>
|
||||
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<Database::IdType>
|
||||
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<IClassifier> classifier, unsigned priority)
|
||||
{
|
||||
std::unique_lock lock {_mutex};
|
||||
std::unique_lock lock {_classifiersMutex};
|
||||
|
||||
_classifiers.emplace(priority, std::move(classifier));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,35 +22,43 @@
|
||||
#include <map>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include <Wt/WIOService.h>
|
||||
|
||||
#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<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> 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;
|
||||
|
||||
|
||||
void reload();
|
||||
void clearClassifiers();
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,13 +19,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <Wt/WSignal.h>
|
||||
|
||||
#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<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;
|
||||
};
|
||||
|
||||
std::unique_ptr<IEngine> createEngine(Database::Session& session);
|
||||
std::unique_ptr<IEngine> createEngine(Database::Db& db);
|
||||
|
||||
} // ns Recommendation
|
||||
|
||||
|
||||
@@ -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<MetaData::TagLibParser>();
|
||||
|
||||
_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<std::string> clusterTypeNames;
|
||||
auto clusterTypes = scanSettings->getClusterTypes();
|
||||
std::set<std::string> 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<MetaData::Track> trackInfo {_metadataParser.parse(file)};
|
||||
std::optional<MetaData::Track> trackInfo {_metadataParser->parse(file)};
|
||||
if (!trackInfo)
|
||||
{
|
||||
stats.errors.emplace_back(file, ScanErrorType::CannotParseFile);
|
||||
|
||||
@@ -29,9 +29,10 @@
|
||||
|
||||
#include <boost/asio/system_timer.hpp>
|
||||
|
||||
#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<Wt::WDateTime> _sigScheduled;
|
||||
Database::Session _dbSession;
|
||||
MetaData::TagLibParser _metadataParser;
|
||||
std::vector<MediaScannerAddon*> _addons;
|
||||
std::unique_ptr<MetaData::IParser> _metadataParser;
|
||||
|
||||
std::mutex _statusMutex;
|
||||
State _curState {State::NotScheduled};
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <Wt/WDateTime.h>
|
||||
#include <Wt/WSignal.h>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ enum class Module
|
||||
METADATA,
|
||||
REMOTE,
|
||||
SERVICE,
|
||||
SIMILARITY,
|
||||
RECOMMENDATION,
|
||||
TRANSCODE,
|
||||
UI,
|
||||
};
|
||||
|
||||
+29
-12
@@ -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 <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
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<std::mutex> 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<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
@@ -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)));
|
||||
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};
|
||||
Recommendation::IEngine& recommendationEngine {ServiceProvider<Recommendation::IEngine>::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<CoverArt::IGrabber>::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;
|
||||
}
|
||||
|
||||
@@ -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<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[])
|
||||
{
|
||||
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<Database::IdType> 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<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;
|
||||
}
|
||||
dumpRecommendation(db, *engine);
|
||||
|
||||
engine->stop();
|
||||
}
|
||||
catch( std::exception& e)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user