Reworked internal namespaces
This commit is contained in:
@@ -19,7 +19,7 @@ target_include_directories(lmsauth PRIVATE
|
||||
)
|
||||
|
||||
target_link_libraries(lmsauth PRIVATE
|
||||
lmsutils
|
||||
lmscore
|
||||
lmsdatabase
|
||||
)
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
AuthServiceBase::AuthServiceBase(Db& db)
|
||||
: _db{ db }
|
||||
|
||||
@@ -22,25 +22,25 @@
|
||||
#include <string_view>
|
||||
#include "database/UserId.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
class Session;
|
||||
}
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class AuthServiceBase
|
||||
{
|
||||
protected:
|
||||
AuthServiceBase(Database::Db& db);
|
||||
AuthServiceBase(db::Db& db);
|
||||
|
||||
Database::UserId getOrCreateUser(std::string_view loginName);
|
||||
void onUserAuthenticated(Database::UserId userId);
|
||||
db::UserId getOrCreateUser(std::string_view loginName);
|
||||
void onUserAuthenticated(db::UserId userId);
|
||||
|
||||
Database::Session& getDbSession();
|
||||
db::Session& getDbSession();
|
||||
|
||||
private:
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,45 +27,45 @@
|
||||
#include "database/AuthToken.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
|
||||
std::unique_ptr<IAuthTokenService> createAuthTokenService(Database::Db& db, std::size_t maxThrottlerEntries)
|
||||
std::unique_ptr<IAuthTokenService> createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntries)
|
||||
{
|
||||
return std::make_unique<AuthTokenService>(db, maxThrottlerEntries);
|
||||
}
|
||||
|
||||
static const Wt::Auth::SHA1HashFunction sha1Function;
|
||||
|
||||
AuthTokenService::AuthTokenService(Database::Db& db, std::size_t maxThrottlerEntries)
|
||||
AuthTokenService::AuthTokenService(db::Db& db, std::size_t maxThrottlerEntries)
|
||||
: AuthServiceBase {db}
|
||||
, _loginThrottler {maxThrottlerEntries}
|
||||
{
|
||||
}
|
||||
|
||||
std::string
|
||||
AuthTokenService::createAuthToken(Database::UserId userId, const Wt::WDateTime& expiry)
|
||||
AuthTokenService::createAuthToken(db::UserId userId, const Wt::WDateTime& expiry)
|
||||
{
|
||||
const std::string secret {Wt::WRandom::generateId(32)};
|
||||
const std::string secretHash {sha1Function.compute(secret, {})};
|
||||
|
||||
Database::Session& session {getDbSession()};
|
||||
db::Session& session {getDbSession()};
|
||||
|
||||
auto transaction {session.createWriteTransaction()};
|
||||
|
||||
Database::User::pointer user {Database::User::find(session, userId)};
|
||||
db::User::pointer user {db::User::find(session, userId)};
|
||||
if (!user)
|
||||
throw Exception {"User deleted"};
|
||||
|
||||
Database::AuthToken::pointer authToken {session.create<Database::AuthToken>(secretHash, expiry, user)};
|
||||
db::AuthToken::pointer authToken {session.create<db::AuthToken>(secretHash, expiry, user)};
|
||||
|
||||
LMS_LOG(UI, DEBUG, "Created auth token for user '" << user->getLoginName() << "', expiry = " << expiry.toString());
|
||||
|
||||
if (user->getAuthTokensCount() >= 50)
|
||||
Database::AuthToken::removeExpiredTokens(session, Wt::WDateTime::currentDateTime());
|
||||
db::AuthToken::removeExpiredTokens(session, Wt::WDateTime::currentDateTime());
|
||||
|
||||
return secret;
|
||||
}
|
||||
@@ -75,10 +75,10 @@ namespace Auth
|
||||
{
|
||||
const std::string secretHash {sha1Function.compute(std::string {secret}, {})};
|
||||
|
||||
Database::Session& session {getDbSession()};
|
||||
db::Session& session {getDbSession()};
|
||||
auto transaction {session.createWriteTransaction()};
|
||||
|
||||
Database::AuthToken::pointer authToken {Database::AuthToken::find(session, secretHash)};
|
||||
db::AuthToken::pointer authToken {db::AuthToken::find(session, secretHash)};
|
||||
if (!authToken)
|
||||
return std::nullopt;
|
||||
|
||||
@@ -127,17 +127,17 @@ namespace Auth
|
||||
}
|
||||
|
||||
void
|
||||
AuthTokenService::clearAuthTokens(Database::UserId userId)
|
||||
AuthTokenService::clearAuthTokens(db::UserId userId)
|
||||
{
|
||||
Database::Session& session {getDbSession()};
|
||||
db::Session& session {getDbSession()};
|
||||
|
||||
auto transaction {session.createWriteTransaction()};
|
||||
|
||||
Database::User::pointer user {Database::User::find(session, userId)};
|
||||
db::User::pointer user {db::User::find(session, userId)};
|
||||
if (!user)
|
||||
throw Exception {"User deleted"};
|
||||
|
||||
user.modify()->clearAuthTokens();
|
||||
}
|
||||
|
||||
} // namespace Auth
|
||||
} // namespace lms::auth
|
||||
|
||||
@@ -25,17 +25,17 @@
|
||||
#include "AuthServiceBase.hpp"
|
||||
#include "LoginThrottler.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
}
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class AuthTokenService : public IAuthTokenService, public AuthServiceBase
|
||||
{
|
||||
public:
|
||||
AuthTokenService(Database::Db& db, std::size_t maxThrottlerEntries);
|
||||
AuthTokenService(db::Db& db, std::size_t maxThrottlerEntries);
|
||||
|
||||
AuthTokenService(const AuthTokenService&) = delete;
|
||||
AuthTokenService& operator=(const AuthTokenService&) = delete;
|
||||
@@ -44,8 +44,8 @@ namespace Auth
|
||||
|
||||
private:
|
||||
AuthTokenProcessResult processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) override;
|
||||
std::string createAuthToken(Database::UserId userId, const Wt::WDateTime& expiry) override;
|
||||
void clearAuthTokens(Database::UserId userId) override;
|
||||
std::string createAuthToken(db::UserId userId, const Wt::WDateTime& expiry) override;
|
||||
void clearAuthTokens(db::UserId userId) override;
|
||||
|
||||
std::optional<AuthTokenService::AuthTokenProcessResult::AuthTokenInfo> processAuthToken(std::string_view secret);
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
#include "services/auth/Types.hpp"
|
||||
#include "http-headers/HttpHeadersEnvService.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
std::unique_ptr<IEnvService>
|
||||
createEnvService(std::string_view backendName, Database::Db& db)
|
||||
createEnvService(std::string_view backendName, db::Db& db)
|
||||
{
|
||||
if (backendName == "http-headers")
|
||||
return std::make_unique<HttpHeadersEnvService>(db);
|
||||
|
||||
@@ -17,104 +17,97 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* This file contains some classes in order to get info from file using the libavconv */
|
||||
/* This file contains some classes in order to get info from file using the libavconv */
|
||||
|
||||
#include "LoginThrottler.hpp"
|
||||
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Random.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Random.hpp"
|
||||
|
||||
namespace Auth {
|
||||
|
||||
static
|
||||
boost::asio::ip::address_v6
|
||||
getAddressWithMask(const boost::asio::ip::address_v6& address, std::size_t prefix)
|
||||
namespace lms::auth
|
||||
{
|
||||
assert(prefix % 8 == 0);
|
||||
namespace
|
||||
{
|
||||
boost::asio::ip::address_v6 getAddressWithMask(const boost::asio::ip::address_v6& address, std::size_t prefix)
|
||||
{
|
||||
assert(prefix % 8 == 0);
|
||||
|
||||
std::array<uint8_t, 16> truncatedBytes;
|
||||
std::array<uint8_t, 16> truncatedBytes;
|
||||
|
||||
auto bytes {address.to_bytes()};
|
||||
std::copy(std::cbegin(bytes), std::next(std::cbegin(bytes), prefix / 8), truncatedBytes.begin());
|
||||
auto bytes{ address.to_bytes() };
|
||||
std::copy(std::cbegin(bytes), std::next(std::cbegin(bytes), prefix / 8), truncatedBytes.begin());
|
||||
|
||||
return boost::asio::ip::address_v6 {truncatedBytes};
|
||||
}
|
||||
return boost::asio::ip::address_v6{ truncatedBytes };
|
||||
}
|
||||
|
||||
static
|
||||
boost::asio::ip::address
|
||||
getAddressToThrottle(const boost::asio::ip::address& address)
|
||||
{
|
||||
return address.is_v6() ? getAddressWithMask(address.to_v6(), 64) : address;
|
||||
}
|
||||
boost::asio::ip::address getAddressToThrottle(const boost::asio::ip::address& address)
|
||||
{
|
||||
return address.is_v6() ? getAddressWithMask(address.to_v6(), 64) : address;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LoginThrottler::removeOutdatedEntries()
|
||||
{
|
||||
const Wt::WDateTime now {Wt::WDateTime::currentDateTime()};
|
||||
void LoginThrottler::removeOutdatedEntries()
|
||||
{
|
||||
const Wt::WDateTime now{ Wt::WDateTime::currentDateTime() };
|
||||
|
||||
for (auto it {std::begin(_attemptsInfo)}; it != std::end(_attemptsInfo); )
|
||||
{
|
||||
if (it->second.nextAttempt <= now)
|
||||
it = _attemptsInfo.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
for (auto it{ std::begin(_attemptsInfo) }; it != std::end(_attemptsInfo); )
|
||||
{
|
||||
if (it->second.nextAttempt <= now)
|
||||
it = _attemptsInfo.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LoginThrottler::onBadClientAttempt(const boost::asio::ip::address& address)
|
||||
{
|
||||
const boost::asio::ip::address clientAddress {getAddressToThrottle(address)};
|
||||
const Wt::WDateTime now {Wt::WDateTime::currentDateTime()};
|
||||
void LoginThrottler::onBadClientAttempt(const boost::asio::ip::address& address)
|
||||
{
|
||||
const boost::asio::ip::address clientAddress{ getAddressToThrottle(address) };
|
||||
const Wt::WDateTime now{ Wt::WDateTime::currentDateTime() };
|
||||
|
||||
if (_attemptsInfo.size() >= _maxEntries)
|
||||
removeOutdatedEntries();
|
||||
if (_attemptsInfo.size() >= _maxEntries)
|
||||
_attemptsInfo.erase(Random::pickRandom(_attemptsInfo));
|
||||
if (_attemptsInfo.size() >= _maxEntries)
|
||||
removeOutdatedEntries();
|
||||
if (_attemptsInfo.size() >= _maxEntries)
|
||||
_attemptsInfo.erase(core::random::pickRandom(_attemptsInfo));
|
||||
|
||||
AttemptInfo& attemptInfo {_attemptsInfo[address]};
|
||||
if (attemptInfo.nextAttempt.isValid())
|
||||
{
|
||||
assert(attemptInfo.nextAttempt <= now); // should not be called if throttled
|
||||
attemptInfo = {};
|
||||
}
|
||||
AttemptInfo& attemptInfo{ _attemptsInfo[address] };
|
||||
if (attemptInfo.nextAttempt.isValid())
|
||||
{
|
||||
assert(attemptInfo.nextAttempt <= now); // should not be called if throttled
|
||||
attemptInfo = {};
|
||||
}
|
||||
|
||||
attemptInfo.badConsecutiveAttemptCount += 1;
|
||||
attemptInfo.badConsecutiveAttemptCount += 1;
|
||||
|
||||
LMS_LOG(AUTH, DEBUG, "Registering bad attempt for '" << clientAddress.to_string() << "', consecutive bad attempts count = " << attemptInfo.badConsecutiveAttemptCount);
|
||||
if (attemptInfo.badConsecutiveAttemptCount >= _maxBadConsecutiveAttemptCount)
|
||||
{
|
||||
LMS_LOG(AUTH, DEBUG, "Throttling '" << clientAddress.to_string() << "'");
|
||||
attemptInfo.nextAttempt = now.addMSecs(std::chrono::duration_cast<std::chrono::milliseconds>(_throttlingDuration).count());
|
||||
}
|
||||
else
|
||||
{
|
||||
attemptInfo.nextAttempt = {};
|
||||
}
|
||||
}
|
||||
LMS_LOG(AUTH, DEBUG, "Registering bad attempt for '" << clientAddress.to_string() << "', consecutive bad attempts count = " << attemptInfo.badConsecutiveAttemptCount);
|
||||
if (attemptInfo.badConsecutiveAttemptCount >= _maxBadConsecutiveAttemptCount)
|
||||
{
|
||||
LMS_LOG(AUTH, DEBUG, "Throttling '" << clientAddress.to_string() << "'");
|
||||
attemptInfo.nextAttempt = now.addMSecs(std::chrono::duration_cast<std::chrono::milliseconds>(_throttlingDuration).count());
|
||||
}
|
||||
else
|
||||
{
|
||||
attemptInfo.nextAttempt = {};
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LoginThrottler::onGoodClientAttempt(const boost::asio::ip::address& address)
|
||||
{
|
||||
const boost::asio::ip::address clientAddress {getAddressToThrottle(address)};
|
||||
void LoginThrottler::onGoodClientAttempt(const boost::asio::ip::address& address)
|
||||
{
|
||||
const boost::asio::ip::address clientAddress{ getAddressToThrottle(address) };
|
||||
|
||||
_attemptsInfo.erase(clientAddress);
|
||||
}
|
||||
_attemptsInfo.erase(clientAddress);
|
||||
}
|
||||
|
||||
bool
|
||||
LoginThrottler::isClientThrottled(const boost::asio::ip::address& address) const
|
||||
{
|
||||
const boost::asio::ip::address clientAddress {getAddressToThrottle(address)};
|
||||
bool LoginThrottler::isClientThrottled(const boost::asio::ip::address& address) const
|
||||
{
|
||||
const boost::asio::ip::address clientAddress{ getAddressToThrottle(address) };
|
||||
|
||||
auto it {_attemptsInfo.find(clientAddress)};
|
||||
if (it == _attemptsInfo.end())
|
||||
return false;
|
||||
auto it{ _attemptsInfo.find(clientAddress) };
|
||||
if (it == _attemptsInfo.end())
|
||||
return false;
|
||||
|
||||
if (!it->second.nextAttempt.isValid())
|
||||
return false;
|
||||
|
||||
return it->second.nextAttempt > Wt::WDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
} // Auth
|
||||
if (!it->second.nextAttempt.isValid())
|
||||
return false;
|
||||
|
||||
return it->second.nextAttempt > Wt::WDateTime::currentDateTime();
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,10 @@
|
||||
|
||||
#include <Wt/WDateTime.h>
|
||||
|
||||
#include "utils/NetAddress.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "core/NetAddress.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class LoginThrottler
|
||||
{
|
||||
|
||||
@@ -30,16 +30,15 @@
|
||||
#include "services/auth/Types.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
|
||||
static const Wt::Auth::SHA1HashFunction sha1Function;
|
||||
|
||||
std::unique_ptr<IPasswordService>
|
||||
createPasswordService(std::string_view passwordAuthenticationBackend, Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService)
|
||||
createPasswordService(std::string_view passwordAuthenticationBackend, db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService)
|
||||
{
|
||||
if (passwordAuthenticationBackend == "internal")
|
||||
return std::make_unique<InternalPasswordService>(db, maxThrottlerEntries, authTokenService);
|
||||
@@ -51,7 +50,7 @@ namespace Auth
|
||||
throw Exception {"Authentication backend '" + std::string {passwordAuthenticationBackend} + "' is not supported!"};
|
||||
}
|
||||
|
||||
PasswordServiceBase::PasswordServiceBase(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService)
|
||||
PasswordServiceBase::PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService)
|
||||
: AuthServiceBase {db}
|
||||
, _loginThrottler {maxThrottlerEntries}
|
||||
, _authTokenService {authTokenService}
|
||||
@@ -82,7 +81,7 @@ namespace Auth
|
||||
{
|
||||
_loginThrottler.onGoodClientAttempt(clientAddress);
|
||||
|
||||
const Database::UserId userId {getOrCreateUser(loginName)};
|
||||
const db::UserId userId {getOrCreateUser(loginName)};
|
||||
onUserAuthenticated(userId);
|
||||
return {CheckResult::State::Granted, userId};
|
||||
}
|
||||
@@ -93,5 +92,5 @@ namespace Auth
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Auth
|
||||
} // namespace lms::auth
|
||||
|
||||
|
||||
@@ -25,19 +25,18 @@
|
||||
#include "AuthServiceBase.hpp"
|
||||
#include "LoginThrottler.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
class Session;
|
||||
}
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
|
||||
class PasswordServiceBase : public IPasswordService, public AuthServiceBase
|
||||
{
|
||||
public:
|
||||
PasswordServiceBase(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService);
|
||||
PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService);
|
||||
|
||||
PasswordServiceBase(const PasswordServiceBase&) = delete;
|
||||
PasswordServiceBase& operator=(const PasswordServiceBase&) = delete;
|
||||
@@ -58,5 +57,4 @@ namespace Auth
|
||||
LoginThrottler _loginThrottler;
|
||||
IAuthTokenService& _authTokenService;
|
||||
};
|
||||
|
||||
} // namespace Auth
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@
|
||||
|
||||
#include <Wt/WEnvironment.h>
|
||||
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Service.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
HttpHeadersEnvService::HttpHeadersEnvService(Database::Db& db)
|
||||
HttpHeadersEnvService::HttpHeadersEnvService(db::Db& db)
|
||||
: AuthServiceBase{ db }
|
||||
, _fieldName{ Service<IConfig>::get()->getString("http-headers-login-field", "X-Forwarded-User") }
|
||||
, _fieldName{ core::Service<core::IConfig>::get()->getString("http-headers-login-field", "X-Forwarded-User") }
|
||||
{
|
||||
LMS_LOG(AUTH, INFO, "Using http header field = '" << _fieldName << "'");
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace Auth
|
||||
|
||||
LMS_LOG(AUTH, DEBUG, "Extracted login name = '" << loginName << "' from HTTP header");
|
||||
|
||||
const Database::UserId userId{ getOrCreateUser(loginName) };
|
||||
const db::UserId userId{ getOrCreateUser(loginName) };
|
||||
onUserAuthenticated(userId);
|
||||
return { CheckResult::State::Granted, userId };
|
||||
}
|
||||
@@ -55,8 +55,8 @@ namespace Auth
|
||||
|
||||
LMS_LOG(AUTH, DEBUG, "Extracted login name = '" << loginName << "' from HTTP header");
|
||||
|
||||
const Database::UserId userId{ getOrCreateUser(loginName) };
|
||||
const db::UserId userId{ getOrCreateUser(loginName) };
|
||||
onUserAuthenticated(userId);
|
||||
return { CheckResult::State::Granted, userId };
|
||||
}
|
||||
} // namespace Auth
|
||||
} // namespace lms::auth
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
#include "services/auth/IEnvService.hpp"
|
||||
#include "AuthServiceBase.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class HttpHeadersEnvService : public IEnvService, public AuthServiceBase
|
||||
{
|
||||
public:
|
||||
HttpHeadersEnvService(Database::Db& db);
|
||||
HttpHeadersEnvService(db::Db& db);
|
||||
|
||||
private:
|
||||
CheckResult processEnv(const Wt::WEnvironment& env) override;
|
||||
@@ -36,5 +36,5 @@ namespace Auth
|
||||
std::string _fieldName;
|
||||
};
|
||||
|
||||
} // namespace Auth
|
||||
} // namespace lms::auth
|
||||
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
#include "services/auth/Types.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
InternalPasswordService::InternalPasswordService(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService)
|
||||
InternalPasswordService::InternalPasswordService(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService)
|
||||
: PasswordServiceBase{ db, maxThrottlerEntries, authTokenService }
|
||||
{
|
||||
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::OneCharClass, 4);
|
||||
@@ -45,12 +45,12 @@ namespace Auth
|
||||
{
|
||||
LMS_LOG(AUTH, DEBUG, "Checking internal password for user '" << loginName << "'");
|
||||
|
||||
Database::User::PasswordHash passwordHash;
|
||||
db::User::PasswordHash passwordHash;
|
||||
{
|
||||
Database::Session& session{ getDbSession() };
|
||||
db::Session& session{ getDbSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const Database::User::pointer user{ Database::User::find(session, loginName) };
|
||||
const db::User::pointer user{ db::User::find(session, loginName) };
|
||||
if (!user)
|
||||
{
|
||||
LMS_LOG(AUTH, DEBUG, "hashing random stuff");
|
||||
@@ -81,24 +81,24 @@ namespace Auth
|
||||
{
|
||||
switch (context.userType)
|
||||
{
|
||||
case Database::UserType::ADMIN:
|
||||
case Database::UserType::REGULAR:
|
||||
case db::UserType::ADMIN:
|
||||
case db::UserType::REGULAR:
|
||||
return _validator.evaluateStrength(std::string{ password }, context.loginName, "").isValid() ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::TooWeak;
|
||||
case Database::UserType::DEMO:
|
||||
case db::UserType::DEMO:
|
||||
return password == context.loginName ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::MustMatchLoginName;
|
||||
}
|
||||
|
||||
throw NotImplementedException{};
|
||||
}
|
||||
|
||||
void InternalPasswordService::setPassword(Database::UserId userId, std::string_view newPassword)
|
||||
void InternalPasswordService::setPassword(db::UserId userId, std::string_view newPassword)
|
||||
{
|
||||
const Database::User::PasswordHash passwordHash{ hashPassword(newPassword) };
|
||||
const db::User::PasswordHash passwordHash{ hashPassword(newPassword) };
|
||||
|
||||
Database::Session& session{ getDbSession() };
|
||||
db::Session& session{ getDbSession() };
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
Database::User::pointer user{ Database::User::find(session, userId) };
|
||||
db::User::pointer user{ db::User::find(session, userId) };
|
||||
if (!user)
|
||||
throw Exception{ "User not found!" };
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Auth
|
||||
getAuthTokenService().clearAuthTokens(userId);
|
||||
}
|
||||
|
||||
Database::User::PasswordHash InternalPasswordService::hashPassword(std::string_view password) const
|
||||
db::User::PasswordHash InternalPasswordService::hashPassword(std::string_view password) const
|
||||
{
|
||||
const std::string salt{ Wt::WRandom::generateId(32) };
|
||||
|
||||
@@ -129,5 +129,5 @@ namespace Auth
|
||||
hashPassword(Wt::WRandom::generateId(32));
|
||||
}
|
||||
|
||||
} // namespace Auth
|
||||
} // namespace lms::auth
|
||||
|
||||
|
||||
@@ -26,23 +26,23 @@
|
||||
#include "PasswordServiceBase.hpp"
|
||||
#include "LoginThrottler.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class IAuthTokenService;
|
||||
|
||||
class InternalPasswordService : public PasswordServiceBase
|
||||
{
|
||||
public:
|
||||
InternalPasswordService(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService);
|
||||
InternalPasswordService(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService);
|
||||
|
||||
private:
|
||||
bool checkUserPassword(std::string_view loginName, std::string_view password) override;
|
||||
|
||||
bool canSetPasswords() const override;
|
||||
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
|
||||
void setPassword(Database::UserId userId, std::string_view newPassword) override;
|
||||
void setPassword(db::UserId userId, std::string_view newPassword) override;
|
||||
|
||||
Database::User::PasswordHash hashPassword(std::string_view password) const;
|
||||
db::User::PasswordHash hashPassword(std::string_view password) const;
|
||||
void hashRandomPassword() const;
|
||||
|
||||
const Wt::Auth::BCryptHashFunction _hashFunc{ 7 }; // TODO parametrize this
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
|
||||
#include "services/auth/Types.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -191,10 +191,10 @@ namespace Auth
|
||||
throw NotImplementedException{};
|
||||
}
|
||||
|
||||
void PAMPasswordService::setPassword(Database::UserId, std::string_view)
|
||||
void PAMPasswordService::setPassword(db::UserId, std::string_view)
|
||||
{
|
||||
throw NotImplementedException{};
|
||||
}
|
||||
|
||||
} // namespace Auth
|
||||
} // namespace lms::auth
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "PasswordServiceBase.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class PAMPasswordService: public PasswordServiceBase
|
||||
{
|
||||
@@ -34,6 +34,6 @@ namespace Auth
|
||||
bool checkUserPassword(std::string_view loginName,std::string_view password) override;
|
||||
bool canSetPasswords() const override;
|
||||
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
|
||||
void setPassword(Database::UserId userId, std::string_view newPassword) override;
|
||||
void setPassword(db::UserId userId, std::string_view newPassword) override;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
|
||||
#include "database/UserId.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
class User;
|
||||
}
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class IAuthTokenService
|
||||
{
|
||||
@@ -54,7 +54,7 @@ namespace Auth
|
||||
|
||||
struct AuthTokenInfo
|
||||
{
|
||||
Database::UserId userId;
|
||||
db::UserId userId;
|
||||
Wt::WDateTime expiry;
|
||||
};
|
||||
|
||||
@@ -66,9 +66,9 @@ namespace Auth
|
||||
virtual AuthTokenProcessResult processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) = 0;
|
||||
|
||||
// Returns a one time token
|
||||
virtual std::string createAuthToken(Database::UserId userid, const Wt::WDateTime& expiry) = 0;
|
||||
virtual void clearAuthTokens(Database::UserId userid) = 0;
|
||||
virtual std::string createAuthToken(db::UserId userid, const Wt::WDateTime& expiry) = 0;
|
||||
virtual void clearAuthTokens(db::UserId userid) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IAuthTokenService> createAuthTokenService(Database::Db& db, std::size_t maxThrottlerEntryCount);
|
||||
std::unique_ptr<IAuthTokenService> createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntryCount);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "database/UserId.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
class Session;
|
||||
@@ -40,7 +40,7 @@ namespace Wt::Http
|
||||
class Request;
|
||||
}
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class IEnvService
|
||||
{
|
||||
@@ -58,12 +58,12 @@ namespace Auth
|
||||
};
|
||||
|
||||
State state {State::Denied};
|
||||
std::optional<Database::UserId> userId {};
|
||||
std::optional<db::UserId> userId {};
|
||||
};
|
||||
|
||||
virtual CheckResult processEnv(const Wt::WEnvironment& env) = 0;
|
||||
virtual CheckResult processRequest(const Wt::Http::Request& request) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IEnvService> createEnvService(std::string_view backendName, Database::Db& db);
|
||||
} // namespace Auth
|
||||
std::unique_ptr<IEnvService> createEnvService(std::string_view backendName, db::Db& db);
|
||||
} // namespace lms::auth
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
#include "services/auth/Types.hpp"
|
||||
#include "database/UserId.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
class User;
|
||||
}
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
|
||||
class IAuthTokenService;
|
||||
@@ -54,7 +54,7 @@ namespace Auth
|
||||
Throttled,
|
||||
};
|
||||
State state {State::Denied};
|
||||
std::optional<Database::UserId> userId {};
|
||||
std::optional<db::UserId> userId {};
|
||||
std::optional<Wt::WDateTime> expiry {};
|
||||
};
|
||||
virtual CheckResult checkUserPassword(const boost::asio::ip::address& clientAddress,
|
||||
@@ -70,9 +70,9 @@ namespace Auth
|
||||
MustMatchLoginName,
|
||||
};
|
||||
virtual PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const = 0;
|
||||
virtual void setPassword(Database::UserId userId, std::string_view newPassword) = 0;
|
||||
virtual void setPassword(db::UserId userId, std::string_view newPassword) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IPasswordService> createPasswordService(std::string_view authPasswordBackend, Database::Db& db, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService);
|
||||
std::unique_ptr<IPasswordService> createPasswordService(std::string_view authPasswordBackend, db::Db& db, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,31 +21,31 @@
|
||||
|
||||
#include <string>
|
||||
#include "database/Types.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
|
||||
namespace Auth
|
||||
namespace lms::auth
|
||||
{
|
||||
class Exception : public ::LmsException
|
||||
class Exception : public core::LmsException
|
||||
{
|
||||
using LmsException::LmsException;
|
||||
using core::LmsException::LmsException;
|
||||
};
|
||||
|
||||
class NotImplementedException : public Exception
|
||||
{
|
||||
public:
|
||||
NotImplementedException() : Auth::Exception {"Not implemented"} {}
|
||||
NotImplementedException() : Exception {"Not implemented"} {}
|
||||
};
|
||||
|
||||
class UserNotFoundException : public Exception
|
||||
{
|
||||
public:
|
||||
UserNotFoundException() : Auth::Exception {"User not found"} {}
|
||||
UserNotFoundException() : Exception {"User not found"} {}
|
||||
};
|
||||
|
||||
struct PasswordValidationContext
|
||||
{
|
||||
std::string loginName;
|
||||
Database::UserType userType;
|
||||
db::UserType userType;
|
||||
};
|
||||
|
||||
class PasswordException : public Exception
|
||||
|
||||
@@ -20,7 +20,7 @@ target_link_libraries(lmsservice-cover PRIVATE
|
||||
target_link_libraries(lmsservice-cover PUBLIC
|
||||
lmsdatabase
|
||||
lmsimage
|
||||
lmsutils
|
||||
lmscore
|
||||
std::filesystem
|
||||
)
|
||||
|
||||
|
||||
@@ -31,14 +31,14 @@
|
||||
|
||||
#include "image/Exception.hpp"
|
||||
#include "image/IRawImage.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "utils/Random.hpp"
|
||||
#include "utils/String.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Path.hpp"
|
||||
#include "core/Random.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "core/Utils.hpp"
|
||||
|
||||
namespace Cover
|
||||
namespace lms::cover
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -47,16 +47,16 @@ namespace Cover
|
||||
bool hasCover{};
|
||||
bool isMultiDisc{};
|
||||
std::filesystem::path trackPath;
|
||||
std::optional<Database::ReleaseId> releaseId;
|
||||
std::optional<db::ReleaseId> releaseId;
|
||||
};
|
||||
|
||||
std::optional<TrackInfo> getTrackInfo(Database::Session& dbSession, Database::TrackId trackId)
|
||||
std::optional<TrackInfo> getTrackInfo(db::Session& dbSession, db::TrackId trackId)
|
||||
{
|
||||
std::optional<TrackInfo> res;
|
||||
|
||||
auto transaction{ dbSession.createReadTransaction() };
|
||||
|
||||
const Database::Track::pointer track{ Database::Track::find(dbSession, trackId) };
|
||||
const db::Track::pointer track{ db::Track::find(dbSession, trackId) };
|
||||
if (!track)
|
||||
return res;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Cover
|
||||
res->hasCover = track->hasCover();
|
||||
res->trackPath = track->getPath();
|
||||
|
||||
if (const Database::Release::pointer & release{ track->getRelease() })
|
||||
if (const db::Release::pointer & release{ track->getRelease() })
|
||||
{
|
||||
res->releaseId = release->getId();
|
||||
if (release->getTotalDisc() > 1)
|
||||
@@ -79,7 +79,7 @@ namespace Cover
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
Service<IConfig>::get()->visitStrings("cover-preferred-file-names",
|
||||
core::Service<core::IConfig>::get()->visitStrings("cover-preferred-file-names",
|
||||
[&res](std::string_view fileName)
|
||||
{
|
||||
res.emplace_back(fileName);
|
||||
@@ -92,7 +92,7 @@ namespace Cover
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
Service<IConfig>::get()->visitStrings("artist-image-file-names",
|
||||
core::Service<core::IConfig>::get()->visitStrings("artist-image-file-names",
|
||||
[&res](std::string_view fileName)
|
||||
{
|
||||
res.emplace_back(fileName);
|
||||
@@ -107,29 +107,29 @@ namespace Cover
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ICoverService> createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath)
|
||||
std::unique_ptr<ICoverService> createCoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath)
|
||||
{
|
||||
return std::make_unique<CoverService>(db, execPath, defaultCoverPath);
|
||||
}
|
||||
|
||||
using namespace Image;
|
||||
using namespace image;
|
||||
|
||||
CoverService::CoverService(Database::Db& db,
|
||||
CoverService::CoverService(db::Db& db,
|
||||
const std::filesystem::path& execPath,
|
||||
const std::filesystem::path& defaultCoverPath)
|
||||
: _db{ db }
|
||||
, _defaultCoverPath{ defaultCoverPath }
|
||||
, _maxCacheSize{ Service<IConfig>::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 }
|
||||
, _maxFileSize{ Service<IConfig>::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 }
|
||||
, _maxCacheSize{ core::Service<core::IConfig>::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 }
|
||||
, _maxFileSize{ core::Service<core::IConfig>::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 }
|
||||
, _preferredFileNames{ constructPreferredFileNames() }
|
||||
, _artistFileNames{ constructArtistFileNames() }
|
||||
{
|
||||
setJpegQuality(Service<IConfig>::get()->getULong("cover-jpeg-quality", 75));
|
||||
setJpegQuality(core::Service<core::IConfig>::get()->getULong("cover-jpeg-quality", 75));
|
||||
|
||||
LMS_LOG(COVER, INFO, "Default cover path = '" << _defaultCoverPath.string() << "'");
|
||||
LMS_LOG(COVER, INFO, "Max cache size = " << _maxCacheSize);
|
||||
LMS_LOG(COVER, INFO, "Max file size = " << _maxFileSize);
|
||||
LMS_LOG(COVER, INFO, "Preferred file names: " << StringUtils::joinStrings(_preferredFileNames, ","));
|
||||
LMS_LOG(COVER, INFO, "Preferred file names: " << core::stringUtils::joinStrings(_preferredFileNames, ","));
|
||||
|
||||
#if LMS_SUPPORT_IMAGE_GM
|
||||
GraphicsMagick::init(execPath);
|
||||
@@ -141,17 +141,17 @@ namespace Cover
|
||||
{
|
||||
getDefault(512);
|
||||
}
|
||||
catch (const Image::ImageException& e)
|
||||
catch (const image::Exception& e)
|
||||
{
|
||||
throw LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what());
|
||||
throw core::LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> CoverService::getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) const
|
||||
std::unique_ptr<IEncodedImage> CoverService::getFromAvMediaFile(const av::IAudioFile& input, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
input.visitAttachedPictures([&](const Av::Picture& picture)
|
||||
input.visitAttachedPictures([&](const av::Picture& picture)
|
||||
{
|
||||
if (image)
|
||||
return;
|
||||
@@ -162,7 +162,7 @@ namespace Cover
|
||||
rawImage->resize(width);
|
||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||
}
|
||||
catch (const Image::ImageException& e)
|
||||
catch (const image::Exception& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR, "Cannot read embedded cover: " << e.what());
|
||||
}
|
||||
@@ -181,7 +181,7 @@ namespace Cover
|
||||
rawImage->resize(width);
|
||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||
}
|
||||
catch (const ImageException& e)
|
||||
catch (const image::Exception& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR, "Cannot read cover in file '" << p.string() << "': " << e.what());
|
||||
}
|
||||
@@ -320,9 +320,9 @@ namespace Cover
|
||||
|
||||
try
|
||||
{
|
||||
image = getFromAvMediaFile(*Av::parseAudioFile(p), width);
|
||||
image = getFromAvMediaFile(*av::parseAudioFile(p), width);
|
||||
}
|
||||
catch (Av::Exception& e)
|
||||
catch (av::Exception& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR, "Cannot get covers from track " << p.string() << ": " << e.what());
|
||||
}
|
||||
@@ -330,14 +330,14 @@ namespace Cover
|
||||
return image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(Database::TrackId trackId, ImageSize width)
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(db::TrackId trackId, ImageSize width)
|
||||
{
|
||||
return getFromTrack(_db.getTLSSession(), trackId, width, true /* allow release fallback*/);
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback)
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(db::Session& dbSession, db::TrackId trackId, ImageSize width, bool allowReleaseFallback)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
const CacheEntryDesc cacheEntryDesc{ trackId, width };
|
||||
|
||||
@@ -369,9 +369,9 @@ namespace Cover
|
||||
return cover;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromRelease(Database::ReleaseId releaseId, ImageSize width)
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromRelease(db::ReleaseId releaseId, ImageSize width)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
const CacheEntryDesc cacheEntryDesc{ releaseId, width };
|
||||
|
||||
std::shared_ptr<IEncodedImage> cover{ loadFromCache(cacheEntryDesc) };
|
||||
@@ -418,9 +418,9 @@ namespace Cover
|
||||
return cover;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromArtist(Database::ArtistId artistId, ImageSize width)
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromArtist(db::ArtistId artistId, ImageSize width)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
const CacheEntryDesc cacheEntryDesc{ artistId, width };
|
||||
|
||||
std::shared_ptr<IEncodedImage> artistImage{ loadFromCache(cacheEntryDesc) };
|
||||
@@ -478,7 +478,7 @@ namespace Cover
|
||||
// /artist.jpg
|
||||
if (!releasePaths.empty())
|
||||
{
|
||||
const std::filesystem::path artistPath{ releasePaths.size() == 1 ? releasePaths.begin()->parent_path() : PathUtils::getLongestCommonPath(std::cbegin(releasePaths), std::cend(releasePaths)) };
|
||||
const std::filesystem::path artistPath{ releasePaths.size() == 1 ? releasePaths.begin()->parent_path() : core::pathUtils::getLongestCommonPath(std::cbegin(releasePaths), std::cend(releasePaths)) };
|
||||
artistImage = getFromDirectory(artistPath, width, artistFileNamesWithGenericNames, false);
|
||||
}
|
||||
|
||||
@@ -531,7 +531,7 @@ namespace Cover
|
||||
|
||||
void CoverService::setJpegQuality(unsigned quality)
|
||||
{
|
||||
_jpegQuality = Utils::clamp<unsigned>(quality, 1, 100);
|
||||
_jpegQuality = core::utils::clamp<unsigned>(quality, 1, 100);
|
||||
|
||||
LMS_LOG(COVER, INFO, "JPEG export quality = " << _jpegQuality);
|
||||
}
|
||||
@@ -542,7 +542,7 @@ namespace Cover
|
||||
|
||||
while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty())
|
||||
{
|
||||
auto itRandom{ Random::pickRandom(_cache) };
|
||||
auto itRandom{ core::random::pickRandom(_cache) };
|
||||
_cacheSize -= itRandom->second->getDataSize();
|
||||
_cache.erase(itRandom);
|
||||
}
|
||||
@@ -566,5 +566,5 @@ namespace Cover
|
||||
return it->second;
|
||||
}
|
||||
|
||||
} // namespace Cover
|
||||
} // namespace lms::cover
|
||||
|
||||
|
||||
@@ -33,21 +33,21 @@
|
||||
#include "image/IEncodedImage.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
}
|
||||
|
||||
namespace Av
|
||||
namespace lms::av
|
||||
{
|
||||
class IAudioFile;
|
||||
}
|
||||
|
||||
namespace Cover
|
||||
namespace lms::cover
|
||||
{
|
||||
struct CacheEntryDesc
|
||||
{
|
||||
std::variant<Database::ArtistId, Database::ReleaseId, Database::TrackId> id;
|
||||
std::variant<db::ArtistId, db::ReleaseId, db::TrackId> id;
|
||||
std::size_t size;
|
||||
|
||||
bool operator==(const CacheEntryDesc& other) const
|
||||
@@ -61,64 +61,64 @@ namespace Cover
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
class hash<Cover::CacheEntryDesc>
|
||||
class hash<lms::cover::CacheEntryDesc>
|
||||
{
|
||||
public:
|
||||
size_t operator()(const Cover::CacheEntryDesc& e) const
|
||||
size_t operator()(const lms::cover::CacheEntryDesc& e) const
|
||||
{
|
||||
size_t h{};
|
||||
std::visit([&](auto id)
|
||||
{
|
||||
using IdType = std::decay_t<decltype(id)>;
|
||||
h ^= std::hash<IdType>()(id);
|
||||
h ^= std::hash<IdType>{}(id);
|
||||
}, e.id);
|
||||
h ^= std::hash<std::size_t>()(e.size) << 1;
|
||||
h ^= std::hash<std::size_t>{}(e.size) << 1;
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
} // ns std
|
||||
|
||||
namespace Cover
|
||||
namespace lms::cover
|
||||
{
|
||||
class CoverService : public ICoverService
|
||||
{
|
||||
public:
|
||||
CoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath);
|
||||
CoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath);
|
||||
|
||||
CoverService(const CoverService&) = delete;
|
||||
CoverService& operator=(const CoverService&) = delete;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Image::IEncodedImage> getFromTrack(Database::TrackId trackId, Image::ImageSize width) override;
|
||||
std::shared_ptr<Image::IEncodedImage> getFromRelease(Database::ReleaseId releaseId, Image::ImageSize width) override;
|
||||
std::shared_ptr<Image::IEncodedImage> getFromArtist(Database::ArtistId artistId, Image::ImageSize width) override;
|
||||
std::shared_ptr<Image::IEncodedImage> getDefault(Image::ImageSize width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getFromTrack(db::TrackId trackId, image::ImageSize width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getFromRelease(db::ReleaseId releaseId, image::ImageSize width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getFromArtist(db::ArtistId artistId, image::ImageSize width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getDefault(image::ImageSize width) override;
|
||||
void flushCache() override;
|
||||
void setJpegQuality(unsigned quality) override;
|
||||
|
||||
std::shared_ptr<Image::IEncodedImage> getFromTrack(Database::Session& dbSession, Database::TrackId trackId, Image::ImageSize width, bool allowReleaseFallback);
|
||||
std::unique_ptr<Image::IEncodedImage> getFromAvMediaFile(const Av::IAudioFile& input, Image::ImageSize width) const;
|
||||
std::unique_ptr<Image::IEncodedImage> getFromCoverFile(const std::filesystem::path& p, Image::ImageSize width) const;
|
||||
std::shared_ptr<image::IEncodedImage> getFromTrack(db::Session& dbSession, db::TrackId trackId, image::ImageSize width, bool allowReleaseFallback);
|
||||
std::unique_ptr<image::IEncodedImage> getFromAvMediaFile(const av::IAudioFile& input, image::ImageSize width) const;
|
||||
std::unique_ptr<image::IEncodedImage> getFromCoverFile(const std::filesystem::path& p, image::ImageSize width) const;
|
||||
|
||||
std::unique_ptr<Image::IEncodedImage> getFromTrack(const std::filesystem::path& path, Image::ImageSize width) const;
|
||||
std::unique_ptr<image::IEncodedImage> getFromTrack(const std::filesystem::path& path, image::ImageSize width) const;
|
||||
std::multimap<std::string, std::filesystem::path> getCoverPaths(const std::filesystem::path& directoryPath) const;
|
||||
std::unique_ptr<Image::IEncodedImage> getFromDirectory(const std::filesystem::path& directory, Image::ImageSize width, const std::vector<std::string>& preferredFileNames, bool allowPickRandom) const;
|
||||
std::unique_ptr<Image::IEncodedImage> getFromSameNamedFile(const std::filesystem::path& filePath, Image::ImageSize width) const;
|
||||
std::unique_ptr<image::IEncodedImage> getFromDirectory(const std::filesystem::path& directory, image::ImageSize width, const std::vector<std::string>& preferredFileNames, bool allowPickRandom) const;
|
||||
std::unique_ptr<image::IEncodedImage> getFromSameNamedFile(const std::filesystem::path& filePath, image::ImageSize width) const;
|
||||
|
||||
bool checkCoverFile(const std::filesystem::path& directoryPath) const;
|
||||
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
|
||||
std::shared_mutex _cacheMutex;
|
||||
std::unordered_map<CacheEntryDesc, std::shared_ptr<Image::IEncodedImage>> _cache;
|
||||
std::unordered_map<Image::ImageSize, std::shared_ptr<Image::IEncodedImage>> _defaultCoverCache;
|
||||
std::unordered_map<CacheEntryDesc, std::shared_ptr<image::IEncodedImage>> _cache;
|
||||
std::unordered_map<image::ImageSize, std::shared_ptr<image::IEncodedImage>> _defaultCoverCache;
|
||||
std::atomic<std::size_t> _cacheMisses{};
|
||||
std::atomic<std::size_t> _cacheHits{};
|
||||
std::size_t _cacheSize{};
|
||||
|
||||
void saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr<Image::IEncodedImage> image);
|
||||
std::shared_ptr<Image::IEncodedImage> loadFromCache(const CacheEntryDesc& entryDesc);
|
||||
void saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr<image::IEncodedImage> image);
|
||||
std::shared_ptr<image::IEncodedImage> loadFromCache(const CacheEntryDesc& entryDesc);
|
||||
|
||||
const std::filesystem::path _defaultCoverPath;
|
||||
const std::size_t _maxCacheSize;
|
||||
@@ -129,5 +129,5 @@ namespace Cover
|
||||
unsigned _jpegQuality;
|
||||
};
|
||||
|
||||
} // namespace Cover
|
||||
} // namespace lms::cover
|
||||
|
||||
|
||||
@@ -27,30 +27,30 @@
|
||||
#include "database/TrackId.hpp"
|
||||
#include "image/IEncodedImage.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Cover
|
||||
namespace lms::cover
|
||||
{
|
||||
class ICoverService
|
||||
{
|
||||
public:
|
||||
virtual ~ICoverService() = default;
|
||||
|
||||
virtual std::shared_ptr<Image::IEncodedImage> getFromTrack(Database::TrackId trackId, Image::ImageSize width) = 0;
|
||||
virtual std::shared_ptr<Image::IEncodedImage> getFromRelease(Database::ReleaseId releaseId, Image::ImageSize width) = 0;
|
||||
virtual std::shared_ptr<Image::IEncodedImage> getFromArtist(Database::ArtistId artistId, Image::ImageSize width) = 0;
|
||||
virtual std::shared_ptr<image::IEncodedImage> getFromTrack(db::TrackId trackId, image::ImageSize width) = 0;
|
||||
virtual std::shared_ptr<image::IEncodedImage> getFromRelease(db::ReleaseId releaseId, image::ImageSize width) = 0;
|
||||
virtual std::shared_ptr<image::IEncodedImage> getFromArtist(db::ArtistId artistId, image::ImageSize width) = 0;
|
||||
|
||||
virtual std::shared_ptr<Image::IEncodedImage> getDefault(Image::ImageSize width) = 0;
|
||||
virtual std::shared_ptr<image::IEncodedImage> getDefault(image::ImageSize width) = 0;
|
||||
|
||||
virtual void flushCache() = 0;
|
||||
|
||||
virtual void setJpegQuality(unsigned quality) = 0; // from 1 to 100
|
||||
};
|
||||
|
||||
std::unique_ptr<ICoverService> createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath);
|
||||
std::unique_ptr<ICoverService> createCoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath);
|
||||
|
||||
} // namespace CoverArt
|
||||
} // namespace lms::coverArt
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ target_include_directories(lmsfeedback PRIVATE
|
||||
)
|
||||
|
||||
target_link_libraries(lmsfeedback PRIVATE
|
||||
lmsutils
|
||||
lmscore
|
||||
)
|
||||
|
||||
target_link_libraries(lmsfeedback PUBLIC
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
#include "database/StarredTrack.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "internal/InternalBackend.hpp"
|
||||
#include "listenbrainz/ListenBrainzBackend.hpp"
|
||||
|
||||
namespace Feedback
|
||||
namespace lms::feedback
|
||||
{
|
||||
std::unique_ptr<IFeedbackService> createFeedbackService(boost::asio::io_context& ioContext, Db& db)
|
||||
{
|
||||
@@ -45,8 +45,8 @@ namespace Feedback
|
||||
: _db{ db }
|
||||
{
|
||||
LMS_LOG(SCROBBLING, INFO, "Starting service...");
|
||||
_backends.emplace(Database::FeedbackBackend::Internal, std::make_unique<InternalBackend>(_db));
|
||||
_backends.emplace(Database::FeedbackBackend::ListenBrainz, std::make_unique<ListenBrainz::ListenBrainzBackend>(ioContext, _db));
|
||||
_backends.emplace(db::FeedbackBackend::Internal, std::make_unique<InternalBackend>(_db));
|
||||
_backends.emplace(db::FeedbackBackend::ListenBrainz, std::make_unique<listenBrainz::ListenBrainzBackend>(ioContext, _db));
|
||||
LMS_LOG(SCROBBLING, INFO, "Service started!");
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ namespace Feedback
|
||||
LMS_LOG(SCROBBLING, INFO, "Service stopped!");
|
||||
}
|
||||
|
||||
std::optional<Database::FeedbackBackend> FeedbackService::getUserFeedbackBackend(UserId userId)
|
||||
std::optional<db::FeedbackBackend> FeedbackService::getUserFeedbackBackend(UserId userId)
|
||||
{
|
||||
std::optional<Database::FeedbackBackend> feedbackBackend;
|
||||
std::optional<db::FeedbackBackend> feedbackBackend;
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
@@ -25,54 +25,54 @@
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
#include "IFeedbackBackend.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Feedback
|
||||
namespace lms::feedback
|
||||
{
|
||||
class FeedbackService : public IFeedbackService
|
||||
{
|
||||
public:
|
||||
FeedbackService(boost::asio::io_context& ioContext, Database::Db& db);
|
||||
FeedbackService(boost::asio::io_context& ioContext, db::Db& db);
|
||||
~FeedbackService();
|
||||
|
||||
private:
|
||||
FeedbackService(const FeedbackService&) = delete;
|
||||
FeedbackService& operator=(const FeedbackService&) = delete;
|
||||
|
||||
void star(Database::UserId userId, Database::ArtistId artistId) override;
|
||||
void unstar(Database::UserId userId, Database::ArtistId artistId) override;
|
||||
bool isStarred(Database::UserId userId, Database::ArtistId artistId) override;
|
||||
Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) override;
|
||||
void star(db::UserId userId, db::ArtistId artistId) override;
|
||||
void unstar(db::UserId userId, db::ArtistId artistId) override;
|
||||
bool isStarred(db::UserId userId, db::ArtistId artistId) override;
|
||||
Wt::WDateTime getStarredDateTime(db::UserId userId, db::ArtistId artistId) override;
|
||||
ArtistContainer findStarredArtists(const ArtistFindParameters& params) override;
|
||||
|
||||
void star(Database::UserId userId, Database::ReleaseId releaseId) override;
|
||||
void unstar(Database::UserId userId, Database::ReleaseId releaseId) override;
|
||||
bool isStarred(Database::UserId userId, Database::ReleaseId releasedId) override;
|
||||
Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ReleaseId releasedId) override;
|
||||
void star(db::UserId userId, db::ReleaseId releaseId) override;
|
||||
void unstar(db::UserId userId, db::ReleaseId releaseId) override;
|
||||
bool isStarred(db::UserId userId, db::ReleaseId releasedId) override;
|
||||
Wt::WDateTime getStarredDateTime(db::UserId userId, db::ReleaseId releasedId) override;
|
||||
ReleaseContainer findStarredReleases(const FindParameters& params) override;
|
||||
|
||||
void star(Database::UserId userId, Database::TrackId trackId) override;
|
||||
void unstar(Database::UserId userId, Database::TrackId trackId) override;
|
||||
bool isStarred(Database::UserId userId, Database::TrackId trackId) override;
|
||||
Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::TrackId trackId) override;
|
||||
void star(db::UserId userId, db::TrackId trackId) override;
|
||||
void unstar(db::UserId userId, db::TrackId trackId) override;
|
||||
bool isStarred(db::UserId userId, db::TrackId trackId) override;
|
||||
Wt::WDateTime getStarredDateTime(db::UserId userId, db::TrackId trackId) override;
|
||||
TrackContainer findStarredTracks(const FindParameters& params) override;
|
||||
|
||||
std::optional<Database::FeedbackBackend> getUserFeedbackBackend(Database::UserId userId);
|
||||
std::optional<db::FeedbackBackend> getUserFeedbackBackend(db::UserId userId);
|
||||
|
||||
template <typename ObjType, typename ObjIdType, typename StarredObjType>
|
||||
void star(Database::UserId userId, ObjIdType id);
|
||||
void star(db::UserId userId, ObjIdType id);
|
||||
template <typename ObjType, typename ObjIdType, typename StarredObjType>
|
||||
void unstar(Database::UserId userId, ObjIdType id);
|
||||
void unstar(db::UserId userId, ObjIdType id);
|
||||
template <typename ObjType, typename ObjIdType, typename StarredObjType>
|
||||
bool isStarred(Database::UserId userId, ObjIdType id);
|
||||
bool isStarred(db::UserId userId, ObjIdType id);
|
||||
template <typename ObjType, typename ObjIdType, typename StarredObjType>
|
||||
Wt::WDateTime getStarredDateTime(Database::UserId userId, ObjIdType id);
|
||||
Wt::WDateTime getStarredDateTime(db::UserId userId, ObjIdType id);
|
||||
|
||||
Database::Db& _db;
|
||||
std::unordered_map<Database::FeedbackBackend, std::unique_ptr<IFeedbackBackend>> _backends;
|
||||
db::Db& _db;
|
||||
std::unordered_map<db::FeedbackBackend, std::unique_ptr<IFeedbackBackend>> _backends;
|
||||
};
|
||||
|
||||
} // ns Feedback
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
namespace Feedback
|
||||
namespace lms::feedback
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
template <typename ObjType, typename ObjIdType, typename StarredObjType>
|
||||
void FeedbackService::star(UserId userId, ObjIdType objId)
|
||||
|
||||
@@ -23,19 +23,19 @@
|
||||
#include "database/StarredReleaseId.hpp"
|
||||
#include "database/StarredTrackId.hpp"
|
||||
|
||||
namespace Feedback
|
||||
namespace lms::feedback
|
||||
{
|
||||
class IFeedbackBackend
|
||||
{
|
||||
public:
|
||||
virtual ~IFeedbackBackend() = default;
|
||||
|
||||
virtual void onStarred(Database::StarredArtistId) = 0;
|
||||
virtual void onUnstarred(Database::StarredArtistId) = 0;
|
||||
virtual void onStarred(Database::StarredReleaseId) = 0;
|
||||
virtual void onUnstarred(Database::StarredReleaseId) = 0;
|
||||
virtual void onStarred(Database::StarredTrackId) = 0;
|
||||
virtual void onUnstarred(Database::StarredTrackId) = 0;
|
||||
virtual void onStarred(db::StarredArtistId) = 0;
|
||||
virtual void onUnstarred(db::StarredArtistId) = 0;
|
||||
virtual void onStarred(db::StarredReleaseId) = 0;
|
||||
virtual void onUnstarred(db::StarredReleaseId) = 0;
|
||||
virtual void onStarred(db::StarredTrackId) = 0;
|
||||
virtual void onUnstarred(db::StarredTrackId) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IFeedbackBackend> createFeedbackBackend(std::string_view backendName);
|
||||
|
||||
@@ -25,21 +25,21 @@
|
||||
#include "database/StarredRelease.hpp"
|
||||
#include "database/StarredTrack.hpp"
|
||||
|
||||
namespace Feedback
|
||||
namespace lms::feedback
|
||||
{
|
||||
namespace details
|
||||
{
|
||||
template <typename StarredObjType>
|
||||
void onStarred(Database::Session& session, typename StarredObjType::IdType id)
|
||||
void onStarred(db::Session& session, typename StarredObjType::IdType id)
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
if (auto starredObj{ StarredObjType::find(session, id) })
|
||||
starredObj.modify()->setSyncState(Database::SyncState::Synchronized);
|
||||
starredObj.modify()->setSyncState(db::SyncState::Synchronized);
|
||||
}
|
||||
|
||||
template <typename StarredObjType>
|
||||
void onUnstarred(Database::Session& session, typename StarredObjType::IdType id)
|
||||
void onUnstarred(db::Session& session, typename StarredObjType::IdType id)
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
@@ -48,37 +48,37 @@ namespace Feedback
|
||||
}
|
||||
}
|
||||
|
||||
InternalBackend::InternalBackend(Database::Db& db)
|
||||
InternalBackend::InternalBackend(db::Db& db)
|
||||
: _db{ db }
|
||||
{}
|
||||
|
||||
void InternalBackend::onStarred(Database::StarredArtistId starredArtistId)
|
||||
void InternalBackend::onStarred(db::StarredArtistId starredArtistId)
|
||||
{
|
||||
details::onStarred<Database::StarredArtist>(_db.getTLSSession(), starredArtistId);
|
||||
details::onStarred<db::StarredArtist>(_db.getTLSSession(), starredArtistId);
|
||||
}
|
||||
|
||||
void InternalBackend::onUnstarred(Database::StarredArtistId starredArtistId)
|
||||
void InternalBackend::onUnstarred(db::StarredArtistId starredArtistId)
|
||||
{
|
||||
details::onUnstarred<Database::StarredArtist>(_db.getTLSSession(), starredArtistId);
|
||||
details::onUnstarred<db::StarredArtist>(_db.getTLSSession(), starredArtistId);
|
||||
}
|
||||
|
||||
void InternalBackend::onStarred(Database::StarredReleaseId starredReleaseId)
|
||||
void InternalBackend::onStarred(db::StarredReleaseId starredReleaseId)
|
||||
{
|
||||
details::onStarred<Database::StarredRelease>(_db.getTLSSession(), starredReleaseId);
|
||||
details::onStarred<db::StarredRelease>(_db.getTLSSession(), starredReleaseId);
|
||||
}
|
||||
|
||||
void InternalBackend::onUnstarred(Database::StarredReleaseId starredReleaseId)
|
||||
void InternalBackend::onUnstarred(db::StarredReleaseId starredReleaseId)
|
||||
{
|
||||
details::onUnstarred<Database::StarredRelease>(_db.getTLSSession(), starredReleaseId);
|
||||
details::onUnstarred<db::StarredRelease>(_db.getTLSSession(), starredReleaseId);
|
||||
}
|
||||
|
||||
void InternalBackend::onStarred(Database::StarredTrackId starredTrackId)
|
||||
void InternalBackend::onStarred(db::StarredTrackId starredTrackId)
|
||||
{
|
||||
details::onStarred<Database::StarredTrack>(_db.getTLSSession(), starredTrackId);
|
||||
details::onStarred<db::StarredTrack>(_db.getTLSSession(), starredTrackId);
|
||||
}
|
||||
|
||||
void InternalBackend::onUnstarred(Database::StarredTrackId starredTrackId)
|
||||
void InternalBackend::onUnstarred(db::StarredTrackId starredTrackId)
|
||||
{
|
||||
details::onUnstarred<Database::StarredTrack>(_db.getTLSSession(), starredTrackId);
|
||||
details::onUnstarred<db::StarredTrack>(_db.getTLSSession(), starredTrackId);
|
||||
}
|
||||
} // Feedback
|
||||
|
||||
@@ -21,27 +21,27 @@
|
||||
|
||||
#include "IFeedbackBackend.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Feedback
|
||||
namespace lms::feedback
|
||||
{
|
||||
class InternalBackend final : public IFeedbackBackend
|
||||
{
|
||||
public:
|
||||
InternalBackend(Database::Db& db);
|
||||
InternalBackend(db::Db& db);
|
||||
|
||||
private:
|
||||
void onStarred(Database::StarredArtistId) override;
|
||||
void onUnstarred(Database::StarredArtistId) override;
|
||||
void onStarred(Database::StarredReleaseId) override;
|
||||
void onUnstarred(Database::StarredReleaseId) override;
|
||||
void onStarred(Database::StarredTrackId) override;
|
||||
void onUnstarred(Database::StarredTrackId) override;
|
||||
void onStarred(db::StarredArtistId) override;
|
||||
void onUnstarred(db::StarredArtistId) override;
|
||||
void onStarred(db::StarredReleaseId) override;
|
||||
void onUnstarred(db::StarredReleaseId) override;
|
||||
void onStarred(db::StarredTrackId) override;
|
||||
void onUnstarred(db::StarredTrackId) override;
|
||||
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
};
|
||||
} // Feedback
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
#include "services/feedback/Exception.hpp"
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
class Exception : public ::Feedback::Exception
|
||||
class Exception : public feedback::Exception
|
||||
{
|
||||
public:
|
||||
using ::Feedback::Exception::Exception;
|
||||
using feedback::Exception::Exception;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "FeedbackTypes.hpp"
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const Feedback& feedback)
|
||||
@@ -27,4 +27,4 @@ namespace Feedback::ListenBrainz
|
||||
os << "created = '" << feedback.created.toString() << "', recording MBID = '" << feedback.recordingMBID.getAsString() << "', score = " << static_cast<int>(feedback.score);
|
||||
return os;
|
||||
}
|
||||
} // Feedback::ListenBrainz
|
||||
} // feedback::ListenBrainz
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
#include <ostream>
|
||||
#include <Wt/WDateTime.h>
|
||||
#include "utils/UUID.hpp"
|
||||
#include "core/UUID.hpp"
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
// See https://listenbrainz.readthedocs.io/en/production/dev/feedback-json/#feedback-json-doc
|
||||
enum class FeedbackType
|
||||
@@ -36,10 +36,10 @@ namespace Feedback::ListenBrainz
|
||||
struct Feedback
|
||||
{
|
||||
Wt::WDateTime created;
|
||||
UUID recordingMBID;
|
||||
core::UUID recordingMBID;
|
||||
FeedbackType score;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Feedback& feedback);
|
||||
|
||||
} // Feedback::ListenBrainz
|
||||
} // feedback::ListenBrainz
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
#include "Exception.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
namespace
|
||||
{
|
||||
Feedback parseFeedback(const Wt::Json::Object& feedbackObj)
|
||||
{
|
||||
const std::optional<UUID> recordingMBID{ UUID::fromString(static_cast<std::string>(feedbackObj.get("recording_mbid"))) };
|
||||
const std::optional<core::UUID> recordingMBID{ core::UUID::fromString(static_cast<std::string>(feedbackObj.get("recording_mbid"))) };
|
||||
if (!recordingMBID)
|
||||
throw Exception{ "MBID not found!" };
|
||||
|
||||
@@ -87,4 +87,4 @@ namespace Feedback::ListenBrainz
|
||||
|
||||
return res;
|
||||
}
|
||||
} // Feedback::ListenBrainz
|
||||
} // feedback::ListenBrainz
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "FeedbackTypes.hpp"
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
class FeedbacksParser
|
||||
{
|
||||
@@ -37,4 +37,4 @@ namespace Feedback::ListenBrainz
|
||||
static Result parse(std::string_view msgBody);
|
||||
};
|
||||
|
||||
} // Feedback::ListenBrainz
|
||||
} // feedback::ListenBrainz
|
||||
|
||||
@@ -30,15 +30,15 @@
|
||||
#include "database/StarredTrack.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/http/IClient.hpp"
|
||||
#include "utils/Service.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/http/IClient.hpp"
|
||||
#include "core/Service.hpp"
|
||||
|
||||
#include "Exception.hpp"
|
||||
#include "FeedbacksParser.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -59,37 +59,37 @@ namespace Feedback::ListenBrainz
|
||||
}
|
||||
}
|
||||
|
||||
FeedbacksSynchronizer::FeedbacksSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client)
|
||||
FeedbacksSynchronizer::FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client)
|
||||
: _ioContext{ ioContext }
|
||||
, _db{ db }
|
||||
, _client{ client }
|
||||
, _maxSyncFeedbackCount{ Service<IConfig>::get()->getULong("listenbrainz-max-sync-feedback-count", 1000) }
|
||||
, _syncFeedbacksPeriod{ Service<IConfig>::get()->getULong("listenbrainz-sync-feedbacks-period-hours", 1) }
|
||||
, _maxSyncFeedbackCount{ core::Service<core::IConfig>::get()->getULong("listenbrainz-max-sync-feedback-count", 1000) }
|
||||
, _syncFeedbacksPeriod{ core::Service<core::IConfig>::get()->getULong("listenbrainz-sync-feedbacks-period-hours", 1) }
|
||||
{
|
||||
LOG(INFO, "Starting Feedbacks synchronizer, maxSyncFeedbackCount = " << _maxSyncFeedbackCount << ", _syncFeedbacksPeriod = " << _syncFeedbacksPeriod.count() << " hours");
|
||||
|
||||
scheduleSync(std::chrono::seconds{ 30 });
|
||||
}
|
||||
|
||||
void FeedbacksSynchronizer::enqueFeedback(FeedbackType type, Database::StarredTrackId starredTrackId)
|
||||
void FeedbacksSynchronizer::enqueFeedback(FeedbackType type, db::StarredTrackId starredTrackId)
|
||||
{
|
||||
try
|
||||
{
|
||||
Database::Session& session{ _db.getTLSSession() };
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
Database::StarredTrack::pointer starredTrack{ Database::StarredTrack::find(session, starredTrackId) };
|
||||
db::StarredTrack::pointer starredTrack{ db::StarredTrack::find(session, starredTrackId) };
|
||||
if (!starredTrack)
|
||||
return;
|
||||
|
||||
std::optional<UUID> recordingMBID{ starredTrack->getTrack()->getRecordingMBID() };
|
||||
std::optional<core::UUID> recordingMBID{ starredTrack->getTrack()->getRecordingMBID() };
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case FeedbackType::Love:
|
||||
if (starredTrack->getSyncState() != Database::SyncState::PendingAdd)
|
||||
starredTrack.modify()->setSyncState(Database::SyncState::PendingAdd);
|
||||
if (starredTrack->getSyncState() != db::SyncState::PendingAdd)
|
||||
starredTrack.modify()->setSyncState(db::SyncState::PendingAdd);
|
||||
break;
|
||||
|
||||
case FeedbackType::Erase:
|
||||
@@ -102,7 +102,7 @@ namespace Feedback::ListenBrainz
|
||||
{
|
||||
// Send the erase order even if it is not on the remote LB server (it may be
|
||||
// queued for add, or not)
|
||||
starredTrack.modify()->setSyncState(Database::SyncState::PendingRemove);
|
||||
starredTrack.modify()->setSyncState(db::SyncState::PendingRemove);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -116,11 +116,11 @@ namespace Feedback::ListenBrainz
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<UUID> listenBrainzToken{ starredTrack->getUser()->getListenBrainzToken() };
|
||||
const std::optional<core::UUID> listenBrainzToken{ starredTrack->getUser()->getListenBrainzToken() };
|
||||
if (!listenBrainzToken)
|
||||
return;
|
||||
|
||||
Http::ClientPOSTRequestParameters request;
|
||||
core::http::ClientPOSTRequestParameters request;
|
||||
request.relativeUrl = "/1/feedback/recording-feedback";
|
||||
request.message.addHeader("Authorization", "Token " + std::string{ listenBrainzToken->getAsString() });
|
||||
|
||||
@@ -146,14 +146,14 @@ namespace Feedback::ListenBrainz
|
||||
}
|
||||
}
|
||||
|
||||
void FeedbacksSynchronizer::onFeedbackSent(FeedbackType type, Database::StarredTrackId starredTrackId)
|
||||
void FeedbacksSynchronizer::onFeedbackSent(FeedbackType type, db::StarredTrackId starredTrackId)
|
||||
{
|
||||
assert(_strand.running_in_this_thread());
|
||||
|
||||
Database::Session& session{ _db.getTLSSession() };
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
Database::StarredTrack::pointer starredTrack{ Database::StarredTrack::find(session, starredTrackId) };
|
||||
db::StarredTrack::pointer starredTrack{ db::StarredTrack::find(session, starredTrackId) };
|
||||
if (!starredTrack)
|
||||
{
|
||||
LOG(DEBUG, "Starred track not found. deleted?");
|
||||
@@ -165,7 +165,7 @@ namespace Feedback::ListenBrainz
|
||||
switch (type)
|
||||
{
|
||||
case FeedbackType::Love:
|
||||
starredTrack.modify()->setSyncState(Database::SyncState::Synchronized);
|
||||
starredTrack.modify()->setSyncState(db::SyncState::Synchronized);
|
||||
LOG(DEBUG, "State set to synchronized");
|
||||
|
||||
if (userContext.feedbackCount)
|
||||
@@ -193,20 +193,20 @@ namespace Feedback::ListenBrainz
|
||||
|
||||
void FeedbacksSynchronizer::enquePendingFeedbacks()
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
auto processPendingFeedbacks{ [this](SyncState scrobblingState, FeedbackType feedbackType)
|
||||
{
|
||||
RangeResults<StarredTrackId> pendingFeedbacks;
|
||||
|
||||
{
|
||||
Database::Session& session {_db.getTLSSession()};
|
||||
db::Session& session {_db.getTLSSession()};
|
||||
|
||||
auto transaction {session.createReadTransaction()};
|
||||
|
||||
StarredTrack::FindParameters params;
|
||||
params.setFeedbackBackend(Database::FeedbackBackend::ListenBrainz, scrobblingState)
|
||||
.setRange(Database::Range {0, 100}); // don't flood too much?
|
||||
params.setFeedbackBackend(db::FeedbackBackend::ListenBrainz, scrobblingState)
|
||||
.setRange(db::Range {0, 100}); // don't flood too much?
|
||||
|
||||
pendingFeedbacks = StarredTrack::find(session, params);
|
||||
}
|
||||
@@ -221,7 +221,7 @@ namespace Feedback::ListenBrainz
|
||||
processPendingFeedbacks(SyncState::PendingRemove, FeedbackType::Erase);
|
||||
}
|
||||
|
||||
FeedbacksSynchronizer::UserContext& FeedbacksSynchronizer::getUserContext(Database::UserId userId)
|
||||
FeedbacksSynchronizer::UserContext& FeedbacksSynchronizer::getUserContext(db::UserId userId)
|
||||
{
|
||||
assert(_strand.running_in_this_thread());
|
||||
|
||||
@@ -274,14 +274,14 @@ namespace Feedback::ListenBrainz
|
||||
|
||||
enquePendingFeedbacks();
|
||||
|
||||
Database::RangeResults<Database::UserId> userIds;
|
||||
db::RangeResults<db::UserId> userIds;
|
||||
{
|
||||
Database::Session& session{ _db.getTLSSession() };
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
userIds = Database::User::find(_db.getTLSSession(), Database::User::FindParameters{}.setFeedbackBackend(Database::FeedbackBackend::ListenBrainz));
|
||||
userIds = db::User::find(_db.getTLSSession(), db::User::FindParameters{}.setFeedbackBackend(db::FeedbackBackend::ListenBrainz));
|
||||
}
|
||||
|
||||
for (const Database::UserId userId : userIds.results)
|
||||
for (const db::UserId userId : userIds.results)
|
||||
startSync(getUserContext(userId));
|
||||
|
||||
if (!isSyncing())
|
||||
@@ -315,20 +315,20 @@ namespace Feedback::ListenBrainz
|
||||
{
|
||||
assert(context.listenBrainzUserName.empty());
|
||||
|
||||
const std::optional<UUID> listenBrainzToken{ ListenBrainz::Utils::getListenBrainzToken(_db.getTLSSession(), context.userId) };
|
||||
const std::optional<core::UUID> listenBrainzToken{ utils::getListenBrainzToken(_db.getTLSSession(), context.userId) };
|
||||
if (!listenBrainzToken)
|
||||
{
|
||||
onSyncEnded(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Http::ClientGETRequestParameters request;
|
||||
request.priority = Http::ClientRequestParameters::Priority::Low;
|
||||
core::http::ClientGETRequestParameters request;
|
||||
request.priority = core::http::ClientRequestParameters::Priority::Low;
|
||||
request.relativeUrl = "/1/validate-token";
|
||||
request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} };
|
||||
request.onSuccessFunc = [this, &context](std::string_view msgBody)
|
||||
{
|
||||
context.listenBrainzUserName = ListenBrainz::Utils::parseValidateToken(msgBody);
|
||||
context.listenBrainzUserName = utils::parseValidateToken(msgBody);
|
||||
if (context.listenBrainzUserName.empty())
|
||||
{
|
||||
onSyncEnded(context);
|
||||
@@ -348,9 +348,9 @@ namespace Feedback::ListenBrainz
|
||||
{
|
||||
assert(!context.listenBrainzUserName.empty());
|
||||
|
||||
Http::ClientGETRequestParameters request;
|
||||
core::http::ClientGETRequestParameters request;
|
||||
request.relativeUrl = "/1/feedback/user/" + std::string{ context.listenBrainzUserName } + "/get-feedback?score=1&count=0";
|
||||
request.priority = Http::ClientRequestParameters::Priority::Low;
|
||||
request.priority = core::http::ClientRequestParameters::Priority::Low;
|
||||
request.onSuccessFunc = [this, &context](std::string_view msgBody)
|
||||
{
|
||||
std::string msgBodyCopy{ msgBody };
|
||||
@@ -383,9 +383,9 @@ namespace Feedback::ListenBrainz
|
||||
{
|
||||
assert(!context.listenBrainzUserName.empty());
|
||||
|
||||
Http::ClientGETRequestParameters request;
|
||||
core::http::ClientGETRequestParameters request;
|
||||
request.relativeUrl = "/1/feedback/user/" + context.listenBrainzUserName + "/get-feedback?offset=" + std::to_string(context.fetchedFeedbackCount);
|
||||
request.priority = Http::ClientRequestParameters::Priority::Low;
|
||||
request.priority = core::http::ClientRequestParameters::Priority::Low;
|
||||
request.onSuccessFunc = [this, &context](std::string_view msgBody)
|
||||
{
|
||||
std::string msgBodyCopy{ msgBody };
|
||||
@@ -429,7 +429,7 @@ namespace Feedback::ListenBrainz
|
||||
|
||||
void FeedbacksSynchronizer::tryImportFeedback(const Feedback& feedback, UserContext& context)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
|
||||
@@ -451,7 +451,7 @@ namespace Feedback::ListenBrainz
|
||||
}
|
||||
|
||||
trackId = tracks.front()->getId();
|
||||
needImport = !StarredTrack::exists(session, trackId, context.userId, Database::FeedbackBackend::ListenBrainz);
|
||||
needImport = !StarredTrack::exists(session, trackId, context.userId, db::FeedbackBackend::ListenBrainz);
|
||||
|
||||
// don't update starred date time
|
||||
// no need to update state if it was found as not synchronized
|
||||
@@ -473,7 +473,7 @@ namespace Feedback::ListenBrainz
|
||||
if (!user)
|
||||
return;
|
||||
|
||||
StarredTrack::pointer starredTrack{ session.create<StarredTrack>(track, user, Database::FeedbackBackend::ListenBrainz) };
|
||||
StarredTrack::pointer starredTrack{ session.create<StarredTrack>(track, user, db::FeedbackBackend::ListenBrainz) };
|
||||
starredTrack.modify()->setSyncState(SyncState::Synchronized);
|
||||
starredTrack.modify()->setDateTime(feedback.created);
|
||||
|
||||
@@ -485,4 +485,4 @@ namespace Feedback::ListenBrainz
|
||||
context.matchedFeedbackCount++;
|
||||
}
|
||||
}
|
||||
} // namespace Feedback::ListenBrainz
|
||||
} // namespace lms::feedback::listenBrainz
|
||||
|
||||
@@ -31,38 +31,40 @@
|
||||
|
||||
#include "FeedbackTypes.hpp"
|
||||
|
||||
namespace Database
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Http
|
||||
namespace lms
|
||||
{
|
||||
namespace core::http
|
||||
{
|
||||
class IClient;
|
||||
}
|
||||
namespace db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
class FeedbacksSynchronizer
|
||||
{
|
||||
public:
|
||||
FeedbacksSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client);
|
||||
FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client);
|
||||
|
||||
void enqueFeedback(FeedbackType type, Database::StarredTrackId starredTrackId);
|
||||
void enqueFeedback(FeedbackType type, db::StarredTrackId starredTrackId);
|
||||
|
||||
private:
|
||||
void onFeedbackSent(FeedbackType type, Database::StarredTrackId starredTrackId);
|
||||
void onFeedbackSent(FeedbackType type, db::StarredTrackId starredTrackId);
|
||||
|
||||
void enquePendingFeedbacks();
|
||||
|
||||
struct UserContext
|
||||
{
|
||||
UserContext(Database::UserId id) : userId{ id } {}
|
||||
UserContext(db::UserId id) : userId{ id } {}
|
||||
|
||||
UserContext(const UserContext&) = delete;
|
||||
UserContext& operator=(const UserContext&) = delete;
|
||||
|
||||
const Database::UserId userId;
|
||||
const db::UserId userId;
|
||||
bool syncing{};
|
||||
std::optional<std::size_t> feedbackCount{};
|
||||
|
||||
@@ -75,7 +77,7 @@ namespace Feedback::ListenBrainz
|
||||
std::size_t importedFeedbackCount{};
|
||||
};
|
||||
|
||||
UserContext& getUserContext(Database::UserId userId);
|
||||
UserContext& getUserContext(db::UserId userId);
|
||||
bool isSyncing() const;
|
||||
void scheduleSync(std::chrono::seconds fromNow);
|
||||
void startSync();
|
||||
@@ -89,14 +91,14 @@ namespace Feedback::ListenBrainz
|
||||
|
||||
boost::asio::io_context& _ioContext;
|
||||
boost::asio::io_context::strand _strand{ _ioContext };
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
boost::asio::steady_timer _syncTimer{ _ioContext };
|
||||
Http::IClient& _client;
|
||||
core::http::IClient& _client;
|
||||
|
||||
std::unordered_map<Database::UserId, UserContext> _userContexts;
|
||||
std::unordered_map<db::UserId, UserContext> _userContexts;
|
||||
|
||||
const std::size_t _maxSyncFeedbackCount;
|
||||
const std::chrono::hours _syncFeedbacksPeriod;
|
||||
};
|
||||
} // Feedback::ListenBrainz
|
||||
} // feedback::ListenBrainz
|
||||
|
||||
|
||||
@@ -24,30 +24,30 @@
|
||||
#include "database/StarredArtist.hpp"
|
||||
#include "database/StarredRelease.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/http/IClient.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Service.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/http/IClient.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
namespace details
|
||||
{
|
||||
template <typename StarredObjType>
|
||||
void onStarred(Database::Session& session, typename StarredObjType::IdType id)
|
||||
void onStarred(db::Session& session, typename StarredObjType::IdType id)
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
if (auto starredObj{ StarredObjType::find(session, id) })
|
||||
{
|
||||
// maybe in the future this will be supported by ListenBrainz so set it to PendingAdd for all types
|
||||
starredObj.modify()->setSyncState(Database::SyncState::PendingAdd);
|
||||
starredObj.modify()->setSyncState(db::SyncState::PendingAdd);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename StarredObjType>
|
||||
void onUnstarred(Database::Session& session, typename StarredObjType::IdType id)
|
||||
void onUnstarred(db::Session& session, typename StarredObjType::IdType id)
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
@@ -56,11 +56,11 @@ namespace Feedback::ListenBrainz
|
||||
}
|
||||
}
|
||||
|
||||
ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, Database::Db& db)
|
||||
ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db)
|
||||
: _ioContext{ ioContext }
|
||||
, _db{ db }
|
||||
, _baseAPIUrl{ Service<IConfig>::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") }
|
||||
, _client{ Http::createClient(_ioContext, _baseAPIUrl) }
|
||||
, _baseAPIUrl{ core::Service<core::IConfig>::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") }
|
||||
, _client{ core::http::createClient(_ioContext, _baseAPIUrl) }
|
||||
, _feedbacksSynchronizer{ _ioContext, db, *_client }
|
||||
{
|
||||
LOG(INFO, "Starting ListenBrainz feedback backend... API endpoint = '" << _baseAPIUrl << "'");
|
||||
@@ -71,33 +71,33 @@ namespace Feedback::ListenBrainz
|
||||
LOG(INFO, "Stopped ListenBrainz feedback backend!");
|
||||
}
|
||||
|
||||
void ListenBrainzBackend::onStarred(Database::StarredArtistId starredArtistId)
|
||||
void ListenBrainzBackend::onStarred(db::StarredArtistId starredArtistId)
|
||||
{
|
||||
details::onStarred<Database::StarredArtist>(_db.getTLSSession(), starredArtistId);
|
||||
details::onStarred<db::StarredArtist>(_db.getTLSSession(), starredArtistId);
|
||||
}
|
||||
|
||||
void ListenBrainzBackend::onUnstarred(Database::StarredArtistId starredArtistId)
|
||||
void ListenBrainzBackend::onUnstarred(db::StarredArtistId starredArtistId)
|
||||
{
|
||||
details::onUnstarred<Database::StarredArtist>(_db.getTLSSession(), starredArtistId);
|
||||
details::onUnstarred<db::StarredArtist>(_db.getTLSSession(), starredArtistId);
|
||||
}
|
||||
|
||||
void ListenBrainzBackend::onStarred(Database::StarredReleaseId starredReleaseId)
|
||||
void ListenBrainzBackend::onStarred(db::StarredReleaseId starredReleaseId)
|
||||
{
|
||||
details::onStarred<Database::StarredRelease>(_db.getTLSSession(), starredReleaseId);
|
||||
details::onStarred<db::StarredRelease>(_db.getTLSSession(), starredReleaseId);
|
||||
}
|
||||
|
||||
void ListenBrainzBackend::onUnstarred(Database::StarredReleaseId starredReleaseId)
|
||||
void ListenBrainzBackend::onUnstarred(db::StarredReleaseId starredReleaseId)
|
||||
{
|
||||
details::onUnstarred<Database::StarredRelease>(_db.getTLSSession(), starredReleaseId);
|
||||
details::onUnstarred<db::StarredRelease>(_db.getTLSSession(), starredReleaseId);
|
||||
}
|
||||
|
||||
void ListenBrainzBackend::onStarred(Database::StarredTrackId starredTrackId)
|
||||
void ListenBrainzBackend::onStarred(db::StarredTrackId starredTrackId)
|
||||
{
|
||||
_feedbacksSynchronizer.enqueFeedback(FeedbackType::Love, starredTrackId);
|
||||
}
|
||||
|
||||
void ListenBrainzBackend::onUnstarred(Database::StarredTrackId starredtrackId)
|
||||
void ListenBrainzBackend::onUnstarred(db::StarredTrackId starredtrackId)
|
||||
{
|
||||
_feedbacksSynchronizer.enqueFeedback(FeedbackType::Erase, starredtrackId);
|
||||
}
|
||||
} // namespace Scrobbling::ListenBrainz
|
||||
} // namespace lms::scrobbling::listenBrainz
|
||||
|
||||
@@ -26,35 +26,34 @@
|
||||
#include "IFeedbackBackend.hpp"
|
||||
#include "FeedbacksSynchronizer.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Feedback::ListenBrainz
|
||||
namespace lms::feedback::listenBrainz
|
||||
{
|
||||
class ListenBrainzBackend final : public IFeedbackBackend
|
||||
{
|
||||
public:
|
||||
ListenBrainzBackend(boost::asio::io_context& ioContext, Database::Db& db);
|
||||
ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db);
|
||||
~ListenBrainzBackend() override;
|
||||
|
||||
private:
|
||||
ListenBrainzBackend(const ListenBrainzBackend&) = delete;
|
||||
ListenBrainzBackend& operator=(const ListenBrainzBackend&) = delete;
|
||||
|
||||
void onStarred(Database::StarredArtistId starredArtistId) override;
|
||||
void onUnstarred(Database::StarredArtistId starredArtistId) override;
|
||||
void onStarred(Database::StarredReleaseId starredReleaseId) override;
|
||||
void onUnstarred(Database::StarredReleaseId starredReleaseId) override;
|
||||
void onStarred(Database::StarredTrackId starredTrackId) override;
|
||||
void onUnstarred(Database::StarredTrackId starredTrackId) override;
|
||||
void onStarred(db::StarredArtistId starredArtistId) override;
|
||||
void onUnstarred(db::StarredArtistId starredArtistId) override;
|
||||
void onStarred(db::StarredReleaseId starredReleaseId) override;
|
||||
void onUnstarred(db::StarredReleaseId starredReleaseId) override;
|
||||
void onStarred(db::StarredTrackId starredTrackId) override;
|
||||
void onUnstarred(db::StarredTrackId starredTrackId) override;
|
||||
|
||||
boost::asio::io_context& _ioContext;
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
std::string _baseAPIUrl;
|
||||
std::unique_ptr<Http::IClient> _client;
|
||||
std::unique_ptr<core::http::IClient> _client;
|
||||
FeedbacksSynchronizer _feedbacksSynchronizer;
|
||||
};
|
||||
} // Feedback::ListenBrainz
|
||||
|
||||
}
|
||||
@@ -25,13 +25,13 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
namespace Feedback::ListenBrainz::Utils
|
||||
namespace lms::feedback::listenBrainz::utils
|
||||
{
|
||||
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId)
|
||||
std::optional<core::UUID> getListenBrainzToken(db::Session& session, db::UserId userId)
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const Database::User::pointer user{ Database::User::find(session, userId) };
|
||||
const db::User::pointer user{ db::User::find(session, userId) };
|
||||
if (!user)
|
||||
return std::nullopt;
|
||||
|
||||
|
||||
@@ -20,18 +20,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "database/UserId.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/UUID.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/UUID.hpp"
|
||||
|
||||
#define LOG(sev, message) LMS_LOG(FEEDBACK, sev, "[listenbrainz] " << message)
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
}
|
||||
|
||||
namespace Feedback::ListenBrainz::Utils
|
||||
namespace lms::feedback::listenBrainz::utils
|
||||
{
|
||||
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId);
|
||||
std::optional<core::UUID> getListenBrainzToken(db::Session& session, db::UserId userId);
|
||||
std::string parseValidateToken(std::string_view msgBody);
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "utils/Exception.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
|
||||
namespace Feedback
|
||||
namespace lms::feedback
|
||||
{
|
||||
class Exception : public LmsException
|
||||
class Exception : public core::LmsException
|
||||
{
|
||||
public:
|
||||
using LmsException::LmsException;
|
||||
|
||||
@@ -33,67 +33,67 @@
|
||||
#include "database/UserId.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Feedback
|
||||
namespace lms::feedback
|
||||
{
|
||||
class IFeedbackService
|
||||
{
|
||||
public:
|
||||
virtual ~IFeedbackService() = default;
|
||||
|
||||
using ArtistContainer = Database::RangeResults<Database::ArtistId>;
|
||||
using ReleaseContainer = Database::RangeResults<Database::ReleaseId>;
|
||||
using TrackContainer = Database::RangeResults<Database::TrackId>;
|
||||
using ArtistContainer = db::RangeResults<db::ArtistId>;
|
||||
using ReleaseContainer = db::RangeResults<db::ReleaseId>;
|
||||
using TrackContainer = db::RangeResults<db::TrackId>;
|
||||
|
||||
struct FindParameters
|
||||
{
|
||||
Database::UserId user;
|
||||
std::vector<Database::ClusterId> clusters; // if non empty, at least one artist that belongs to these clusters
|
||||
std::optional<Database::Range> range;
|
||||
Database::MediaLibraryId library;
|
||||
db::UserId user;
|
||||
std::vector<db::ClusterId> clusters; // if non empty, at least one artist that belongs to these clusters
|
||||
std::optional<db::Range> range;
|
||||
db::MediaLibraryId library;
|
||||
|
||||
FindParameters& setUser(const Database::UserId _user) { user = _user; return *this; }
|
||||
FindParameters& setClusters(const std::vector<Database::ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
||||
FindParameters& setRange(std::optional<Database::Range> _range) { range = _range; return *this; }
|
||||
FindParameters& setMediaLibrary(Database::MediaLibraryId _library) { library = _library; return *this; }
|
||||
FindParameters& setUser(const db::UserId _user) { user = _user; return *this; }
|
||||
FindParameters& setClusters(const std::vector<db::ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
||||
FindParameters& setRange(std::optional<db::Range> _range) { range = _range; return *this; }
|
||||
FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; }
|
||||
};
|
||||
|
||||
// Artists
|
||||
struct ArtistFindParameters : public FindParameters
|
||||
{
|
||||
std::optional<Database::TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
||||
Database::ArtistSortMethod sortMethod{ Database::ArtistSortMethod::None };
|
||||
std::optional<db::TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
||||
db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None };
|
||||
|
||||
ArtistFindParameters& setLinkType(std::optional<Database::TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; }
|
||||
ArtistFindParameters& setSortMethod(Database::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
|
||||
ArtistFindParameters& setLinkType(std::optional<db::TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; }
|
||||
ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
|
||||
};
|
||||
|
||||
virtual void star(Database::UserId userId, Database::ArtistId artistId) = 0;
|
||||
virtual void unstar(Database::UserId userId, Database::ArtistId artistId) = 0;
|
||||
virtual bool isStarred(Database::UserId userId, Database::ArtistId artistId) = 0;
|
||||
virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) = 0;
|
||||
virtual void star(db::UserId userId, db::ArtistId artistId) = 0;
|
||||
virtual void unstar(db::UserId userId, db::ArtistId artistId) = 0;
|
||||
virtual bool isStarred(db::UserId userId, db::ArtistId artistId) = 0;
|
||||
virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::ArtistId artistId) = 0;
|
||||
virtual ArtistContainer findStarredArtists(const ArtistFindParameters& params) = 0;
|
||||
|
||||
// Releases
|
||||
virtual void star(Database::UserId userId, Database::ReleaseId releaseId) = 0;
|
||||
virtual void unstar(Database::UserId userId, Database::ReleaseId releaseId) = 0;
|
||||
virtual bool isStarred(Database::UserId userId, Database::ReleaseId artistId) = 0;
|
||||
virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ReleaseId artistId) = 0;
|
||||
virtual void star(db::UserId userId, db::ReleaseId releaseId) = 0;
|
||||
virtual void unstar(db::UserId userId, db::ReleaseId releaseId) = 0;
|
||||
virtual bool isStarred(db::UserId userId, db::ReleaseId artistId) = 0;
|
||||
virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::ReleaseId artistId) = 0;
|
||||
virtual ReleaseContainer findStarredReleases(const FindParameters& params) = 0;
|
||||
|
||||
// Tracks
|
||||
virtual void star(Database::UserId userId, Database::TrackId trackId) = 0;
|
||||
virtual void unstar(Database::UserId userId, Database::TrackId trackId) = 0;
|
||||
virtual bool isStarred(Database::UserId userId, Database::TrackId artistId) = 0;
|
||||
virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::TrackId artistId) = 0;
|
||||
virtual void star(db::UserId userId, db::TrackId trackId) = 0;
|
||||
virtual void unstar(db::UserId userId, db::TrackId trackId) = 0;
|
||||
virtual bool isStarred(db::UserId userId, db::TrackId artistId) = 0;
|
||||
virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::TrackId artistId) = 0;
|
||||
virtual TrackContainer findStarredTracks(const FindParameters& params) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IFeedbackService> createFeedbackService(boost::asio::io_service& ioService, Database::Db& db);
|
||||
std::unique_ptr<IFeedbackService> createFeedbackService(boost::asio::io_service& ioService, db::Db& db);
|
||||
|
||||
} // ns Feedback
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
class IEngine;
|
||||
std::unique_ptr<IEngine> createClustersEngine(Database::Db& db);
|
||||
std::unique_ptr<IEngine> createClustersEngine(db::Db& db);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
#include <memory>
|
||||
#include "IEngine.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
std::unique_ptr<IEngine> createFeaturesEngine(Database::Db& db);
|
||||
std::unique_ptr<IEngine> createFeaturesEngine(db::Db& db);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
#include "database/Types.hpp"
|
||||
#include "database/TrackListId.hpp"
|
||||
#include "services/recommendation/Types.hpp"
|
||||
#include "utils/EnumSet.hpp"
|
||||
#include "core/EnumSet.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
class IEngine
|
||||
{
|
||||
@@ -40,13 +40,13 @@ namespace Recommendation
|
||||
virtual void load(bool forceReload, const ProgressCallback& progressCallback = {}) = 0;
|
||||
virtual void requestCancelLoad() = 0;
|
||||
|
||||
virtual TrackContainer findSimilarTracksFromTrackList(Database::TrackListId tracklistId, std::size_t maxCount) const = 0;
|
||||
virtual TrackContainer findSimilarTracks(const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const = 0;
|
||||
virtual ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const = 0;
|
||||
virtual ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const = 0;
|
||||
virtual TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const = 0;
|
||||
virtual TrackContainer findSimilarTracks(const std::vector<db::TrackId>& tracksId, std::size_t maxCount) const = 0;
|
||||
virtual ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const = 0;
|
||||
virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet<db::TrackArtistLinkType> linkTypes, std::size_t maxCount) const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IEngine> createEngine(Database::Db& db);
|
||||
std::unique_ptr<IEngine> createEngine(db::Db& db);
|
||||
|
||||
} // ns Recommendation
|
||||
|
||||
|
||||
@@ -26,18 +26,18 @@
|
||||
#include "playlist-constraints/ConsecutiveArtists.hpp"
|
||||
#include "playlist-constraints/ConsecutiveReleases.hpp"
|
||||
#include "playlist-constraints/DuplicateTracks.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
std::unique_ptr<IPlaylistGeneratorService> createPlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
||||
std::unique_ptr<IPlaylistGeneratorService> createPlaylistGeneratorService(Db& db, IRecommendationService& recommendationService)
|
||||
{
|
||||
return std::make_unique<PlaylistGeneratorService>(db, recommendationService);
|
||||
}
|
||||
|
||||
PlaylistGeneratorService::PlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
||||
PlaylistGeneratorService::PlaylistGeneratorService(Db& db, IRecommendationService& recommendationService)
|
||||
: _db{ db }
|
||||
, _recommendationService{ recommendationService }
|
||||
{
|
||||
@@ -69,7 +69,7 @@ namespace Recommendation
|
||||
// select the similar track that has the best score
|
||||
for (std::size_t trackIndex{}; trackIndex < similarTracks.size(); ++trackIndex)
|
||||
{
|
||||
using namespace Database::Debug;
|
||||
using namespace db::Debug;
|
||||
|
||||
finalResult.push_back(similarTracks[trackIndex]);
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Recommendation
|
||||
return std::vector(std::cbegin(finalResult) + startingTracks.size(), std::cend(finalResult));
|
||||
}
|
||||
|
||||
TrackContainer PlaylistGeneratorService::getTracksFromTrackList(Database::TrackListId tracklistId) const
|
||||
TrackContainer PlaylistGeneratorService::getTracksFromTrackList(db::TrackListId tracklistId) const
|
||||
{
|
||||
TrackContainer tracks;
|
||||
|
||||
|
||||
@@ -23,20 +23,20 @@
|
||||
#include "services/recommendation/IRecommendationService.hpp"
|
||||
#include "playlist-constraints/IConstraint.hpp"
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
class PlaylistGeneratorService : public IPlaylistGeneratorService
|
||||
{
|
||||
public:
|
||||
PlaylistGeneratorService(Database::Db& db, Recommendation::IRecommendationService& recommendationService);
|
||||
PlaylistGeneratorService(db::Db& db, IRecommendationService& recommendationService);
|
||||
|
||||
private:
|
||||
TrackContainer extendPlaylist(Database::TrackListId tracklistId, std::size_t maxCount) const override;
|
||||
TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const override;
|
||||
|
||||
TrackContainer getTracksFromTrackList(Database::TrackListId tracklistId) const;
|
||||
TrackContainer getTracksFromTrackList(db::TrackListId tracklistId) const;
|
||||
|
||||
Database::Db& _db;
|
||||
Recommendation::IRecommendationService& _recommendationService;
|
||||
db::Db& _db;
|
||||
IRecommendationService& _recommendationService;
|
||||
std::vector<std::unique_ptr<PlaylistGeneratorConstraint::IConstraint>> _constraints;
|
||||
};
|
||||
} // namespace Radio
|
||||
|
||||
@@ -28,33 +28,33 @@
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/ScanSettings.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
namespace
|
||||
{
|
||||
Database::ScanSettings::SimilarityEngineType getSimilarityEngineType(Database::Session& session)
|
||||
db::ScanSettings::SimilarityEngineType getSimilarityEngineType(db::Session& session)
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
return Database::ScanSettings::get(session)->getSimilarityEngineType();
|
||||
return db::ScanSettings::get(session)->getSimilarityEngineType();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<IRecommendationService> createRecommendationService(Database::Db& db)
|
||||
std::unique_ptr<IRecommendationService> createRecommendationService(db::Db& db)
|
||||
{
|
||||
return std::make_unique<RecommendationService>(db);
|
||||
}
|
||||
|
||||
RecommendationService::RecommendationService(Database::Db& db)
|
||||
RecommendationService::RecommendationService(db::Db& db)
|
||||
: _db{ db }
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
TrackContainer RecommendationService::findSimilarTracks(Database::TrackListId trackListId, std::size_t maxCount) const
|
||||
TrackContainer RecommendationService::findSimilarTracks(db::TrackListId trackListId, std::size_t maxCount) const
|
||||
{
|
||||
TrackContainer res;
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Recommendation
|
||||
return _engine->findSimilarTracksFromTrackList(trackListId, maxCount);
|
||||
}
|
||||
|
||||
TrackContainer RecommendationService::findSimilarTracks(const std::vector<Database::TrackId>& trackIds, std::size_t maxCount) const
|
||||
TrackContainer RecommendationService::findSimilarTracks(const std::vector<db::TrackId>& trackIds, std::size_t maxCount) const
|
||||
{
|
||||
TrackContainer res;
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Recommendation
|
||||
return _engine->findSimilarTracks(trackIds, maxCount);
|
||||
}
|
||||
|
||||
ReleaseContainer RecommendationService::getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const
|
||||
ReleaseContainer RecommendationService::getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const
|
||||
{
|
||||
ReleaseContainer res;
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace Recommendation
|
||||
return _engine->getSimilarReleases(releaseId, maxCount);;
|
||||
}
|
||||
|
||||
ArtistContainer RecommendationService::getSimilarArtists(Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const
|
||||
ArtistContainer RecommendationService::getSimilarArtists(db::ArtistId artistId, core::EnumSet<db::TrackArtistLinkType> linkTypes, std::size_t maxCount) const
|
||||
{
|
||||
ArtistContainer res;
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Recommendation
|
||||
|
||||
void RecommendationService::load()
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
switch (getSimilarityEngineType(_db.getTLSSession()))
|
||||
{
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
#include "services/recommendation/IRecommendationService.hpp"
|
||||
#include "IEngine.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
enum class EngineType
|
||||
{
|
||||
@@ -40,7 +40,7 @@ namespace Recommendation
|
||||
class RecommendationService : public IRecommendationService
|
||||
{
|
||||
public:
|
||||
RecommendationService(Database::Db& db);
|
||||
RecommendationService(db::Db& db);
|
||||
~RecommendationService() = default;
|
||||
|
||||
RecommendationService(const RecommendationService&) = delete;
|
||||
@@ -49,16 +49,16 @@ namespace Recommendation
|
||||
private:
|
||||
void load() override;
|
||||
|
||||
TrackContainer findSimilarTracks(Database::TrackListId tracklistId, std::size_t maxCount) const override;
|
||||
TrackContainer findSimilarTracks(const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const override;
|
||||
ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const override;
|
||||
ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const override;
|
||||
TrackContainer findSimilarTracks(db::TrackListId tracklistId, std::size_t maxCount) const override;
|
||||
TrackContainer findSimilarTracks(const std::vector<db::TrackId>& tracksId, std::size_t maxCount) const override;
|
||||
ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override;
|
||||
ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet<db::TrackArtistLinkType> linkTypes, std::size_t maxCount) const override;
|
||||
|
||||
void setEnginePriorities(const std::vector<EngineType>& engineTypes);
|
||||
void clearEngines();
|
||||
void loadPendingEngine(EngineType engineType, std::unique_ptr<IEngine> engine, bool forceReload, const ProgressCallback& progressCallback);
|
||||
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
std::optional<EngineType> _engineType;
|
||||
std::unique_ptr<IEngine> _engine;
|
||||
};
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
|
||||
namespace Recommendation {
|
||||
namespace lms::recommendation {
|
||||
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
std::unique_ptr<IEngine> createClustersEngine(Db& db)
|
||||
{
|
||||
@@ -92,7 +92,7 @@ namespace Recommendation {
|
||||
return res;
|
||||
}
|
||||
|
||||
ArtistContainer ClusterEngine::getSimilarArtists(ArtistId artistId, EnumSet<TrackArtistLinkType> artistLinkTypes, std::size_t maxCount) const
|
||||
ArtistContainer ClusterEngine::getSimilarArtists(ArtistId artistId, core::EnumSet<TrackArtistLinkType> artistLinkTypes, std::size_t maxCount) const
|
||||
{
|
||||
if (maxCount == 0)
|
||||
return {};
|
||||
@@ -108,4 +108,4 @@ namespace Recommendation {
|
||||
return std::move(similarArtistIds.results);
|
||||
}
|
||||
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
|
||||
#include "IEngine.hpp"
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
|
||||
class ClusterEngine : public IEngine
|
||||
{
|
||||
public:
|
||||
ClusterEngine(Database::Db& db) : _db {db} {}
|
||||
ClusterEngine(db::Db& db) : _db {db} {}
|
||||
|
||||
ClusterEngine(const ClusterEngine&) = delete;
|
||||
ClusterEngine(ClusterEngine&&) = delete;
|
||||
@@ -38,13 +38,13 @@ namespace Recommendation
|
||||
void load(bool, const ProgressCallback&) override {}
|
||||
void requestCancelLoad() override {}
|
||||
|
||||
TrackContainer findSimilarTracksFromTrackList(Database::TrackListId tracklistId, std::size_t maxCount) const override;
|
||||
TrackContainer findSimilarTracks(const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const override;
|
||||
ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const override;
|
||||
ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const override;
|
||||
TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const override;
|
||||
TrackContainer findSimilarTracks(const std::vector<db::TrackId>& tracksId, std::size_t maxCount) const override;
|
||||
ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override;
|
||||
ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet<db::TrackArtistLinkType> linkTypes, std::size_t maxCount) const override;
|
||||
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
};
|
||||
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#include "utils/Exception.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
|
||||
namespace Recommendation {
|
||||
namespace lms::recommendation {
|
||||
|
||||
static const std::unordered_map<FeatureName, FeatureDef> featureDefinitions
|
||||
{
|
||||
@@ -373,7 +373,7 @@ getFeatureDef(const FeatureName& featureName)
|
||||
{
|
||||
auto it {featureDefinitions.find(featureName)};
|
||||
if (it == std::cend(featureDefinitions))
|
||||
throw LmsException {"Unhandled requested feature '" + featureName + "'"};
|
||||
throw core::LmsException {"Unhandled requested feature '" + featureName + "'"};
|
||||
|
||||
return it->second;
|
||||
}
|
||||
@@ -389,5 +389,5 @@ getFeatureNames()
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace Recommendation {
|
||||
namespace lms::recommendation {
|
||||
|
||||
using FeatureName = std::string;
|
||||
using FeatureNames = std::unordered_set<FeatureName>;
|
||||
@@ -46,4 +46,4 @@ struct FeatureSettings
|
||||
};
|
||||
using FeatureSettingsMap = std::unordered_map<FeatureName, FeatureSettings>;
|
||||
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
#include "database/TrackFeatures.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "som/DataNormalizer.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Random.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Random.hpp"
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
std::unique_ptr<IEngine> createFeaturesEngine(Db& db)
|
||||
{
|
||||
@@ -44,10 +44,10 @@ namespace Recommendation
|
||||
|
||||
namespace
|
||||
{
|
||||
std::optional<SOM::InputVector> convertFeatureValuesMapToInputVector(const FeatureValuesMap& featureValuesMap, std::size_t nbDimensions)
|
||||
std::optional<som::InputVector> convertFeatureValuesMapToInputVector(const FeatureValuesMap& featureValuesMap, std::size_t nbDimensions)
|
||||
{
|
||||
std::size_t i{};
|
||||
std::optional<SOM::InputVector> res{ SOM::InputVector {nbDimensions} };
|
||||
std::optional<som::InputVector> res{ som::InputVector {nbDimensions} };
|
||||
for (const auto& [featureName, values] : featureValuesMap)
|
||||
{
|
||||
if (values.size() != getFeatureDef(featureName).nbDimensions)
|
||||
@@ -64,9 +64,9 @@ namespace Recommendation
|
||||
return res;
|
||||
}
|
||||
|
||||
SOM::InputVector getInputVectorWeights(const FeatureSettingsMap& featureSettingsMap, std::size_t nbDimensions)
|
||||
som::InputVector getInputVectorWeights(const FeatureSettingsMap& featureSettingsMap, std::size_t nbDimensions)
|
||||
{
|
||||
SOM::InputVector weights{ nbDimensions };
|
||||
som::InputVector weights{ nbDimensions };
|
||||
std::size_t index{};
|
||||
for (const auto& [featureName, featureSettings] : featureSettingsMap)
|
||||
{
|
||||
@@ -120,7 +120,7 @@ namespace Recommendation
|
||||
LMS_LOG(RECOMMENDATION, DEBUG, "Getting Track features DONE (found " << trackFeaturesIds.results.size() << " track features)");
|
||||
}
|
||||
|
||||
std::vector<SOM::InputVector> samples;
|
||||
std::vector<som::InputVector> samples;
|
||||
std::vector<TrackId> samplesTrackIds;
|
||||
|
||||
samples.reserve(trackFeaturesIds.results.size());
|
||||
@@ -143,7 +143,7 @@ namespace Recommendation
|
||||
if (featureValuesMap.empty())
|
||||
continue;
|
||||
|
||||
std::optional<SOM::InputVector> inputVector{ convertFeatureValuesMapToInputVector(featureValuesMap, nbDimensions) };
|
||||
std::optional<som::InputVector> inputVector{ convertFeatureValuesMapToInputVector(featureValuesMap, nbDimensions) };
|
||||
if (!inputVector)
|
||||
continue;
|
||||
|
||||
@@ -159,13 +159,13 @@ namespace Recommendation
|
||||
}
|
||||
|
||||
LMS_LOG(RECOMMENDATION, DEBUG, "Normalizing data...");
|
||||
SOM::DataNormalizer dataNormalizer{ nbDimensions };
|
||||
som::DataNormalizer dataNormalizer{ nbDimensions };
|
||||
|
||||
dataNormalizer.computeNormalizationFactors(samples);
|
||||
for (auto& sample : samples)
|
||||
dataNormalizer.normalizeData(sample);
|
||||
|
||||
SOM::Coordinate size{ static_cast<SOM::Coordinate>(std::sqrt(samples.size() / trainSettings.sampleCountPerNeuron)) };
|
||||
som::Coordinate size{ static_cast<som::Coordinate>(std::sqrt(samples.size() / trainSettings.sampleCountPerNeuron)) };
|
||||
if (size < 2)
|
||||
{
|
||||
LMS_LOG(RECOMMENDATION, WARNING, "Very few tracks (" << samples.size() << ") are being used by the features engine, expect bad behaviors");
|
||||
@@ -173,12 +173,12 @@ namespace Recommendation
|
||||
}
|
||||
LMS_LOG(RECOMMENDATION, INFO, "Found " << samples.size() << " tracks, constructing a " << size << "*" << size << " network");
|
||||
|
||||
SOM::Network network{ size, size, nbDimensions };
|
||||
som::Network network{ size, size, nbDimensions };
|
||||
|
||||
SOM::InputVector weights{ getInputVectorWeights(trainSettings.featureSettingsMap, nbDimensions) };
|
||||
som::InputVector weights{ getInputVectorWeights(trainSettings.featureSettingsMap, nbDimensions) };
|
||||
network.setDataWeights(weights);
|
||||
|
||||
auto somProgressCallback{ [&](const SOM::Network::CurrentIteration& iter)
|
||||
auto somProgressCallback{ [&](const som::Network::CurrentIteration& iter)
|
||||
{
|
||||
LMS_LOG(RECOMMENDATION, DEBUG, "Current pass = " << iter.idIteration << " / " << iter.iterationCount);
|
||||
progressCallback(Progress {iter.idIteration, iter.iterationCount});
|
||||
@@ -186,7 +186,7 @@ namespace Recommendation
|
||||
|
||||
LMS_LOG(RECOMMENDATION, DEBUG, "Training network...");
|
||||
network.train(samples, trainSettings.iterationCount,
|
||||
progressCallback ? somProgressCallback : SOM::Network::ProgressCallback{},
|
||||
progressCallback ? somProgressCallback : som::Network::ProgressCallback{},
|
||||
[this] { return _loadCancelled; });
|
||||
LMS_LOG(RECOMMENDATION, DEBUG, "Training network DONE");
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace Recommendation
|
||||
if (_loadCancelled)
|
||||
return;
|
||||
|
||||
const SOM::Position position{ network.getClosestRefVectorPosition(samples[i]) };
|
||||
const som::Position position{ network.getClosestRefVectorPosition(samples[i]) };
|
||||
|
||||
trackPositions[samplesTrackIds[i]].push_back(position);
|
||||
}
|
||||
@@ -275,7 +275,7 @@ namespace Recommendation
|
||||
return similarReleaseIds;
|
||||
}
|
||||
|
||||
ArtistContainer FeaturesEngine::getSimilarArtists(ArtistId artistId, EnumSet<TrackArtistLinkType> linkTypes, std::size_t maxCount) const
|
||||
ArtistContainer FeaturesEngine::getSimilarArtists(ArtistId artistId, core::EnumSet<TrackArtistLinkType> linkTypes, std::size_t maxCount) const
|
||||
{
|
||||
auto getSimilarArtistIdsForLinkType{ [&](TrackArtistLinkType linkType)
|
||||
{
|
||||
@@ -313,7 +313,7 @@ namespace Recommendation
|
||||
}
|
||||
|
||||
while (res.size() > maxCount)
|
||||
res.erase(Random::pickRandom(res));
|
||||
res.erase(core::random::pickRandom(res));
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -349,15 +349,15 @@ namespace Recommendation
|
||||
_loadCancelled = true;
|
||||
}
|
||||
|
||||
void FeaturesEngine::load(const SOM::Network& network, const TrackPositions& trackPositions)
|
||||
void FeaturesEngine::load(const som::Network& network, const TrackPositions& trackPositions)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
_networkRefVectorsDistanceMedian = network.computeRefVectorsDistanceMedian();
|
||||
LMS_LOG(RECOMMENDATION, DEBUG, "Median distance betweend ref vectors = " << _networkRefVectorsDistanceMedian);
|
||||
|
||||
const SOM::Coordinate width{ network.getWidth() };
|
||||
const SOM::Coordinate height{ network.getHeight() };
|
||||
const som::Coordinate width{ network.getWidth() };
|
||||
const som::Coordinate height{ network.getHeight() };
|
||||
|
||||
_releaseMatrix = ReleaseMatrix{ width, height };
|
||||
_trackMatrix = TrackMatrix{ width, height };
|
||||
@@ -377,22 +377,22 @@ namespace Recommendation
|
||||
if (!track)
|
||||
continue;
|
||||
|
||||
for (const SOM::Position& position : positions)
|
||||
for (const som::Position& position : positions)
|
||||
{
|
||||
Utils::push_back_if_not_present(_trackPositions[trackId], position);
|
||||
Utils::push_back_if_not_present(_trackMatrix[position], trackId);
|
||||
core::utils::push_back_if_not_present(_trackPositions[trackId], position);
|
||||
core::utils::push_back_if_not_present(_trackMatrix[position], trackId);
|
||||
|
||||
if (Release::pointer release{ track->getRelease() })
|
||||
{
|
||||
const ReleaseId releaseId{ release->getId() };
|
||||
Utils::push_back_if_not_present(_releasePositions[releaseId], position);
|
||||
Utils::push_back_if_not_present(_releaseMatrix[position], releaseId);
|
||||
core::utils::push_back_if_not_present(_releasePositions[releaseId], position);
|
||||
core::utils::push_back_if_not_present(_releaseMatrix[position], releaseId);
|
||||
}
|
||||
for (const TrackArtistLink::pointer& artistLink : track->getArtistLinks())
|
||||
{
|
||||
const ArtistId artistId{ artistLink->getArtist()->getId() };
|
||||
|
||||
Utils::push_back_if_not_present(_artistPositions[artistId], position);
|
||||
core::utils::push_back_if_not_present(_artistPositions[artistId], position);
|
||||
auto itArtists{ _artistMatrix.find(artistLink->getType()) };
|
||||
if (itArtists == std::cend(_artistMatrix))
|
||||
{
|
||||
@@ -400,12 +400,12 @@ namespace Recommendation
|
||||
assert(inserted);
|
||||
itArtists = it;
|
||||
}
|
||||
Utils::push_back_if_not_present(itArtists->second[position], artistId);
|
||||
core::utils::push_back_if_not_present(itArtists->second[position], artistId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_network = std::make_unique<SOM::Network>(network);
|
||||
_network = std::make_unique<som::Network>(network);
|
||||
|
||||
LMS_LOG(RECOMMENDATION, INFO, "Classifier successfully loaded!");
|
||||
}
|
||||
|
||||
@@ -28,178 +28,174 @@
|
||||
|
||||
#include "som/DataNormalizer.hpp"
|
||||
#include "som/Network.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
#include "core/Utils.hpp"
|
||||
#include "IEngine.hpp"
|
||||
#include "FeaturesEngineCache.hpp"
|
||||
#include "FeaturesDefs.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
class Session;
|
||||
}
|
||||
|
||||
namespace Recommendation {
|
||||
|
||||
using FeatureWeight = double;
|
||||
|
||||
class FeaturesEngine : public IEngine
|
||||
namespace lms::recommendation
|
||||
{
|
||||
public:
|
||||
FeaturesEngine(Database::Db& db) : _db {db} {}
|
||||
using FeatureWeight = double;
|
||||
|
||||
FeaturesEngine(const FeaturesEngine&) = delete;
|
||||
FeaturesEngine(FeaturesEngine&&) = delete;
|
||||
FeaturesEngine& operator=(const FeaturesEngine&) = delete;
|
||||
FeaturesEngine& operator=(FeaturesEngine&&) = delete;
|
||||
class FeaturesEngine : public IEngine
|
||||
{
|
||||
public:
|
||||
FeaturesEngine(db::Db& db) : _db{ db } {}
|
||||
|
||||
static const FeatureSettingsMap& getDefaultTrainFeatureSettings();
|
||||
FeaturesEngine(const FeaturesEngine&) = delete;
|
||||
FeaturesEngine(FeaturesEngine&&) = delete;
|
||||
FeaturesEngine& operator=(const FeaturesEngine&) = delete;
|
||||
FeaturesEngine& operator=(FeaturesEngine&&) = delete;
|
||||
|
||||
private:
|
||||
void load(bool forceReload, const ProgressCallback& progressCallback) override;
|
||||
void requestCancelLoad() override;
|
||||
static const FeatureSettingsMap& getDefaultTrainFeatureSettings();
|
||||
|
||||
TrackContainer findSimilarTracksFromTrackList(Database::TrackListId tracklistId, std::size_t maxCount) const override;
|
||||
TrackContainer findSimilarTracks(const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const override;
|
||||
ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const override;
|
||||
ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const override;
|
||||
private:
|
||||
void load(bool forceReload, const ProgressCallback& progressCallback) override;
|
||||
void requestCancelLoad() override;
|
||||
|
||||
void loadFromCache(FeaturesEngineCache&& cache);
|
||||
TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const override;
|
||||
TrackContainer findSimilarTracks(const std::vector<db::TrackId>& tracksId, std::size_t maxCount) const override;
|
||||
ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override;
|
||||
ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet<db::TrackArtistLinkType> linkTypes, std::size_t maxCount) const override;
|
||||
|
||||
// Use training (may be very slow)
|
||||
struct TrainSettings
|
||||
{
|
||||
std::size_t iterationCount {10};
|
||||
float sampleCountPerNeuron {4};
|
||||
FeatureSettingsMap featureSettingsMap;
|
||||
};
|
||||
void loadFromTraining(const TrainSettings& trainSettings, const ProgressCallback& progressCallback);
|
||||
void loadFromCache(FeaturesEngineCache&& cache);
|
||||
|
||||
template <typename IdType>
|
||||
using ObjectPositions = std::unordered_map<IdType, std::vector<SOM::Position>>;
|
||||
// Use training (may be very slow)
|
||||
struct TrainSettings
|
||||
{
|
||||
std::size_t iterationCount{ 10 };
|
||||
float sampleCountPerNeuron{ 4 };
|
||||
FeatureSettingsMap featureSettingsMap;
|
||||
};
|
||||
void loadFromTraining(const TrainSettings& trainSettings, const ProgressCallback& progressCallback);
|
||||
|
||||
using ArtistPositions = ObjectPositions<Database::ArtistId>;
|
||||
using ReleasePositions = ObjectPositions<Database::ReleaseId>;
|
||||
using TrackPositions = ObjectPositions<Database::TrackId>;
|
||||
template <typename IdType>
|
||||
using ObjectPositions = std::unordered_map<IdType, std::vector<som::Position>>;
|
||||
|
||||
template <typename IdType>
|
||||
using ObjectMatrix = SOM::Matrix<std::vector<IdType>>;
|
||||
using ArtistMatrix = ObjectMatrix<Database::ArtistId>;
|
||||
using ReleaseMatrix = ObjectMatrix<Database::ReleaseId>;
|
||||
using TrackMatrix = ObjectMatrix<Database::TrackId>;
|
||||
using ArtistPositions = ObjectPositions<db::ArtistId>;
|
||||
using ReleasePositions = ObjectPositions<db::ReleaseId>;
|
||||
using TrackPositions = ObjectPositions<db::TrackId>;
|
||||
|
||||
void load(const SOM::Network& network, const TrackPositions& tracksPosition);
|
||||
template <typename IdType>
|
||||
using ObjectMatrix = som::Matrix<std::vector<IdType>>;
|
||||
using ArtistMatrix = ObjectMatrix<db::ArtistId>;
|
||||
using ReleaseMatrix = ObjectMatrix<db::ReleaseId>;
|
||||
using TrackMatrix = ObjectMatrix<db::TrackId>;
|
||||
|
||||
FeaturesEngineCache toCache() const;
|
||||
void load(const som::Network& network, const TrackPositions& tracksPosition);
|
||||
|
||||
template <typename IdType>
|
||||
static std::vector<SOM::Position> getMatchingRefVectorsPosition(const std::vector<IdType>& ids, const ObjectPositions<IdType>& objectPositions);
|
||||
FeaturesEngineCache toCache() const;
|
||||
|
||||
template <typename IdType>
|
||||
static std::vector<IdType> getObjectsIds(const std::vector<SOM::Position>& positions, const ObjectMatrix<IdType>& objectsMatrix);
|
||||
template <typename IdType>
|
||||
static std::vector<som::Position> getMatchingRefVectorsPosition(const std::vector<IdType>& ids, const ObjectPositions<IdType>& objectPositions);
|
||||
|
||||
template <typename IdType>
|
||||
std::vector<IdType> getSimilarObjects(const std::vector<IdType>& ids,
|
||||
const ObjectMatrix<IdType>& objectMatrix,
|
||||
const ObjectPositions<IdType>& objectPositions,
|
||||
std::size_t maxCount) const;
|
||||
template <typename IdType>
|
||||
static std::vector<IdType> getObjectsIds(const std::vector<som::Position>& positions, const ObjectMatrix<IdType>& objectsMatrix);
|
||||
|
||||
Database::Db& _db;
|
||||
bool _loadCancelled {};
|
||||
std::unique_ptr<SOM::Network> _network;
|
||||
double _networkRefVectorsDistanceMedian {};
|
||||
template <typename IdType>
|
||||
std::vector<IdType> getSimilarObjects(const std::vector<IdType>& ids,
|
||||
const ObjectMatrix<IdType>& objectMatrix,
|
||||
const ObjectPositions<IdType>& objectPositions,
|
||||
std::size_t maxCount) const;
|
||||
|
||||
ArtistPositions _artistPositions;
|
||||
std::unordered_map<Database::TrackArtistLinkType, ArtistMatrix> _artistMatrix;
|
||||
db::Db& _db;
|
||||
bool _loadCancelled{};
|
||||
std::unique_ptr<som::Network> _network;
|
||||
double _networkRefVectorsDistanceMedian{};
|
||||
|
||||
ReleasePositions _releasePositions;
|
||||
ReleaseMatrix _releaseMatrix;
|
||||
ArtistPositions _artistPositions;
|
||||
std::unordered_map<db::TrackArtistLinkType, ArtistMatrix> _artistMatrix;
|
||||
|
||||
TrackPositions _trackPositions;
|
||||
TrackMatrix _trackMatrix;
|
||||
};
|
||||
ReleasePositions _releasePositions;
|
||||
ReleaseMatrix _releaseMatrix;
|
||||
|
||||
template <typename IdType>
|
||||
std::vector<SOM::Position>
|
||||
FeaturesEngine::getMatchingRefVectorsPosition(const std::vector<IdType>& ids, const ObjectPositions<IdType>& objectPositions)
|
||||
{
|
||||
std::vector<SOM::Position> res;
|
||||
TrackPositions _trackPositions;
|
||||
TrackMatrix _trackMatrix;
|
||||
};
|
||||
|
||||
if (ids.empty())
|
||||
return res;
|
||||
template <typename IdType>
|
||||
std::vector<som::Position> FeaturesEngine::getMatchingRefVectorsPosition(const std::vector<IdType>& ids, const ObjectPositions<IdType>& objectPositions)
|
||||
{
|
||||
std::vector<som::Position> res;
|
||||
|
||||
for (const IdType id : ids)
|
||||
{
|
||||
auto it = objectPositions.find(id);
|
||||
if (it == objectPositions.end())
|
||||
continue;
|
||||
if (ids.empty())
|
||||
return res;
|
||||
|
||||
for (const SOM::Position& position : it->second)
|
||||
Utils::push_back_if_not_present(res, position);
|
||||
}
|
||||
for (const IdType id : ids)
|
||||
{
|
||||
auto it = objectPositions.find(id);
|
||||
if (it == objectPositions.end())
|
||||
continue;
|
||||
|
||||
return res;
|
||||
for (const som::Position& position : it->second)
|
||||
core::utils::push_back_if_not_present(res, position);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename IdType>
|
||||
std::vector<IdType> FeaturesEngine::getObjectsIds(const std::vector<som::Position>& positions, const ObjectMatrix<IdType>& objectMatrix)
|
||||
{
|
||||
std::vector<IdType> res;
|
||||
|
||||
for (const som::Position& position : positions)
|
||||
{
|
||||
for (const IdType id : objectMatrix.get(position))
|
||||
core::utils::push_back_if_not_present(res, id);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename IdType>
|
||||
std::vector<IdType> FeaturesEngine::getSimilarObjects(const std::vector<IdType>& ids,
|
||||
const ObjectMatrix<IdType>& objectMatrix,
|
||||
const ObjectPositions<IdType>& objectPositions,
|
||||
std::size_t maxCount) const
|
||||
{
|
||||
std::vector<IdType> res;
|
||||
|
||||
std::vector<som::Position> searchedRefVectorsPosition{ getMatchingRefVectorsPosition(ids, objectPositions) };
|
||||
if (searchedRefVectorsPosition.empty())
|
||||
return res;
|
||||
|
||||
while (1)
|
||||
{
|
||||
std::vector<IdType> closestObjectIds{ getObjectsIds(searchedRefVectorsPosition, objectMatrix) };
|
||||
|
||||
// Remove objects that are already in input or already reported
|
||||
closestObjectIds.erase(std::remove_if(std::begin(closestObjectIds), std::end(closestObjectIds),
|
||||
[&](IdType id)
|
||||
{
|
||||
return std::find(std::cbegin(ids), std::cend(ids), id) != std::cend(ids);
|
||||
})
|
||||
, std::end(closestObjectIds));
|
||||
|
||||
for (IdType id : closestObjectIds)
|
||||
{
|
||||
if (res.size() == maxCount)
|
||||
break;
|
||||
|
||||
core::utils::push_back_if_not_present(res, id);
|
||||
}
|
||||
|
||||
if (res.size() == maxCount)
|
||||
break;
|
||||
|
||||
// If there is not enough objects, try again with closest neighbour until there is too much distance
|
||||
const std::optional<som::Position> closestRefVectorPosition{ _network->getClosestRefVectorPosition(searchedRefVectorsPosition, _networkRefVectorsDistanceMedian * 0.75) };
|
||||
if (!closestRefVectorPosition)
|
||||
break;
|
||||
|
||||
core::utils::push_back_if_not_present(searchedRefVectorsPosition, closestRefVectorPosition.value());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IdType>
|
||||
std::vector<IdType>
|
||||
FeaturesEngine::getObjectsIds(const std::vector<SOM::Position>& positions, const ObjectMatrix<IdType>& objectMatrix)
|
||||
{
|
||||
std::vector<IdType> res;
|
||||
|
||||
for (const SOM::Position& position : positions)
|
||||
{
|
||||
for (const IdType id : objectMatrix.get(position))
|
||||
Utils::push_back_if_not_present(res, id);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename IdType>
|
||||
std::vector<IdType>
|
||||
FeaturesEngine::getSimilarObjects(const std::vector<IdType>& ids,
|
||||
const ObjectMatrix<IdType>& objectMatrix,
|
||||
const ObjectPositions<IdType>& objectPositions,
|
||||
std::size_t maxCount) const
|
||||
{
|
||||
std::vector<IdType> res;
|
||||
|
||||
std::vector<SOM::Position> searchedRefVectorsPosition {getMatchingRefVectorsPosition(ids, objectPositions)};
|
||||
if (searchedRefVectorsPosition.empty())
|
||||
return res;
|
||||
|
||||
while (1)
|
||||
{
|
||||
std::vector<IdType> closestObjectIds {getObjectsIds(searchedRefVectorsPosition, objectMatrix)};
|
||||
|
||||
// Remove objects that are already in input or already reported
|
||||
closestObjectIds.erase(std::remove_if(std::begin(closestObjectIds), std::end(closestObjectIds),
|
||||
[&](IdType id)
|
||||
{
|
||||
return std::find(std::cbegin(ids), std::cend(ids), id) != std::cend(ids);
|
||||
})
|
||||
, std::end(closestObjectIds));
|
||||
|
||||
for (IdType id : closestObjectIds)
|
||||
{
|
||||
if (res.size() == maxCount)
|
||||
break;
|
||||
|
||||
Utils::push_back_if_not_present(res, id);
|
||||
}
|
||||
|
||||
if (res.size() == maxCount)
|
||||
break;
|
||||
|
||||
// If there is not enough objects, try again with closest neighbour until there is too much distance
|
||||
const std::optional<SOM::Position> closestRefVectorPosition {_network->getClosestRefVectorPosition(searchedRefVectorsPosition, _networkRefVectorsDistanceMedian * 0.75)};
|
||||
if (!closestRefVectorPosition)
|
||||
break;
|
||||
|
||||
Utils::push_back_if_not_present(searchedRefVectorsPosition, closestRefVectorPosition.value());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // ns Recommendation
|
||||
|
||||
@@ -22,17 +22,17 @@
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Service.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::filesystem::path getCacheDirectory()
|
||||
{
|
||||
return Service<IConfig>::get()->getPath("working-dir") / "cache" / "features";
|
||||
return core::Service<core::IConfig>::get()->getPath("working-dir") / "cache" / "features";
|
||||
}
|
||||
|
||||
std::filesystem::path getCacheNetworkFilePath()
|
||||
@@ -45,7 +45,7 @@ namespace Recommendation
|
||||
return getCacheDirectory() / "track_positions";
|
||||
}
|
||||
|
||||
bool networkToCacheFile(const SOM::Network& network, std::filesystem::path path)
|
||||
bool networkToCacheFile(const som::Network& network, std::filesystem::path path)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -55,12 +55,12 @@ namespace Recommendation
|
||||
root.put("height", network.getHeight());
|
||||
root.put("dim_count", network.getInputDimCount());
|
||||
|
||||
for (SOM::InputVector::value_type weight : network.getDataWeights())
|
||||
for (som::InputVector::value_type weight : network.getDataWeights())
|
||||
root.add("weights.weight", weight);
|
||||
|
||||
for (SOM::Coordinate x = 0; x < network.getWidth(); ++x)
|
||||
for (som::Coordinate x = 0; x < network.getWidth(); ++x)
|
||||
{
|
||||
for (SOM::Coordinate y = 0; y < network.getWidth(); ++y)
|
||||
for (som::Coordinate y = 0; y < network.getWidth(); ++y)
|
||||
{
|
||||
const auto& refVector = network.getRefVector({ x, y });
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Recommendation
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<SOM::Network> FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path)
|
||||
std::optional<som::Network> FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path)
|
||||
{
|
||||
if (!std::filesystem::exists(path))
|
||||
return std::nullopt;
|
||||
@@ -101,14 +101,14 @@ namespace Recommendation
|
||||
|
||||
boost::property_tree::read_xml(path.string(), root);
|
||||
|
||||
SOM::Coordinate width{ root.get<SOM::Coordinate>("width") };
|
||||
SOM::Coordinate height{ root.get<SOM::Coordinate>("height") };
|
||||
som::Coordinate width{ root.get<som::Coordinate>("width") };
|
||||
som::Coordinate height{ root.get<som::Coordinate>("height") };
|
||||
std::size_t dimCount{ root.get<std::size_t>("dim_count") };
|
||||
|
||||
SOM::Network res{ width, height, dimCount };
|
||||
som::Network res{ width, height, dimCount };
|
||||
|
||||
{
|
||||
SOM::InputVector weights{ dimCount };
|
||||
som::InputVector weights{ dimCount };
|
||||
std::size_t i{};
|
||||
for (const auto& val : root.get_child("weights"))
|
||||
weights[i++] = val.second.get_value<double>();
|
||||
@@ -118,13 +118,13 @@ namespace Recommendation
|
||||
|
||||
for (const auto& node : root.get_child("ref_vectors"))
|
||||
{
|
||||
SOM::Coordinate x{ node.second.get<SOM::Coordinate>("coord_x") };
|
||||
SOM::Coordinate y{ node.second.get<SOM::Coordinate>("coord_y") };
|
||||
som::Coordinate x{ node.second.get<som::Coordinate>("coord_x") };
|
||||
som::Coordinate y{ node.second.get<som::Coordinate>("coord_y") };
|
||||
|
||||
SOM::InputVector refVector{ dimCount };
|
||||
som::InputVector refVector{ dimCount };
|
||||
std::size_t i{};
|
||||
for (const auto& val : node.second.get_child("values"))
|
||||
refVector[i++] = val.second.get_value<SOM::InputVector::value_type>();
|
||||
refVector[i++] = val.second.get_value<som::InputVector::value_type>();
|
||||
|
||||
res.setRefVector({ x, y }, refVector);
|
||||
}
|
||||
@@ -152,7 +152,7 @@ namespace Recommendation
|
||||
|
||||
node.put("id", id.getValue());
|
||||
|
||||
for (const SOM::Position& position : positions)
|
||||
for (const som::Position& position : positions)
|
||||
{
|
||||
boost::property_tree::ptree positionNode;
|
||||
positionNode.put("x", position.x);
|
||||
@@ -188,11 +188,11 @@ namespace Recommendation
|
||||
|
||||
for (const auto& object : root.get_child("objects"))
|
||||
{
|
||||
const Database::TrackId id{ object.second.get<Database::IdType::ValueType>("id") };
|
||||
const db::TrackId id{ object.second.get<db::IdType::ValueType>("id") };
|
||||
for (const auto& position : object.second.get_child("position"))
|
||||
{
|
||||
auto x = position.second.get<SOM::Coordinate>("x");
|
||||
auto y = position.second.get<SOM::Coordinate>("y");
|
||||
auto x = position.second.get<som::Coordinate>("x");
|
||||
auto y = position.second.get<som::Coordinate>("y");
|
||||
|
||||
res[id].push_back({ x, y });
|
||||
}
|
||||
@@ -230,7 +230,7 @@ namespace Recommendation
|
||||
|
||||
void FeaturesEngineCache::write() const
|
||||
{
|
||||
std::filesystem::create_directories(Service<IConfig>::get()->getPath("working-dir") / "cache" / "features");
|
||||
std::filesystem::create_directories(core::Service<core::IConfig>::get()->getPath("working-dir") / "cache" / "features");
|
||||
|
||||
if (!networkToCacheFile(_network, getCacheNetworkFilePath())
|
||||
|| !objectPositionToCacheFile(_trackPositions, getCacheTrackPositionsFilePath()))
|
||||
@@ -239,10 +239,10 @@ namespace Recommendation
|
||||
}
|
||||
}
|
||||
|
||||
FeaturesEngineCache::FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions)
|
||||
FeaturesEngineCache::FeaturesEngineCache(som::Network network, TrackPositions trackPositions)
|
||||
: _network{ std::move(network) },
|
||||
_trackPositions{ std::move(trackPositions) }
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
@@ -25,29 +25,30 @@
|
||||
#include "database/TrackId.hpp"
|
||||
#include "som/Network.hpp"
|
||||
|
||||
namespace Recommendation {
|
||||
|
||||
class FeaturesEngineCache
|
||||
namespace lms::recommendation
|
||||
{
|
||||
public:
|
||||
static void invalidate();
|
||||
|
||||
static std::optional<FeaturesEngineCache> read();
|
||||
void write() const;
|
||||
class FeaturesEngineCache
|
||||
{
|
||||
public:
|
||||
static void invalidate();
|
||||
|
||||
private:
|
||||
using TrackPositions = std::unordered_map<Database::TrackId, std::vector<SOM::Position>>;
|
||||
static std::optional<FeaturesEngineCache> read();
|
||||
void write() const;
|
||||
|
||||
FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions);
|
||||
private:
|
||||
using TrackPositions = std::unordered_map<db::TrackId, std::vector<som::Position>>;
|
||||
|
||||
static std::optional<SOM::Network> createNetworkFromCacheFile(const std::filesystem::path& path);
|
||||
static std::optional<TrackPositions> createObjectPositionsFromCacheFile(const std::filesystem::path& path);
|
||||
static bool objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path);
|
||||
FeaturesEngineCache(som::Network network, TrackPositions trackPositions);
|
||||
|
||||
friend class FeaturesEngine;
|
||||
static std::optional<som::Network> createNetworkFromCacheFile(const std::filesystem::path& path);
|
||||
static std::optional<TrackPositions> createObjectPositionsFromCacheFile(const std::filesystem::path& path);
|
||||
static bool objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path);
|
||||
|
||||
SOM::Network _network;
|
||||
TrackPositions _trackPositions;
|
||||
};
|
||||
friend class FeaturesEngine;
|
||||
|
||||
} // namespace Recommendation
|
||||
som::Network _network;
|
||||
TrackPositions _trackPositions;
|
||||
};
|
||||
|
||||
} // namespace lms::recommendation
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Recommendation::PlaylistGeneratorConstraint
|
||||
namespace lms::recommendation::PlaylistGeneratorConstraint
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -44,12 +44,12 @@ namespace Recommendation::PlaylistGeneratorConstraint
|
||||
}
|
||||
}
|
||||
|
||||
ConsecutiveArtists::ConsecutiveArtists(Database::Db& db)
|
||||
ConsecutiveArtists::ConsecutiveArtists(db::Db& db)
|
||||
: _db {db}
|
||||
{}
|
||||
|
||||
float
|
||||
ConsecutiveArtists::computeScore(const std::vector<Database::TrackId>& trackIds, std::size_t trackIndex)
|
||||
ConsecutiveArtists::computeScore(const std::vector<db::TrackId>& trackIds, std::size_t trackIndex)
|
||||
{
|
||||
assert(!trackIds.empty());
|
||||
assert(trackIndex <= trackIds.size() - 1);
|
||||
@@ -73,9 +73,9 @@ namespace Recommendation::PlaylistGeneratorConstraint
|
||||
}
|
||||
|
||||
ArtistContainer
|
||||
ConsecutiveArtists::getArtists(Database::TrackId trackId)
|
||||
ConsecutiveArtists::getArtists(db::TrackId trackId)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
ArtistContainer res;
|
||||
|
||||
@@ -93,5 +93,5 @@ namespace Recommendation::PlaylistGeneratorConstraint
|
||||
}
|
||||
|
||||
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
|
||||
@@ -23,23 +23,23 @@
|
||||
|
||||
#include "database/ReleaseId.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Recommendation::PlaylistGeneratorConstraint
|
||||
namespace lms::recommendation::PlaylistGeneratorConstraint
|
||||
{
|
||||
class ConsecutiveArtists : public IConstraint
|
||||
{
|
||||
public:
|
||||
ConsecutiveArtists(Database::Db& db);
|
||||
ConsecutiveArtists(db::Db& db);
|
||||
|
||||
private:
|
||||
float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) override;
|
||||
ArtistContainer getArtists(Database::TrackId trackId);
|
||||
ArtistContainer getArtists(db::TrackId trackId);
|
||||
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
};
|
||||
} // namespace Recommendation::PlaylistGeneratorConstraint
|
||||
} // namespace lms::recommendation::PlaylistGeneratorConstraint
|
||||
|
||||
|
||||
@@ -23,21 +23,21 @@
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Recommendation::PlaylistGeneratorConstraint
|
||||
namespace lms::recommendation::PlaylistGeneratorConstraint
|
||||
{
|
||||
ConsecutiveReleases::ConsecutiveReleases(Database::Db& db)
|
||||
ConsecutiveReleases::ConsecutiveReleases(db::Db& db)
|
||||
: _db {db}
|
||||
{}
|
||||
|
||||
float
|
||||
ConsecutiveReleases::computeScore(const std::vector<Database::TrackId>& trackIds, std::size_t trackIndex)
|
||||
ConsecutiveReleases::computeScore(const std::vector<db::TrackId>& trackIds, std::size_t trackIndex)
|
||||
{
|
||||
assert(!trackIds.empty());
|
||||
assert(trackIndex <= trackIds.size() - 1);
|
||||
|
||||
const Database::ReleaseId releaseId {getReleaseId(trackIds[trackIndex])};
|
||||
const db::ReleaseId releaseId {getReleaseId(trackIds[trackIndex])};
|
||||
|
||||
constexpr std::size_t rangeSize{ 3 }; // check up to rangeSize tracks before/after the target track
|
||||
static_assert(rangeSize > 0);
|
||||
@@ -55,10 +55,10 @@ namespace Recommendation::PlaylistGeneratorConstraint
|
||||
return score;
|
||||
}
|
||||
|
||||
Database::ReleaseId
|
||||
ConsecutiveReleases::getReleaseId(Database::TrackId trackId)
|
||||
db::ReleaseId
|
||||
ConsecutiveReleases::getReleaseId(db::TrackId trackId)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
Session& dbSession {_db.getTLSSession()};
|
||||
auto transaction {dbSession.createReadTransaction()};
|
||||
@@ -73,5 +73,5 @@ namespace Recommendation::PlaylistGeneratorConstraint
|
||||
|
||||
return release->getId();
|
||||
}
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
|
||||
@@ -23,24 +23,24 @@
|
||||
|
||||
#include "database/ReleaseId.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Recommendation::PlaylistGeneratorConstraint
|
||||
namespace lms::recommendation::PlaylistGeneratorConstraint
|
||||
{
|
||||
class ConsecutiveReleases : public IConstraint
|
||||
{
|
||||
public:
|
||||
ConsecutiveReleases(Database::Db& db);
|
||||
ConsecutiveReleases(db::Db& db);
|
||||
|
||||
private:
|
||||
float computeScore(const std::vector<Database::TrackId>& trackIds, std::size_t trackIndex) override;
|
||||
float computeScore(const std::vector<db::TrackId>& trackIds, std::size_t trackIndex) override;
|
||||
|
||||
Database::ReleaseId getReleaseId(Database::TrackId trackId);
|
||||
db::ReleaseId getReleaseId(db::TrackId trackId);
|
||||
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
};
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Recommendation::PlaylistGeneratorConstraint
|
||||
namespace lms::recommendation::PlaylistGeneratorConstraint
|
||||
{
|
||||
float
|
||||
DuplicateTracks::computeScore(const std::vector<Database::TrackId>& trackIds, std::size_t trackIndex)
|
||||
DuplicateTracks::computeScore(const std::vector<db::TrackId>& trackIds, std::size_t trackIndex)
|
||||
{
|
||||
const auto count {std::count(std::cbegin(trackIds), std::cend(trackIds), trackIds[trackIndex])};
|
||||
return count == 1 ? 0 : 1000;
|
||||
}
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
|
||||
#include "IConstraint.hpp"
|
||||
|
||||
namespace Recommendation::PlaylistGeneratorConstraint
|
||||
namespace lms::recommendation::PlaylistGeneratorConstraint
|
||||
{
|
||||
class DuplicateTracks : public IConstraint
|
||||
{
|
||||
private:
|
||||
float computeScore(const std::vector<Database::TrackId>& trackIds, std::size_t trackIndex) override;
|
||||
float computeScore(const std::vector<db::TrackId>& trackIds, std::size_t trackIndex) override;
|
||||
};
|
||||
} // namespace Recommendation::PlaylistGeneratorConstraints
|
||||
} // namespace lms::recommendation::PlaylistGeneratorConstraints
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "services/recommendation/Types.hpp"
|
||||
|
||||
namespace Recommendation::PlaylistGeneratorConstraint
|
||||
namespace lms::recommendation::PlaylistGeneratorConstraint
|
||||
{
|
||||
class IConstraint
|
||||
{
|
||||
@@ -36,4 +36,4 @@ namespace Recommendation::PlaylistGeneratorConstraint
|
||||
// > 1 : violation
|
||||
virtual float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) = 0;
|
||||
};
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
+4
-4
@@ -24,12 +24,12 @@
|
||||
#include "database/Types.hpp"
|
||||
#include "services/recommendation/Types.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
class IRecommendationService;
|
||||
class IPlaylistGeneratorService
|
||||
@@ -38,9 +38,9 @@ namespace Recommendation
|
||||
virtual ~IPlaylistGeneratorService() = default;
|
||||
|
||||
// extend an existing playlist with similar tracks (but use playlist contraints)
|
||||
virtual TrackContainer extendPlaylist(Database::TrackListId tracklistId, std::size_t maxCount) const = 0;
|
||||
virtual TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IPlaylistGeneratorService> createPlaylistGeneratorService(Database::Db& db, IRecommendationService& recommandationService);
|
||||
std::unique_ptr<IPlaylistGeneratorService> createPlaylistGeneratorService(db::Db& db, IRecommendationService& recommandationService);
|
||||
} // ns Recommendation
|
||||
|
||||
|
||||
+8
-8
@@ -21,17 +21,17 @@
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "utils/EnumSet.hpp"
|
||||
#include "core/EnumSet.hpp"
|
||||
#include "database/TrackListId.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "services/recommendation/Types.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
class IRecommendationService
|
||||
{
|
||||
@@ -40,12 +40,12 @@ namespace Recommendation
|
||||
|
||||
virtual void load() = 0;
|
||||
|
||||
virtual TrackContainer findSimilarTracks(Database::TrackListId tracklistId, std::size_t maxCount) const = 0;
|
||||
virtual TrackContainer findSimilarTracks(const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const = 0;
|
||||
virtual ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const = 0;
|
||||
virtual ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const = 0;
|
||||
virtual TrackContainer findSimilarTracks(db::TrackListId tracklistId, std::size_t maxCount) const = 0;
|
||||
virtual TrackContainer findSimilarTracks(const std::vector<db::TrackId>& tracksId, std::size_t maxCount) const = 0;
|
||||
virtual ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const = 0;
|
||||
virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet<db::TrackArtistLinkType> linkTypes, std::size_t maxCount) const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IRecommendationService> createRecommendationService(Database::Db& db);
|
||||
std::unique_ptr<IRecommendationService> createRecommendationService(db::Db& db);
|
||||
} // ns Recommendation
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
|
||||
namespace Recommendation
|
||||
namespace lms::recommendation
|
||||
{
|
||||
struct Progress
|
||||
{
|
||||
@@ -17,8 +17,8 @@ namespace Recommendation
|
||||
template <typename IdType>
|
||||
using ResultContainer = std::vector<IdType>;
|
||||
|
||||
using ArtistContainer = ResultContainer<Database::ArtistId>;
|
||||
using ReleaseContainer = ResultContainer<Database::ReleaseId>;
|
||||
using TrackContainer = ResultContainer<Database::TrackId>;
|
||||
using ArtistContainer = ResultContainer<db::ArtistId>;
|
||||
using ReleaseContainer = ResultContainer<db::ReleaseId>;
|
||||
using TrackContainer = ResultContainer<db::TrackId>;
|
||||
|
||||
} // namespace Recommendation
|
||||
} // namespace lms::recommendation
|
||||
|
||||
@@ -21,7 +21,7 @@ target_link_libraries(lmsscanner PRIVATE
|
||||
lmsdatabase
|
||||
lmsmetadata
|
||||
lmsrecommendation
|
||||
lmsutils
|
||||
lmscore
|
||||
)
|
||||
|
||||
target_link_libraries(lmsscanner PUBLIC
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "services/scanner/ScannerStats.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
class IScanStep
|
||||
{
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
#include "IScanStep.hpp"
|
||||
#include "ScannerSettings.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
class ScanStepBase : public IScanStep
|
||||
{
|
||||
@@ -43,7 +43,7 @@ namespace Scanner
|
||||
const ScannerSettings& settings;
|
||||
ProgressCallback progressCallback;
|
||||
bool& abortScan;
|
||||
Database::Db& db;
|
||||
db::Db& db;
|
||||
};
|
||||
ScanStepBase(InitParams& initParams)
|
||||
: _settings {initParams.settings}
|
||||
@@ -56,6 +56,6 @@ namespace Scanner
|
||||
const ScannerSettings& _settings;
|
||||
ProgressCallback _progressCallback;
|
||||
bool& _abortScan;
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
void ScanStepCheckDuplicatedDbFiles::process(ScanContext& context)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
if (_abortScan)
|
||||
return;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "ScanStepBase.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
class ScanStepCheckDuplicatedDbFiles : public ScanStepBase
|
||||
{
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Path.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
void ScanStepComputeClusterStats::process(ScanContext& context)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
if (context.stats.nbChanges() == 0)
|
||||
return;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "ScanStepBase.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
class ScanStepComputeClusterStats : public ScanStepBase
|
||||
{
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
|
||||
#include "ScanStepDiscoverFiles.hpp"
|
||||
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Path.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
void ScanStepDiscoverFiles::process(ScanContext& context)
|
||||
{
|
||||
@@ -31,12 +31,12 @@ namespace Scanner
|
||||
for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries)
|
||||
{
|
||||
std::size_t currentDirectoryProcessElemsCount{};
|
||||
PathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path)
|
||||
core::pathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path)
|
||||
{
|
||||
if (_abortScan)
|
||||
return false;
|
||||
|
||||
if (!ec && PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
|
||||
if (!ec && core::pathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
|
||||
{
|
||||
context.currentStepStats.processedElems++;
|
||||
currentDirectoryProcessElemsCount++;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "ScanStepBase.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
class ScanStepDiscoverFiles : public ScanStepBase
|
||||
{
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Path.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -81,7 +81,7 @@ namespace Scanner
|
||||
|
||||
void ScanStepRemoveOrphanDbFiles::removeOrphanTracks(ScanContext& context)
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
if (_abortScan)
|
||||
return;
|
||||
@@ -150,25 +150,25 @@ namespace Scanner
|
||||
void ScanStepRemoveOrphanDbFiles::removeOrphanClusters()
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Checking orphan clusters...");
|
||||
removeOrphanEntries<Database::Cluster>(_db.getTLSSession(), _abortScan);
|
||||
removeOrphanEntries<db::Cluster>(_db.getTLSSession(), _abortScan);
|
||||
}
|
||||
|
||||
void ScanStepRemoveOrphanDbFiles::removeOrphanClusterTypes()
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Checking orphan cluster types...");
|
||||
removeOrphanEntries<Database::ClusterType>(_db.getTLSSession(), _abortScan);
|
||||
removeOrphanEntries<db::ClusterType>(_db.getTLSSession(), _abortScan);
|
||||
}
|
||||
|
||||
void ScanStepRemoveOrphanDbFiles::removeOrphanArtists()
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Checking orphan artists...");
|
||||
removeOrphanEntries<Database::Artist>(_db.getTLSSession(), _abortScan);
|
||||
removeOrphanEntries<db::Artist>(_db.getTLSSession(), _abortScan);
|
||||
}
|
||||
|
||||
void ScanStepRemoveOrphanDbFiles::removeOrphanReleases()
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Checking orphan releases...");
|
||||
removeOrphanEntries<Database::Release>(_db.getTLSSession(), _abortScan);
|
||||
removeOrphanEntries<db::Release>(_db.getTLSSession(), _abortScan);
|
||||
}
|
||||
|
||||
bool ScanStepRemoveOrphanDbFiles::checkFile(const std::filesystem::path& p)
|
||||
@@ -186,14 +186,14 @@ namespace Scanner
|
||||
if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries),
|
||||
[&](const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
{
|
||||
return PathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName);
|
||||
return core::pathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName);
|
||||
}))
|
||||
{
|
||||
LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': out of media directory");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PathUtils::hasFileAnyExtension(p, _settings.supportedExtensions))
|
||||
if (!core::pathUtils::hasFileAnyExtension(p, _settings.supportedExtensions))
|
||||
{
|
||||
LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': file format no longer handled");
|
||||
return false;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "ScanStepBase.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
class ScanStepRemoveOrphanDbFiles : public ScanStepBase
|
||||
{
|
||||
|
||||
@@ -30,19 +30,19 @@
|
||||
#include "database/TrackArtistLink.hpp"
|
||||
#include "metadata/Exception.hpp"
|
||||
#include "metadata/IParser.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "utils/ITraceLogger.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Path.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
namespace
|
||||
{
|
||||
Artist::pointer createArtist(Session& session, const MetaData::Artist& artistInfo)
|
||||
Artist::pointer createArtist(Session& session, const metadata::Artist& artistInfo)
|
||||
{
|
||||
Artist::pointer artist{ session.create<Artist>(artistInfo.name) };
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Scanner
|
||||
return artist;
|
||||
}
|
||||
|
||||
void updateArtistIfNeeded(Artist::pointer artist, const MetaData::Artist& artistInfo)
|
||||
void updateArtistIfNeeded(Artist::pointer artist, const metadata::Artist& artistInfo)
|
||||
{
|
||||
// Name may have been updated
|
||||
if (artist->getName() != artistInfo.name)
|
||||
@@ -69,11 +69,11 @@ namespace Scanner
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Artist::pointer> getOrCreateArtists(Session& session, const std::vector<MetaData::Artist>& artistsInfo, bool allowFallbackOnMBIDEntries)
|
||||
std::vector<Artist::pointer> getOrCreateArtists(Session& session, const std::vector<metadata::Artist>& artistsInfo, bool allowFallbackOnMBIDEntries)
|
||||
{
|
||||
std::vector<Artist::pointer> artists;
|
||||
|
||||
for (const MetaData::Artist& artistInfo : artistsInfo)
|
||||
for (const metadata::Artist& artistInfo : artistsInfo)
|
||||
{
|
||||
Artist::pointer artist;
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Scanner
|
||||
return releaseType;
|
||||
}
|
||||
|
||||
void updateReleaseIfNeeded(Session& session, Release::pointer release, const MetaData::Release& releaseInfo)
|
||||
void updateReleaseIfNeeded(Session& session, Release::pointer release, const metadata::Release& releaseInfo)
|
||||
{
|
||||
if (release->getName() != releaseInfo.name)
|
||||
release.modify()->setName(releaseInfo.name);
|
||||
@@ -146,7 +146,7 @@ namespace Scanner
|
||||
}
|
||||
}
|
||||
|
||||
Release::pointer getOrCreateRelease(Session& session, const MetaData::Release& releaseInfo, const std::filesystem::path& expectedReleaseDirectory)
|
||||
Release::pointer getOrCreateRelease(Session& session, const metadata::Release& releaseInfo, const std::filesystem::path& expectedReleaseDirectory)
|
||||
{
|
||||
Release::pointer release;
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace Scanner
|
||||
return Release::pointer{};
|
||||
}
|
||||
|
||||
std::vector<Cluster::pointer> getOrCreateClusters(Session& session, const MetaData::Track& track)
|
||||
std::vector<Cluster::pointer> getOrCreateClusters(Session& session, const metadata::Track& track)
|
||||
{
|
||||
std::vector<Cluster::pointer> clusters;
|
||||
|
||||
@@ -217,23 +217,23 @@ namespace Scanner
|
||||
return clusters;
|
||||
}
|
||||
|
||||
MetaData::ParserReadStyle getParserReadStyle()
|
||||
metadata::ParserReadStyle getParserReadStyle()
|
||||
{
|
||||
std::string_view readStyle{ Service<IConfig>::get()->getString("scanner-parser-read-style", "average") };
|
||||
std::string_view readStyle{ core::Service<core::IConfig>::get()->getString("scanner-parser-read-style", "average") };
|
||||
|
||||
if (readStyle == "fast")
|
||||
return MetaData::ParserReadStyle::Fast;
|
||||
return metadata::ParserReadStyle::Fast;
|
||||
else if (readStyle == "average")
|
||||
return MetaData::ParserReadStyle::Average;
|
||||
return metadata::ParserReadStyle::Average;
|
||||
else if (readStyle == "accurate")
|
||||
return MetaData::ParserReadStyle::Accurate;
|
||||
return metadata::ParserReadStyle::Accurate;
|
||||
|
||||
throw LmsException{ "Invalid value for 'scanner-parser-read-style'" };
|
||||
throw core::LmsException{ "Invalid value for 'scanner-parser-read-style'" };
|
||||
}
|
||||
|
||||
std::size_t getScanMetaDataThreadCount()
|
||||
{
|
||||
std::size_t threadCount{ Service<IConfig>::get()->getULong("scanner-metadata-thread-count", 0) };
|
||||
std::size_t threadCount{ core::Service<core::IConfig>::get()->getULong("scanner-metadata-thread-count", 0) };
|
||||
|
||||
if (threadCount == 0)
|
||||
threadCount = std::max<std::size_t>(std::thread::hardware_concurrency() / 2, 1);
|
||||
@@ -242,7 +242,7 @@ namespace Scanner
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ScanStepScanFiles::MetadataScanQueue::MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount, bool& abort)
|
||||
ScanStepScanFiles::MetadataScanQueue::MetadataScanQueue(metadata::IParser& parser, std::size_t threadCount, bool& abort)
|
||||
: _metadataParser{ parser }
|
||||
, _scanContextRunner{ _scanContext, threadCount, "ScannerMetadata" }
|
||||
, _abort{ abort }
|
||||
@@ -259,7 +259,7 @@ namespace Scanner
|
||||
{
|
||||
LMS_SCOPED_TRACE_OVERVIEW("Scanner", "AudioFileParseJob");
|
||||
|
||||
std::unique_ptr<MetaData::Track> track;
|
||||
std::unique_ptr<metadata::Track> track;
|
||||
|
||||
if (_abort)
|
||||
{
|
||||
@@ -272,7 +272,7 @@ namespace Scanner
|
||||
{
|
||||
track = _metadataParser.parse(path);
|
||||
}
|
||||
catch (const MetaData::Exception& e)
|
||||
catch (const metadata::Exception& e)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, INFO, "Failed to parse '" << path.string() << "'");
|
||||
}
|
||||
@@ -323,7 +323,7 @@ namespace Scanner
|
||||
|
||||
ScanStepScanFiles::ScanStepScanFiles(InitParams& initParams)
|
||||
: ScanStepBase{ initParams }
|
||||
, _metadataParser{ MetaData::createParser(MetaData::ParserBackend::TagLib, getParserReadStyle()) } // For now, always use TagLib
|
||||
, _metadataParser{ metadata::createParser(metadata::ParserBackend::TagLib, getParserReadStyle()) } // For now, always use TagLib
|
||||
, _metadataScanQueue{ *_metadataParser, getScanMetaDataThreadCount(), _abortScan }
|
||||
{
|
||||
LMS_LOG(DBUPDATER, INFO, "Using " << _metadataScanQueue.getThreadCount() << " thread(s) for scanning file metadata");
|
||||
@@ -347,7 +347,7 @@ namespace Scanner
|
||||
|
||||
for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries)
|
||||
{
|
||||
PathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path)
|
||||
core::pathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path)
|
||||
{
|
||||
LMS_SCOPED_TRACE_DETAILED("Scanner", "OnExploreFile");
|
||||
|
||||
@@ -359,7 +359,7 @@ namespace Scanner
|
||||
LMS_LOG(DBUPDATER, ERROR, "Cannot process entry '" << path.string() << "': " << ec.message());
|
||||
context.stats.errors.emplace_back(ScanError{ path, ScanErrorType::CannotReadFile, ec.message() });
|
||||
}
|
||||
else if (PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
|
||||
else if (core::pathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
|
||||
{
|
||||
if (checkFileNeedScan(context, path, mediaLibrary))
|
||||
_metadataScanQueue.pushScanRequest(path);
|
||||
@@ -392,9 +392,9 @@ namespace Scanner
|
||||
Wt::WDateTime lastWriteTime;
|
||||
try
|
||||
{
|
||||
lastWriteTime = PathUtils::getLastWriteTime(file);
|
||||
lastWriteTime = core::pathUtils::getLastWriteTime(file);
|
||||
}
|
||||
catch (LmsException& e)
|
||||
catch (core::LmsException& e)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, ERROR, e.what());
|
||||
stats.skips++;
|
||||
@@ -405,7 +405,7 @@ namespace Scanner
|
||||
if (!context.forceScan)
|
||||
{
|
||||
// Skip file if last write is the same
|
||||
Database::Session& dbSession{ _db.getTLSSession() };
|
||||
db::Session& dbSession{ _db.getTLSSession() };
|
||||
auto transaction{ _db.getTLSSession().createReadTransaction() };
|
||||
|
||||
const Track::pointer track{ Track::findByPath(dbSession, file) };
|
||||
@@ -429,12 +429,12 @@ namespace Scanner
|
||||
|
||||
if (needUpdateLibrary)
|
||||
{
|
||||
Database::Session& dbSession{ _db.getTLSSession() };
|
||||
db::Session& dbSession{ _db.getTLSSession() };
|
||||
auto transaction{ _db.getTLSSession().createWriteTransaction() };
|
||||
|
||||
Track::pointer track{ Track::findByPath(dbSession, file) };
|
||||
assert(track);
|
||||
track.modify()->setMediaLibrary(Database::MediaLibrary::find(dbSession, libraryInfo.id)); // may be null, will be handled in the next scan anyway
|
||||
track.modify()->setMediaLibrary(db::MediaLibrary::find(dbSession, libraryInfo.id)); // may be null, will be handled in the next scan anyway
|
||||
stats.updates++;
|
||||
return false;
|
||||
}
|
||||
@@ -446,7 +446,7 @@ namespace Scanner
|
||||
{
|
||||
LMS_SCOPED_TRACE_OVERVIEW("Scanner", "ProcessScanResults");
|
||||
|
||||
Database::Session& dbSession{ _db.getTLSSession() };
|
||||
db::Session& dbSession{ _db.getTLSSession() };
|
||||
auto transaction{ dbSession.createWriteTransaction() };
|
||||
|
||||
for (const MetaDataScanResult& scanResult : scanResults)
|
||||
@@ -471,22 +471,22 @@ namespace Scanner
|
||||
}
|
||||
}
|
||||
|
||||
void ScanStepScanFiles::processFileMetaData(ScanContext& context, const std::filesystem::path& file, const MetaData::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
void ScanStepScanFiles::processFileMetaData(ScanContext& context, const std::filesystem::path& file, const metadata::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
{
|
||||
ScanStats& stats{ context.stats };
|
||||
Wt::WDateTime lastWriteTime;
|
||||
try
|
||||
{
|
||||
lastWriteTime = PathUtils::getLastWriteTime(file);
|
||||
lastWriteTime = core::pathUtils::getLastWriteTime(file);
|
||||
}
|
||||
catch (LmsException& e)
|
||||
catch (core::LmsException& e)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, ERROR, e.what());
|
||||
stats.skips++;
|
||||
return;
|
||||
}
|
||||
|
||||
Database::Session& dbSession{ _db.getTLSSession() };
|
||||
db::Session& dbSession{ _db.getTLSSession() };
|
||||
Track::pointer track{ Track::findByPath(dbSession, file) };
|
||||
|
||||
if (trackMetadata.mbid && (!track || _settings.skipDuplicateMBID))
|
||||
@@ -519,7 +519,7 @@ namespace Scanner
|
||||
if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries),
|
||||
[&](const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
{
|
||||
return PathUtils::isPathInRootPath(file, libraryInfo.rootDirectory, &excludeDirFileName);
|
||||
return core::pathUtils::isPathInRootPath(file, libraryInfo.rootDirectory, &excludeDirFileName);
|
||||
}))
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "metadata/IParser.hpp"
|
||||
#include "utils/IOContextRunner.hpp"
|
||||
#include "core/IOContextRunner.hpp"
|
||||
#include "ScanStepBase.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
class ScanStepScanFiles : public ScanStepBase
|
||||
{
|
||||
@@ -47,18 +47,18 @@ namespace Scanner
|
||||
struct MetaDataScanResult
|
||||
{
|
||||
std::filesystem::path path;
|
||||
std::unique_ptr<MetaData::Track> trackMetaData;
|
||||
std::unique_ptr<metadata::Track> trackMetaData;
|
||||
};
|
||||
void processMetaDataScanResults(ScanContext& context, std::span<const MetaDataScanResult> scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo);
|
||||
void processFileMetaData(ScanContext& context, const std::filesystem::path& file, const MetaData::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo);
|
||||
void processFileMetaData(ScanContext& context, const std::filesystem::path& file, const metadata::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo);
|
||||
|
||||
std::unique_ptr<MetaData::IParser> _metadataParser;
|
||||
std::unique_ptr<metadata::IParser> _metadataParser;
|
||||
const std::vector<std::string> _extraTagsToParse;
|
||||
|
||||
class MetadataScanQueue
|
||||
{
|
||||
public:
|
||||
MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount, bool& abort);
|
||||
MetadataScanQueue(metadata::IParser& parser, std::size_t threadCount, bool& abort);
|
||||
|
||||
std::size_t getThreadCount() const { return _scanContextRunner.getThreadCount(); }
|
||||
|
||||
@@ -70,9 +70,9 @@ namespace Scanner
|
||||
void wait(std::size_t maxScanRequestCount = 0); // wait until ongoing scan request count <= maxScanRequestCount
|
||||
|
||||
private:
|
||||
MetaData::IParser& _metadataParser;
|
||||
metadata::IParser& _metadataParser;
|
||||
boost::asio::io_context _scanContext;
|
||||
IOContextRunner _scanContextRunner;
|
||||
core::IOContextRunner _scanContextRunner;
|
||||
|
||||
mutable std::mutex _mutex ;
|
||||
std::size_t _ongoingScanCount{};
|
||||
|
||||
@@ -25,11 +25,10 @@
|
||||
#include "database/MediaLibrary.hpp"
|
||||
#include "database/TrackFeatures.hpp"
|
||||
#include "database/ScanSettings.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "utils/Tuple.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Path.hpp"
|
||||
|
||||
#include "ScanStepCheckDuplicatedDbFiles.hpp"
|
||||
#include "ScanStepDiscoverFiles.hpp"
|
||||
@@ -37,9 +36,9 @@
|
||||
#include "ScanStepScanFiles.hpp"
|
||||
#include "ScanStepComputeClusterStats.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -357,7 +356,7 @@ namespace Scanner
|
||||
{
|
||||
ScannerSettings newSettings;
|
||||
|
||||
newSettings.skipDuplicateMBID = Service<IConfig>::get()->getBool("scanner-skip-duplicate-mbid", false);
|
||||
newSettings.skipDuplicateMBID = core::Service<core::IConfig>::get()->getBool("scanner-skip-duplicate-mbid", false);
|
||||
{
|
||||
auto transaction{ _dbSession.createReadTransaction() };
|
||||
|
||||
@@ -371,7 +370,7 @@ namespace Scanner
|
||||
const auto fileExtensions{ scanSettings->getAudioFileExtensions() };
|
||||
newSettings.supportedExtensions.reserve(fileExtensions.size());
|
||||
std::transform(std::cbegin(fileExtensions), std::end(fileExtensions), std::back_inserter(newSettings.supportedExtensions),
|
||||
[](const std::filesystem::path& extension) { return std::filesystem::path{ StringUtils::stringToLower(extension.string()) }; });
|
||||
[](const std::filesystem::path& extension) { return std::filesystem::path{ core::stringUtils::stringToLower(extension.string()) }; });
|
||||
}
|
||||
|
||||
MediaLibrary::find(_dbSession, [&](const MediaLibrary::pointer& mediaLibrary)
|
||||
@@ -411,4 +410,4 @@ namespace Scanner
|
||||
notifyInProgress(stepStats);
|
||||
}
|
||||
|
||||
} // namespace Scanner
|
||||
} // namespace lms::scanner
|
||||
|
||||
@@ -34,16 +34,16 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "services/scanner/IScannerService.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "core/Path.hpp"
|
||||
#include "IScanStep.hpp"
|
||||
#include "ScannerSettings.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
class ScannerService : public IScannerService
|
||||
{
|
||||
public:
|
||||
ScannerService(Database::Db& db);
|
||||
ScannerService(db::Db& db);
|
||||
~ScannerService();
|
||||
|
||||
ScannerService(const ScannerService&) = delete;
|
||||
@@ -88,8 +88,8 @@ namespace Scanner
|
||||
boost::asio::system_timer _scheduleTimer{ _ioService };
|
||||
Events _events;
|
||||
std::chrono::system_clock::time_point _lastScanInProgressEmit{};
|
||||
Database::Db& _db;
|
||||
Database::Session _dbSession;
|
||||
db::Db& _db;
|
||||
db::Session _dbSession;
|
||||
|
||||
mutable std::shared_mutex _statusMutex;
|
||||
State _curState{ State::NotScheduled };
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
#include "database/MediaLibraryId.hpp"
|
||||
#include "database/ScanSettings.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
struct ScannerSettings
|
||||
{
|
||||
std::size_t scanVersion{};
|
||||
Wt::WTime startTime;
|
||||
Database::ScanSettings::UpdatePeriod updatePeriod{ Database::ScanSettings::UpdatePeriod::Never };
|
||||
db::ScanSettings::UpdatePeriod updatePeriod{ db::ScanSettings::UpdatePeriod::Never };
|
||||
std::vector<std::filesystem::path> supportedExtensions;
|
||||
bool skipDuplicateMBID{};
|
||||
std::vector<std::string> extraTags;
|
||||
@@ -41,7 +41,7 @@ namespace Scanner
|
||||
|
||||
struct MediaLibraryInfo
|
||||
{
|
||||
Database::MediaLibraryId id;
|
||||
db::MediaLibraryId id;
|
||||
std::filesystem::path rootDirectory;
|
||||
|
||||
bool operator<=>(const MediaLibraryInfo& other) const = default;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "services/scanner/ScannerStats.hpp"
|
||||
|
||||
namespace Scanner {
|
||||
namespace lms::scanner {
|
||||
|
||||
ScanError::ScanError(const std::filesystem::path& _file, ScanErrorType _error, const std::string& _systemError)
|
||||
: file {_file},
|
||||
@@ -46,5 +46,5 @@ ScanStepStats::progress() const
|
||||
return (processedElems / static_cast<float>(totalElems ? totalElems : 1)) * 100;
|
||||
}
|
||||
|
||||
} // namespace Scanner
|
||||
} // namespace lms::scanner
|
||||
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
#include "ScannerEvents.hpp"
|
||||
#include "ScannerStats.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
|
||||
class IScannerService
|
||||
@@ -62,7 +62,7 @@ namespace Scanner
|
||||
virtual Events& getEvents() = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IScannerService> createScannerService(Database::Db& db);
|
||||
std::unique_ptr<IScannerService> createScannerService(db::Db& db);
|
||||
|
||||
} // Scanner
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "ScannerStats.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
|
||||
struct Events
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#include "database/TrackId.hpp"
|
||||
|
||||
namespace Scanner
|
||||
namespace lms::scanner
|
||||
{
|
||||
enum class ScanErrorType
|
||||
{
|
||||
@@ -53,7 +53,7 @@ namespace Scanner
|
||||
|
||||
struct ScanDuplicate
|
||||
{
|
||||
Database::TrackId trackId;
|
||||
db::TrackId trackId;
|
||||
DuplicateReason reason;
|
||||
};
|
||||
|
||||
@@ -104,5 +104,5 @@ namespace Scanner
|
||||
std::size_t nbFiles() const;
|
||||
std::size_t nbChanges() const;
|
||||
};
|
||||
} // namespace Scanner
|
||||
} // namespace lms::scanner
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ target_include_directories(lmsscrobbling PRIVATE
|
||||
)
|
||||
|
||||
target_link_libraries(lmsscrobbling PRIVATE
|
||||
lmsutils
|
||||
lmscore
|
||||
)
|
||||
|
||||
target_link_libraries(lmsscrobbling PUBLIC
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
|
||||
#include "services/scrobbling/Listen.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
class TrackList;
|
||||
class User;
|
||||
}
|
||||
|
||||
namespace Scrobbling
|
||||
namespace lms::scrobbling
|
||||
{
|
||||
class IScrobblingBackend
|
||||
{
|
||||
|
||||
@@ -26,20 +26,20 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "internal/InternalBackend.hpp"
|
||||
#include "listenbrainz/ListenBrainzBackend.hpp"
|
||||
|
||||
namespace Scrobbling
|
||||
namespace lms::scrobbling
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
namespace
|
||||
{
|
||||
Database::Listen::StatsFindParameters convertToListenFindParameters(const ScrobblingService::FindParameters& params)
|
||||
db::Listen::StatsFindParameters convertToListenFindParameters(const ScrobblingService::FindParameters& params)
|
||||
{
|
||||
Database::Listen::StatsFindParameters listenFindParams;
|
||||
db::Listen::StatsFindParameters listenFindParams;
|
||||
listenFindParams.setUser(params.user);
|
||||
listenFindParams.setClusters(params.clusters);
|
||||
listenFindParams.setRange(params.range);
|
||||
@@ -49,9 +49,9 @@ namespace Scrobbling
|
||||
return listenFindParams;
|
||||
}
|
||||
|
||||
Database::Listen::ArtistStatsFindParameters convertToListenFindParameters(const ScrobblingService::ArtistFindParameters& params)
|
||||
db::Listen::ArtistStatsFindParameters convertToListenFindParameters(const ScrobblingService::ArtistFindParameters& params)
|
||||
{
|
||||
return Database::Listen::ArtistStatsFindParameters{ convertToListenFindParameters(static_cast<const ScrobblingService::FindParameters&>(params)), params.linkType };
|
||||
return db::Listen::ArtistStatsFindParameters{ convertToListenFindParameters(static_cast<const ScrobblingService::FindParameters&>(params)), params.linkType };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Scrobbling
|
||||
{
|
||||
LMS_LOG(SCROBBLING, INFO, "Starting service...");
|
||||
_scrobblingBackends.emplace(ScrobblingBackend::Internal, std::make_unique<InternalBackend>(_db));
|
||||
_scrobblingBackends.emplace(ScrobblingBackend::ListenBrainz, std::make_unique<ListenBrainz::ListenBrainzBackend>(ioContext, _db));
|
||||
_scrobblingBackends.emplace(ScrobblingBackend::ListenBrainz, std::make_unique<listenBrainz::ListenBrainzBackend>(ioContext, _db));
|
||||
LMS_LOG(SCROBBLING, INFO, "Service started!");
|
||||
}
|
||||
|
||||
@@ -112,13 +112,13 @@ namespace Scrobbling
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
db::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getRecentArtists(session, listenFindParams);
|
||||
res = db::Listen::getRecentArtists(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -130,13 +130,13 @@ namespace Scrobbling
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
db::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getRecentReleases(session, listenFindParams);
|
||||
res = db::Listen::getRecentReleases(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -148,31 +148,31 @@ namespace Scrobbling
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
db::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getRecentTracks(session, listenFindParams);
|
||||
res = db::Listen::getRecentTracks(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
std::size_t ScrobblingService::getCount(Database::UserId userId, Database::ReleaseId releaseId)
|
||||
std::size_t ScrobblingService::getCount(db::UserId userId, db::ReleaseId releaseId)
|
||||
{
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
return Database::Listen::getCount(session, userId, releaseId);
|
||||
return db::Listen::getCount(session, userId, releaseId);
|
||||
}
|
||||
|
||||
std::size_t ScrobblingService::getCount(Database::UserId userId, Database::TrackId trackId)
|
||||
std::size_t ScrobblingService::getCount(db::UserId userId, db::TrackId trackId)
|
||||
{
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
return Database::Listen::getCount(session, userId, trackId);
|
||||
return db::Listen::getCount(session, userId, trackId);
|
||||
}
|
||||
|
||||
Wt::WDateTime ScrobblingService::getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId)
|
||||
Wt::WDateTime ScrobblingService::getLastListenDateTime(db::UserId userId, db::ReleaseId releaseId)
|
||||
{
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
if (!backend)
|
||||
@@ -181,11 +181,11 @@ namespace Scrobbling
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const Database::Listen::pointer listen{ Database::Listen::getMostRecentListen(session, userId, *backend, releaseId) };
|
||||
const db::Listen::pointer listen{ db::Listen::getMostRecentListen(session, userId, *backend, releaseId) };
|
||||
return listen ? listen->getDateTime() : Wt::WDateTime{};
|
||||
}
|
||||
|
||||
Wt::WDateTime ScrobblingService::getLastListenDateTime(Database::UserId userId, Database::TrackId trackId)
|
||||
Wt::WDateTime ScrobblingService::getLastListenDateTime(db::UserId userId, db::TrackId trackId)
|
||||
{
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
if (!backend)
|
||||
@@ -194,7 +194,7 @@ namespace Scrobbling
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const Database::Listen::pointer listen{ Database::Listen::getMostRecentListen(session, userId, *backend, trackId) };
|
||||
const db::Listen::pointer listen{ db::Listen::getMostRecentListen(session, userId, *backend, trackId) };
|
||||
return listen ? listen->getDateTime() : Wt::WDateTime{};
|
||||
}
|
||||
|
||||
@@ -207,13 +207,13 @@ namespace Scrobbling
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
db::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getTopArtists(session, listenFindParams);
|
||||
res = db::Listen::getTopArtists(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -225,13 +225,13 @@ namespace Scrobbling
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
db::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getTopReleases(session, listenFindParams);
|
||||
res = db::Listen::getTopReleases(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -243,13 +243,13 @@ namespace Scrobbling
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
db::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getTopTracks(session, listenFindParams);
|
||||
res = db::Listen::getTopTracks(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
} // ns Scrobbling
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
#include "IScrobblingBackend.hpp"
|
||||
|
||||
namespace Scrobbling
|
||||
namespace lms::scrobbling
|
||||
{
|
||||
class ScrobblingService : public IScrobblingService
|
||||
{
|
||||
public:
|
||||
ScrobblingService(boost::asio::io_context& ioContext, Database::Db& db);
|
||||
ScrobblingService(boost::asio::io_context& ioContext, db::Db& db);
|
||||
~ScrobblingService();
|
||||
|
||||
private:
|
||||
@@ -43,20 +43,20 @@ namespace Scrobbling
|
||||
ReleaseContainer getRecentReleases(const FindParameters& params) override;
|
||||
TrackContainer getRecentTracks(const FindParameters& params) override;
|
||||
|
||||
std::size_t getCount(Database::UserId userId, Database::ReleaseId releaseId) override;
|
||||
std::size_t getCount(Database::UserId userId, Database::TrackId trackId) override;
|
||||
std::size_t getCount(db::UserId userId, db::ReleaseId releaseId) override;
|
||||
std::size_t getCount(db::UserId userId, db::TrackId trackId) override;
|
||||
|
||||
Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId) override;
|
||||
Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) override;
|
||||
Wt::WDateTime getLastListenDateTime(db::UserId userId, db::ReleaseId releaseId) override;
|
||||
Wt::WDateTime getLastListenDateTime(db::UserId userId, db::TrackId trackId) override;
|
||||
|
||||
ArtistContainer getTopArtists(const ArtistFindParameters& params) override;
|
||||
ReleaseContainer getTopReleases(const FindParameters& params) override;
|
||||
TrackContainer getTopTracks(const FindParameters& params) override;
|
||||
|
||||
std::optional<Database::ScrobblingBackend> getUserBackend(Database::UserId userId);
|
||||
std::optional<db::ScrobblingBackend> getUserBackend(db::UserId userId);
|
||||
|
||||
Database::Db& _db;
|
||||
std::unordered_map<Database::ScrobblingBackend, std::unique_ptr<IScrobblingBackend>> _scrobblingBackends;
|
||||
db::Db& _db;
|
||||
std::unordered_map<db::ScrobblingBackend, std::unique_ptr<IScrobblingBackend>> _scrobblingBackends;
|
||||
};
|
||||
|
||||
} // ns Scrobbling
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
namespace Scrobbling
|
||||
namespace lms::scrobbling
|
||||
{
|
||||
InternalBackend::InternalBackend(Database::Db& db)
|
||||
InternalBackend::InternalBackend(db::Db& db)
|
||||
: _db{ db }
|
||||
{}
|
||||
|
||||
@@ -47,22 +47,22 @@ namespace Scrobbling
|
||||
|
||||
void InternalBackend::addTimedListen(const TimedListen& listen)
|
||||
{
|
||||
Database::Session& session{ _db.getTLSSession() };
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
if (Database::Listen::find(session, listen.userId, listen.trackId, Database::ScrobblingBackend::Internal, listen.listenedAt))
|
||||
if (db::Listen::find(session, listen.userId, listen.trackId, db::ScrobblingBackend::Internal, listen.listenedAt))
|
||||
return;
|
||||
|
||||
const Database::User::pointer user{ Database::User::find(session, listen.userId) };
|
||||
const db::User::pointer user{ db::User::find(session, listen.userId) };
|
||||
if (!user)
|
||||
return;
|
||||
|
||||
const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) };
|
||||
const db::Track::pointer track{ db::Track::find(session, listen.trackId) };
|
||||
if (!track)
|
||||
return;
|
||||
|
||||
auto dbListen{ session.create<Database::Listen>(user, track, Database::ScrobblingBackend::Internal, listen.listenedAt) };
|
||||
dbListen.modify()->setSyncState(Database::SyncState::Synchronized);
|
||||
auto dbListen{ session.create<db::Listen>(user, track, db::ScrobblingBackend::Internal, listen.listenedAt) };
|
||||
dbListen.modify()->setSyncState(db::SyncState::Synchronized);
|
||||
}
|
||||
} // Scrobbling
|
||||
|
||||
|
||||
@@ -21,24 +21,24 @@
|
||||
|
||||
#include "IScrobblingBackend.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Scrobbling
|
||||
namespace lms::scrobbling
|
||||
{
|
||||
class InternalBackend final : public IScrobblingBackend
|
||||
{
|
||||
public:
|
||||
InternalBackend(Database::Db& db);
|
||||
InternalBackend(db::Db& db);
|
||||
|
||||
private:
|
||||
void listenStarted(const Listen& listen) override;
|
||||
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
|
||||
void addTimedListen(const TimedListen& listen) override;
|
||||
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
};
|
||||
} // Scrobbling
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
#include "services/scrobbling/Exception.hpp"
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
namespace lms::scrobbling::listenBrainz
|
||||
{
|
||||
class Exception : public Scrobbling::Exception
|
||||
class Exception : public scrobbling::Exception
|
||||
{
|
||||
public:
|
||||
using Scrobbling::Exception::Exception;
|
||||
using scrobbling::Exception::Exception;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,15 +22,15 @@
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/http/IClient.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Service.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/http/IClient.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
namespace lms::scrobbling::listenBrainz
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace db;
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -53,8 +53,8 @@ namespace Scrobbling::ListenBrainz
|
||||
ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, Db& db)
|
||||
: _ioContext{ ioContext }
|
||||
, _db{ db }
|
||||
, _baseAPIUrl{ Service<IConfig>::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") }
|
||||
, _client{ Http::createClient(_ioContext, _baseAPIUrl) }
|
||||
, _baseAPIUrl{ core::Service<core::IConfig>::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") }
|
||||
, _client{ core::http::createClient(_ioContext, _baseAPIUrl) }
|
||||
, _listensSynchronizer{ _ioContext, db, *_client }
|
||||
{
|
||||
LOG(INFO, "Starting ListenBrainz backend... API endpoint = '" << _baseAPIUrl << "'");
|
||||
@@ -83,5 +83,5 @@ namespace Scrobbling::ListenBrainz
|
||||
{
|
||||
_listensSynchronizer.enqueListen(timedListen);
|
||||
}
|
||||
} // namespace Scrobbling::ListenBrainz
|
||||
} // namespace lms::scrobbling::listenBrainz
|
||||
|
||||
|
||||
@@ -26,17 +26,17 @@
|
||||
#include "IScrobblingBackend.hpp"
|
||||
#include "ListensSynchronizer.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Db;
|
||||
}
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
namespace lms::scrobbling::listenBrainz
|
||||
{
|
||||
class ListenBrainzBackend final : public IScrobblingBackend
|
||||
{
|
||||
public:
|
||||
ListenBrainzBackend(boost::asio::io_context& ioContext, Database::Db& db);
|
||||
ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db);
|
||||
~ListenBrainzBackend() override;
|
||||
|
||||
private:
|
||||
@@ -51,10 +51,10 @@ namespace Scrobbling::ListenBrainz
|
||||
void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint);
|
||||
|
||||
boost::asio::io_context& _ioContext;
|
||||
Database::Db& _db;
|
||||
db::Db& _db;
|
||||
std::string _baseAPIUrl;
|
||||
std::unique_ptr<Http::IClient> _client;
|
||||
std::unique_ptr<core::http::IClient> _client;
|
||||
ListensSynchronizer _listensSynchronizer;
|
||||
};
|
||||
} // Scrobbling::ListenBrainz
|
||||
} // scrobbling::ListenBrainz
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "ListenTypes.hpp"
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
namespace lms::scrobbling::listenBrainz
|
||||
{
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const Listen& listen)
|
||||
@@ -38,4 +38,4 @@ namespace Scrobbling::ListenBrainz
|
||||
|
||||
return os;
|
||||
}
|
||||
} // Scrobbling::ListenBrainz
|
||||
} // scrobbling::ListenBrainz
|
||||
|
||||
@@ -23,21 +23,21 @@
|
||||
#include <ostream>
|
||||
#include <Wt/WDateTime.h>
|
||||
|
||||
#include "utils/UUID.hpp"
|
||||
#include "core/UUID.hpp"
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
namespace lms::scrobbling::listenBrainz
|
||||
{
|
||||
struct Listen
|
||||
{
|
||||
std::string trackName;
|
||||
std::string releaseName;
|
||||
std::string artistName;
|
||||
std::optional<UUID> recordingMBID;
|
||||
std::optional<UUID> trackMBID;
|
||||
std::optional<UUID> releaseMBID;
|
||||
std::optional<core::UUID> recordingMBID;
|
||||
std::optional<core::UUID> trackMBID;
|
||||
std::optional<core::UUID> releaseMBID;
|
||||
std::optional<unsigned> trackNumber;
|
||||
Wt::WDateTime listenedAt;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Listen& listen);
|
||||
} // Scrobbling::ListenBrainz
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user