Made database ID manipulations safer

This commit is contained in:
emeric
2021-09-20 23:53:38 +02:00
parent 598f01069e
commit 441aed622c
138 changed files with 2164 additions and 2054 deletions
+3
View File
@@ -6,6 +6,9 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
if (UNIX)
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
endif ()
include(CTest)
find_package(PkgConfig REQUIRED)
+3 -3
View File
@@ -25,7 +25,7 @@
namespace Auth
{
Database::IdType
Database::UserId
AuthServiceBase::getOrCreateUser(Database::Session& session, std::string_view loginName)
{
auto transaction {session.createUniqueTransaction()};
@@ -41,11 +41,11 @@ namespace Auth
user.modify()->setType(type);
}
return user.id();
return user->getId();
}
void
AuthServiceBase::onUserAuthenticated(Database::Session& session, Database::IdType userId)
AuthServiceBase::onUserAuthenticated(Database::Session& session, Database::UserId userId)
{
auto transaction {session.createUniqueTransaction()};
Database::User::pointer user {Database::User::getById(session, userId)};
+2 -2
View File
@@ -32,7 +32,7 @@ namespace Auth
class AuthServiceBase
{
protected:
Database::IdType getOrCreateUser(Database::Session& session, std::string_view loginName);
void onUserAuthenticated(Database::Session& session, Database::IdType userId);
Database::UserId getOrCreateUser(Database::Session& session, std::string_view loginName);
void onUserAuthenticated(Database::Session& session, Database::UserId userId);
};
}
+3 -3
View File
@@ -45,7 +45,7 @@ namespace Auth
}
std::string
AuthTokenService::createAuthToken(Database::Session& session, Database::IdType userId, const Wt::WDateTime& expiry)
AuthTokenService::createAuthToken(Database::Session& session, Database::UserId userId, const Wt::WDateTime& expiry)
{
const std::string secret {Wt::WRandom::generateId(32)};
const std::string secretHash {sha1Function.compute(secret, {})};
@@ -86,7 +86,7 @@ namespace Auth
LMS_LOG(UI, DEBUG) << "Found auth token for user '" << authToken->getUser()->getLoginName() << "'!";
AuthTokenService::AuthTokenProcessResult::AuthTokenInfo res {authToken->getUser().id(), authToken->getExpiry()};
AuthTokenService::AuthTokenProcessResult::AuthTokenInfo res {authToken->getUser()->getId(), authToken->getExpiry()};
authToken.remove();
return res;
@@ -123,7 +123,7 @@ namespace Auth
}
void
AuthTokenService::clearAuthTokens(Database::Session& session, Database::IdType userId)
AuthTokenService::clearAuthTokens(Database::Session& session, Database::UserId userId)
{
auto transaction {session.createUniqueTransaction()};
+2 -2
View File
@@ -44,8 +44,8 @@ namespace Auth
private:
AuthTokenProcessResult processAuthToken(Database::Session& session, const boost::asio::ip::address& clientAddress, std::string_view tokenValue) override;
std::string createAuthToken(Database::Session& session, Database::IdType userId, const Wt::WDateTime& expiry) override;
void clearAuthTokens(Database::Session& session, Database::IdType userId) override;
std::string createAuthToken(Database::Session& session, Database::UserId userId, const Wt::WDateTime& expiry) override;
void clearAuthTokens(Database::Session& session, Database::UserId userId) override;
std::shared_mutex _mutex;
LoginThrottler _loginThrottler;
+1 -1
View File
@@ -84,7 +84,7 @@ namespace Auth
{
_loginThrottler.onGoodClientAttempt(clientAddress);
const Database::IdType userId {getOrCreateUser(session, loginName)};
const Database::UserId userId {getOrCreateUser(session, loginName)};
onUserAuthenticated(session, userId);
return {CheckResult::State::Granted, userId};
}
@@ -43,7 +43,7 @@ namespace Auth
LMS_LOG(AUTH, DEBUG) << "Extracted login name = '" << loginName << "' from HTTP header";
const Database::IdType userId {getOrCreateUser(session, loginName)};
const Database::UserId userId {getOrCreateUser(session, loginName)};
onUserAuthenticated(session, userId);
return {CheckResult::State::Granted, userId};
}
@@ -57,7 +57,7 @@ namespace Auth
LMS_LOG(AUTH, DEBUG) << "Extracted login name = '" << loginName << "' from HTTP header";
const Database::IdType userId {getOrCreateUser(session, loginName)};
const Database::UserId userId {getOrCreateUser(session, loginName)};
onUserAuthenticated(session, userId);
return {CheckResult::State::Granted, userId};
}
@@ -96,13 +96,13 @@ namespace Auth
}
void
InternalPasswordService::setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword)
InternalPasswordService::setPassword(Database::Session& session, Database::UserId userId, std::string_view newPassword)
{
const Database::User::PasswordHash passwordHash {hashPassword(newPassword)};
auto transaction {session.createUniqueTransaction()};
const Database::User::pointer user {Database::User::getById(session, userId)};
Database::User::pointer user {Database::User::getById(session, userId)};
if (!user)
throw Exception {"User not found!"};
@@ -42,7 +42,7 @@ namespace Auth
bool canSetPasswords() const override;
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) override;
void setPassword(Database::Session& session, Database::UserId userId, std::string_view newPassword) override;
Database::User::PasswordHash hashPassword(std::string_view password) const;
void hashRandomPassword() const;
@@ -193,7 +193,7 @@ namespace Auth
}
void
PAMPasswordService::setPassword(Database::Session&, Database::IdType, std::string_view)
PAMPasswordService::setPassword(Database::Session&, Database::UserId, std::string_view)
{
throw NotImplementedException {};
}
@@ -38,7 +38,7 @@ namespace Auth
bool canSetPasswords() const override;
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
void setPassword(Database::Session& session,
Database::IdType userId,
Database::UserId userId,
std::string_view newPassword) override;
};
}
@@ -54,7 +54,7 @@ namespace Auth
struct AuthTokenInfo
{
Database::IdType userId;
Database::UserId userId;
Wt::WDateTime expiry;
};
@@ -66,8 +66,8 @@ namespace Auth
virtual AuthTokenProcessResult processAuthToken(Database::Session& session, const boost::asio::ip::address& clientAddress, std::string_view tokenValue) = 0;
// Returns a one time token
virtual std::string createAuthToken(Database::Session& session, Database::IdType userid, const Wt::WDateTime& expiry) = 0;
virtual void clearAuthTokens(Database::Session& session, Database::IdType userid) = 0;
virtual std::string createAuthToken(Database::Session& session, Database::UserId userid, const Wt::WDateTime& expiry) = 0;
virtual void clearAuthTokens(Database::Session& session, Database::UserId userid) = 0;
};
std::unique_ptr<IAuthTokenService> createAuthTokenService(std::size_t maxThrottlerEntryCount);
+1 -1
View File
@@ -57,7 +57,7 @@ namespace Auth
};
State state {State::Denied};
std::optional<Database::IdType> userId {};
std::optional<Database::UserId> userId {};
};
virtual CheckResult processEnv(Database::Session& session, const Wt::WEnvironment& env) = 0;
@@ -53,7 +53,7 @@ namespace Auth
Throttled,
};
State state {State::Denied};
std::optional<Database::IdType> userId {};
std::optional<Database::UserId> userId {};
std::optional<Wt::WDateTime> expiry {};
};
virtual CheckResult checkUserPassword(Database::Session& session,
@@ -70,7 +70,7 @@ namespace Auth
MustMatchLoginName,
};
virtual PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const = 0;
virtual void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) = 0;
virtual void setPassword(Database::Session& session, Database::UserId userId, std::string_view newPassword) = 0;
};
std::unique_ptr<IPasswordService> createPasswordService(std::string_view authPasswordBackend, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService);
+11 -15
View File
@@ -45,11 +45,11 @@ namespace
bool hasCover {};
bool isMultiDisc {};
std::filesystem::path trackPath;
std::optional<Database::IdType> releaseId;
std::optional<Database::ReleaseId> releaseId;
};
std::optional<TrackInfo>
getTrackInfo(Database::Session& dbSession, Database::IdType trackId)
getTrackInfo(Database::Session& dbSession, Database::TrackId trackId)
{
std::optional<TrackInfo> res;
@@ -66,7 +66,7 @@ namespace
if (const Database::Release::pointer& release {track->getRelease()})
{
res->releaseId = release.id();
res->releaseId = release->getId();
if (release->getTotalDisc() > 1)
res->isMultiDisc = true;
}
@@ -75,7 +75,6 @@ namespace
}
}
namespace CoverArt {
static
@@ -101,7 +100,7 @@ Grabber::Grabber(const std::filesystem::path& execPath,
: _defaultCoverPath {defaultCoverPath}
, _maxCacheSize {maxCacheSize}
, _maxFileSize {maxFileSize}
, _jpegQuality {clamp<unsigned>(jpegQuality, 1, 100)}
, _jpegQuality {Utils::clamp<unsigned>(jpegQuality, 1, 100)}
{
LMS_LOG(COVER, INFO) << "Default cover path = '" << _defaultCoverPath.string() << "'";
LMS_LOG(COVER, INFO) << "Max cache size = " << _maxCacheSize;
@@ -314,20 +313,17 @@ Grabber::getFromTrack(const std::filesystem::path& p, ImageSize width) const
}
std::shared_ptr<IEncodedImage>
Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width)
Grabber::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width)
{
return getFromTrack(dbSession, trackId, width, true /* allow release fallback*/);
}
std::shared_ptr<IEncodedImage>
Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width, bool allowReleaseFallback)
Grabber::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback)
{
using namespace Database;
const CacheEntryDesc cacheEntryDesc {CacheEntryDesc::Type::Track, trackId, width};
const CacheEntryDesc cacheEntryDesc {trackId, width};
std::shared_ptr<IEncodedImage> cover {loadFromCache(cacheEntryDesc)};
if (cover)
@@ -361,9 +357,9 @@ Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, Im
}
std::shared_ptr<IEncodedImage>
Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId, ImageSize width)
Grabber::getFromRelease(Database::Session& session, Database::ReleaseId releaseId, ImageSize width)
{
const CacheEntryDesc cacheEntryDesc {CacheEntryDesc::Type::Release, releaseId, width};
const CacheEntryDesc cacheEntryDesc {releaseId, width};
std::shared_ptr<IEncodedImage> cover {loadFromCache(cacheEntryDesc)};
if (cover)
@@ -371,7 +367,7 @@ Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId,
struct ReleaseInfo
{
Database::IdType firstTrackId;
Database::TrackId firstTrackId;
std::filesystem::path releaseDirectory;
};
@@ -386,7 +382,7 @@ Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId,
if (const auto firstTrack {release->getFirstTrack()})
{
res = ReleaseInfo {};
res->firstTrackId = firstTrack.id();
res->firstTrackId = firstTrack->getId();
res->releaseDirectory = firstTrack->getPath().parent_path();
}
}
+12 -15
View File
@@ -26,6 +26,7 @@
#include <shared_mutex>
#include <string_view>
#include <unordered_map>
#include <variant>
#include <vector>
#include "cover/ICoverArtGrabber.hpp"
@@ -46,20 +47,12 @@ namespace CoverArt
{
struct CacheEntryDesc
{
enum class Type
{
Track,
Release,
};
Type type;
Database::IdType id;
std::variant<Database::TrackId, Database::ReleaseId> id;
std::size_t size;
bool operator==(const CacheEntryDesc& other) const
{
return type == other.type
&& id == other.id
return id == other.id
&& size == other.size;
}
};
@@ -75,8 +68,12 @@ namespace std
public:
size_t operator()(const CoverArt::CacheEntryDesc& e) const
{
size_t h = std::hash<int>()(static_cast<int>(e.type));
h ^= std::hash<Database::IdType>()(e.id) << 1;
size_t h {};
std::visit([&](auto id)
{
using IdType = std::decay_t<decltype(id)>;
h ^= std::hash<IdType>()(id);
}, e.id);
h ^= std::hash<std::size_t>()(e.size) << 1;
return h;
}
@@ -101,11 +98,11 @@ namespace CoverArt
Grabber& operator=(Grabber&&) = delete;
private:
std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width) override;
std::shared_ptr<IEncodedImage> getFromRelease(Database::Session& dbSession, Database::IdType releaseId, ImageSize width) override;
std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width) override;
std::shared_ptr<IEncodedImage> getFromRelease(Database::Session& dbSession, Database::ReleaseId releaseId, ImageSize width) override;
void flushCache() override;
std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width, bool allowReleaseFallback);
std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback);
std::unique_ptr<IEncodedImage> getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) const;
std::unique_ptr<IEncodedImage> getFromCoverFile(const std::filesystem::path& p, ImageSize width) const;
@@ -37,8 +37,8 @@ namespace CoverArt
public:
virtual ~IGrabber() = default;
virtual std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width) = 0;
virtual std::shared_ptr<IEncodedImage> getFromRelease(Database::Session& dbSession, Database::IdType releaseId, ImageSize width) = 0;
virtual std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width) = 0;
virtual std::shared_ptr<IEncodedImage> getFromRelease(Database::Session& dbSession, Database::ReleaseId releaseId, ImageSize width) = 0;
virtual void flushCache() = 0;
};
+74 -92
View File
@@ -28,6 +28,7 @@
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
#include "Traits.hpp"
namespace Database
{
@@ -37,7 +38,6 @@ Artist::Artist(const std::string& name, const std::optional<UUID>& MBID)
_sortName {_name},
_MBID {MBID ? MBID->getAsString() : ""}
{
}
std::vector<Artist::pointer>
@@ -45,7 +45,7 @@ Artist::getByName(Session& session, const std::string& name)
{
session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> res = session.getDboSession().find<Artist>()
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session.getDboSession().find<Artist>()
.where("name = ?").bind(std::string {name, 0, _maxNameLength})
.orderBy("LENGTH(mbid) DESC"); // put mbid entries first
@@ -56,14 +56,14 @@ Artist::pointer
Artist::getByMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
return session.getDboSession().find<Artist>().where("mbid = ?").bind(std::string {mbid.getAsString()});
return session.getDboSession().find<Artist>().where("mbid = ?").bind(std::string {mbid.getAsString()}).resultValue();
}
Artist::pointer
Artist::getById(Session& session, IdType id)
Artist::getById(Session& session, ArtistId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Artist>().where("id = ?").bind(id);
return session.getDboSession().find<Artist>().where("id = ?").bind(id).resultValue();
}
Artist::pointer
@@ -82,7 +82,7 @@ static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<TrackArtistLinkType> linkType)
{
@@ -125,7 +125,7 @@ createQuery(Session& session,
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
for (const ClusterId clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
@@ -145,7 +145,7 @@ Artist::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Artist>();
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session.getDboSession().find<Artist>();
return std::vector<pointer>(res.begin(), res.end());
}
@@ -167,7 +167,7 @@ Artist::getAll(Session& session, SortMethod sortMethod)
break;
}
Wt::Dbo::collection<pointer> res = query;
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = query;
return std::vector<pointer>(res.begin(), res.end());
}
@@ -177,7 +177,7 @@ Artist::getAll(Session& session, SortMethod sortMethod, std::optional<Range> ran
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT a FROM Artist a", {}, {}, std::nullopt)};
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT a FROM Artist a", {}, {}, std::nullopt)};
switch (sortMethod)
{
@@ -191,11 +191,11 @@ Artist::getAll(Session& session, SortMethod sortMethod, std::optional<Range> ran
break;
}
Wt::Dbo::collection<Artist::pointer> collection = query
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<Artist::pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -207,27 +207,27 @@ Artist::getAll(Session& session, SortMethod sortMethod, std::optional<Range> ran
return res;
}
std::vector<IdType>
std::vector<ArtistId>
Artist::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM artist");
return std::vector<IdType>(res.begin(), res.end());
Wt::Dbo::collection<ArtistId> res = session.getDboSession().query<ArtistId>("SELECT id FROM artist");
return std::vector<ArtistId>(res.begin(), res.end());
}
std::vector<IdType>
Artist::getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size)
std::vector<ArtistId>
Artist::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT DISTINCT a.id from artist a", clusters, {}, linkType)};
auto query {createQuery<ArtistId>(session, "SELECT DISTINCT a.id from artist a", clusters, {}, linkType)};
Wt::Dbo::collection<IdType> res = query
Wt::Dbo::collection<ArtistId> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ArtistId>(res.begin(), res.end());
}
@@ -240,22 +240,22 @@ Artist::getAllOrphans(Session& session)
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<ArtistId>
Artist::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
Wt::Dbo::collection<ArtistId> res = session.getDboSession().query<ArtistId>
("SELECT DISTINCT a.id FROM artist a"
" INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ArtistId>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getByClusters(Session& session, const std::set<IdType>& clusters, SortMethod sortMethod)
Artist::getByClusters(Session& session, const std::vector<ClusterId>& clusters, SortMethod sortMethod)
{
assert(!clusters.empty());
@@ -266,7 +266,7 @@ Artist::getByClusters(Session& session, const std::set<IdType>& clusters, SortMe
std::vector<Artist::pointer>
Artist::getByFilter(Session& session,
const std::set<IdType>& clusters,
const std::vector<ClusterId>& clusters,
const std::vector<std::string_view>& keywords,
std::optional<TrackArtistLinkType> linkType,
SortMethod sortMethod,
@@ -275,7 +275,7 @@ Artist::getByFilter(Session& session,
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT DISTINCT a from artist a", clusters, keywords, linkType)};
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, keywords, linkType)};
switch (sortMethod)
{
case Artist::SortMethod::None:
@@ -288,11 +288,11 @@ Artist::getByFilter(Session& session,
break;
}
Wt::Dbo::collection<Artist::pointer> collection = query
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
@@ -308,23 +308,23 @@ Artist::getByFilter(Session& session,
std::vector<Artist::pointer>
Artist::getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusters,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
if (after)
query.where("t.file_last_write > ?").bind(*after);
Wt::Dbo::collection<Artist::pointer> collection = query
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.orderBy("t.file_last_write DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
@@ -340,14 +340,14 @@ Artist::getLastWritten(Session& session,
std::vector<Artist::pointer>
Artist::getStarred(Session& session,
User::pointer user,
const std::set<IdType>& clusters,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType,
SortMethod sortMethod,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
{
std::ostringstream oss;
@@ -355,7 +355,7 @@ Artist::getStarred(Session& session,
" INNER JOIN user_artist_starred uas ON uas.artist_id = a.id"
" INNER JOIN user u ON u.id = uas.user_id WHERE u.id = ?)";
query.bind(user.id());
query.bind(user->getId());
query.where(oss.str());
}
@@ -371,12 +371,12 @@ Artist::getStarred(Session& session,
break;
}
Wt::Dbo::collection<Artist::pointer> collection = query
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.groupBy("a.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
@@ -389,11 +389,9 @@ Artist::getStarred(Session& session,
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Wt::Dbo::ptr<Release>>
Artist::getReleases(const std::set<IdType>& clusterIds) const
std::vector<Release::pointer>
Artist::getReleases(const std::vector<ClusterId>& clusterIds) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
WhereClause where;
@@ -409,12 +407,12 @@ Artist::getReleases(const std::set<IdType>& clusterIds) const
WhereClause clusterClause;
for (auto id : clusterIds)
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
where.And(clusterClause);
}
where.And(WhereClause("a.id = ?")).bind(std::to_string(id()));
where.And(WhereClause("a.id = ?")).bind(getId().toString());
oss << " " << where.get();
@@ -423,56 +421,48 @@ Artist::getReleases(const std::set<IdType>& clusterIds) const
oss << " ORDER BY t.year DESC, r.name COLLATE NOCASE";
Wt::Dbo::Query<Release::pointer> query = session()->query<Release::pointer>( oss.str() );
auto query {session()->query<Wt::Dbo::ptr<Release>>(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
Wt::Dbo::collection<Wt::Dbo::ptr<Release>> res = query;
return std::vector<Wt::Dbo::ptr<Release>>(res.begin(), res.end());
auto res {query.resultList()};
return std::vector<Release::pointer>(res.begin(), res.end());
}
std::size_t
Artist::getReleaseCount() const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
int res = session()->query<int>("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id INNER JOIN track t ON t.release_id = r.id")
.where("a.id = ?").bind(self()->id());
.where("a.id = ?").bind(getId());
return res;
}
std::vector<Wt::Dbo::ptr<Track>>
std::vector<Track::pointer>
Artist::getTracks(std::optional<TrackArtistLinkType> linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT DISTINCT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(self()->id())
.where("a.id = ?").bind(getId())
.orderBy("t.year DESC,t.release_id,t.disc_number,t.track_number")};
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {query.resultList()};
return std::vector<Wt::Dbo::ptr<Track>>(tracks.begin(), tracks.end());
auto tracks {query.resultList()};
return std::vector<Track::pointer>(tracks.begin(), tracks.end());
}
std::vector<Wt::Dbo::ptr<Track>>
std::vector<Track::pointer>
Artist::getNonReleaseTracks(std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(self()->id())
.where("a.id = ?").bind(getId())
.where("t.release_id is NULL")
.orderBy("t.name")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
@@ -481,9 +471,8 @@ Artist::getNonReleaseTracks(std::optional<TrackArtistLinkType> linkType, std::op
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
Wt::Dbo::collection<Track::pointer> tracks {query.resultList()};
auto res {std::vector<Track::pointer>(tracks.begin(), tracks.end())};
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {query.resultList()};
std::vector<Track::pointer> res(tracks.begin(), tracks.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -499,37 +488,32 @@ bool
Artist::hasNonReleaseTracks(std::optional<TrackArtistLinkType> linkType) const
{
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(self()->id())
.where("a.id = ?").bind(getId())
.where("t.release_id is NULL")
.orderBy("t.name")};
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
Wt::Dbo::collection<Track::pointer> tracks {query.resultList()};
return !tracks.empty();
return !query.resultList().empty();
}
std::vector<Wt::Dbo::ptr<Track>>
std::vector<Track::pointer>
Artist::getRandomTracks(std::optional<std::size_t> count) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {session()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(self()->id())
.where("a.id = ?").bind(getId())
.orderBy("RANDOM()")
.limit(count ? static_cast<int>(*count) : -1)};
return std::vector<Wt::Dbo::ptr<Track>>(tracks.begin(), tracks.end());
return std::vector<Track::pointer>(tracks.begin(), tracks.end());
}
std::vector<Wt::Dbo::ptr<Artist>>
std::vector<Artist::pointer>
Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::optional<Range> range) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
std::ostringstream oss;
@@ -563,9 +547,9 @@ Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::opt
oss << ")";
}
Wt::Dbo::Query<pointer> query {session()->query<pointer>(oss.str())
.bind(self()->id())
.bind(self()->id())
Wt::Dbo::Query<Wt::Dbo::ptr<Artist>> query {session()->query<Wt::Dbo::ptr<Artist>>(oss.str())
.bind(getId())
.bind(getId())
.groupBy("a.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(range ? static_cast<int>(range->limit) : -1)
@@ -574,15 +558,13 @@ Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::opt
for (TrackArtistLinkType type : artistLinkTypes)
query.bind(type);
Wt::Dbo::collection<pointer> res = query;
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res {query.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>>
std::vector<std::vector<Cluster::pointer>>
Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
WhereClause where;
@@ -590,34 +572,34 @@ Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::si
std::ostringstream oss;
oss << "SELECT c FROM cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN artist a ON t_a_l.artist_id = a.id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id";
where.And(WhereClause("a.id = ?")).bind(std::to_string(self()->id()));
where.And(WhereClause("a.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(std::to_string(clusterType.id()));
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
where.And(clusterClause);
}
oss << " " << where.get();
oss << "GROUP BY c.id ORDER BY COUNT(DISTINCT c.id) DESC";
Wt::Dbo::Query<Cluster::pointer> query = session()->query<Cluster::pointer>( oss.str() );
Wt::Dbo::Query<Wt::Dbo::ptr<Cluster>> query = session()->query<Wt::Dbo::ptr<Cluster>>( oss.str() );
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
Wt::Dbo::collection<Cluster::pointer> queryRes = query;
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> queryRes = query;
std::map<IdType, std::vector<Cluster::pointer>> clusters;
for (auto cluster : queryRes)
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clustersByType;
for (const Cluster::pointer& cluster : queryRes)
{
if (clusters[cluster->getType().id()].size() < size)
clusters[cluster->getType().id()].push_back(cluster);
if (clustersByType[cluster->getType()->getId()].size() < size)
clustersByType[cluster->getType()->getId()].push_back(cluster);
}
std::vector<std::vector<Cluster::pointer>> res;
for (auto cluster_list : clusters)
res.push_back(cluster_list.second);
for (const auto& [clusterTypeId, clusters] : clustersByType)
res.push_back(clusters);
return res;
}
+35 -47
View File
@@ -25,21 +25,18 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "SqlQuery.hpp"
#include "Traits.hpp"
namespace Database {
Cluster::Cluster()
{
}
Cluster::Cluster(Wt::Dbo::ptr<ClusterType> type, std::string_view name)
: _name(std::string {name, 0, _maxNameLength}),
_clusterType {type}
Cluster::Cluster(ObjectPtr<ClusterType> type, std::string_view name)
: _name {std::string {name, 0, _maxNameLength}},
_clusterType {getDboPtr(type)}
{
}
Cluster::pointer
Cluster::create(Session& session, Wt::Dbo::ptr<ClusterType> type, std::string_view name)
Cluster::create(Session& session, ObjectPtr<ClusterType> type, std::string_view name)
{
session.checkUniqueLocked();
@@ -54,8 +51,7 @@ Cluster::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Cluster::pointer> res {session.getDboSession().find<Cluster>()};
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> res {session.getDboSession().find<Cluster>()};
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
@@ -63,67 +59,61 @@ std::vector<Cluster::pointer>
Cluster::getAllOrphans(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Cluster::pointer> res {session.getDboSession().query<Cluster::pointer>("SELECT DISTINCT c FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)")};
auto res {session.getDboSession().query<Wt::Dbo::ptr<Cluster>>("SELECT DISTINCT c FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)").resultList()};
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
Cluster::pointer
Cluster::getById(Session& session, IdType id)
Cluster::getById(Session& session, ClusterId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Cluster>().where("id = ?").bind(id);
return session.getDboSession().find<Cluster>().where("id = ?").bind(id).resultValue();
}
void
Cluster::addTrack(Wt::Dbo::ptr<Track> track)
Cluster::addTrack(ObjectPtr<Track> track)
{
_tracks.insert(track);
_tracks.insert(getDboPtr(track));
}
std::vector<Wt::Dbo::ptr<Track>>
std::vector<Track::pointer>
Cluster::getTracks(std::optional<std::size_t> offset, std::optional<std::size_t> limit) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Track::pointer> res
{session()->query<Track::pointer>("SELECT t FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(self()->id())
auto res {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(getId())
.offset(offset ? static_cast<int>(*offset) : -1)
.limit(limit ? static_cast<int>(*limit) : -1)};
.limit(limit ? static_cast<int>(*limit) : -1)
.resultList()};
return std::vector<Wt::Dbo::ptr<Track>>(res.begin(), res.end());
return std::vector<Track::pointer>(res.begin(), res.end());
}
std::set<IdType>
std::vector<TrackId>
Cluster::getTrackIds() const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<IdType> res = session()->query<IdType>("SELECT t_c.track_id FROM track_cluster t_c INNER JOIN cluster c ON c.id = t_c.cluster_id")
.where("c.id = ?").bind(self()->id());
return std::set<IdType>(res.begin(), res.end());
Wt::Dbo::collection<TrackId> res = session()->query<TrackId>("SELECT t_c.track_id FROM track_cluster t_c INNER JOIN cluster c ON c.id = t_c.cluster_id")
.where("c.id = ?").bind(getId());
return std::vector<TrackId>(res.begin(), res.end());
}
std::size_t
Cluster::getReleasesCount() const
{
assert(session());
assert(IdIsValid(self()->id()));
return session()->query<int>("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN track t on t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(self()->id());
.where("c.id = ?").bind(getId());
}
ClusterType::ClusterType(std::string name)
: _name(name)
ClusterType::ClusterType(std::string_view name)
: _name {name}
{
}
@@ -132,7 +122,7 @@ ClusterType::getAllOrphans(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
Wt::Dbo::collection<Wt::Dbo::ptr<ClusterType>> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
"SELECT c_t from cluster_type c_t"
" LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id")
.where("c.id IS NULL");
@@ -145,7 +135,7 @@ ClusterType::getAllUsed(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
Wt::Dbo::collection<Wt::Dbo::ptr<ClusterType>> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
"SELECT DISTINCT c_t from cluster_type c_t")
.join("cluster c ON c_t.id = c.cluster_type_id");
@@ -157,15 +147,15 @@ ClusterType::getByName(Session& session, const std::string& name)
{
session.checkSharedLocked();
return session.getDboSession().find<ClusterType>().where("name = ?").bind(name);
return session.getDboSession().find<ClusterType>().where("name = ?").bind(name).resultValue();
}
ClusterType::pointer
ClusterType::getById(Session& session, IdType id)
ClusterType::getById(Session& session, ClusterTypeId id)
{
session.checkSharedLocked();
return session.getDboSession().find<ClusterType>().where("id= ?").bind(id);
return session.getDboSession().find<ClusterType>().where("id = ?").bind(id).resultValue();
}
std::vector<ClusterType::pointer>
@@ -173,8 +163,7 @@ ClusterType::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<ClusterType>();
auto res {session.getDboSession().find<ClusterType>().resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
@@ -193,24 +182,23 @@ Cluster::pointer
ClusterType::getCluster(const std::string& name) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
return session()->find<Cluster>()
.where("name = ?").bind(name)
.where("cluster_type_id = ?").bind(self()->id());
.where("cluster_type_id = ?").bind(getId()).resultValue();
}
std::vector<Cluster::pointer>
ClusterType::getClusters() const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<Cluster::pointer> res = session()->find<Cluster>()
.where("cluster_type_id = ?").bind(self()->id())
.orderBy("name");
auto res = session()->find<Cluster>()
.where("cluster_type_id = ?").bind(getId())
.orderBy("name")
.resultList();
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
+108 -114
View File
@@ -28,6 +28,7 @@
#include "database/User.hpp"
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "Traits.hpp"
#include "Utils.hpp"
namespace Database
@@ -38,7 +39,7 @@ static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords)
{
@@ -57,7 +58,7 @@ createQuery(Session& session,
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
for (const ClusterId clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
@@ -76,7 +77,6 @@ Release::Release(const std::string& name, const std::optional<UUID>& MBID)
: _name {std::string(name, 0 , _maxNameLength)},
_MBID {MBID ? MBID->getAsString() : ""}
{
}
std::vector<Release::pointer>
@@ -84,7 +84,11 @@ Release::getByName(Session& session, const std::string& name)
{
session.checkUniqueLocked();
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().find<Release>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
auto res {session.getDboSession()
.find<Release>()
.where("name = ?").bind( std::string(name, 0, _maxNameLength) )
.resultList()};
return std::vector<Release::pointer>(res.begin(), res.end());
}
@@ -93,15 +97,21 @@ Release::getByMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
return session.getDboSession().find<Release>().where("mbid = ?").bind(std::string {mbid.getAsString()});
return session.getDboSession()
.find<Release>()
.where("mbid = ?").bind(std::string {mbid.getAsString()})
.resultValue();;
}
Release::pointer
Release::getById(Session& session, IdType id)
Release::getById(Session& session, ReleaseId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Release>().where("id = ?").bind(id);
return session.getDboSession()
.find<Release>()
.where("id = ?").bind(id)
.resultValue();
}
Release::pointer
@@ -120,8 +130,7 @@ Release::getCount(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> releases {session.getDboSession().find<Release>()};
return releases.size();
return session.getDboSession().find<Release>().resultList().size();
}
std::vector<Release::pointer>
@@ -129,21 +138,22 @@ Release::getAll(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Release>()
auto res {session.getDboSession().find<Release>()
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) : -1)
.orderBy("name COLLATE NOCASE");
.orderBy("name COLLATE NOCASE")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<ReleaseId>
Release::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM release");
return std::vector<IdType>(res.begin(), res.end());
Wt::Dbo::collection<ReleaseId> res = session.getDboSession().query<ReleaseId>("SELECT id FROM release");
return std::vector<ReleaseId>(res.begin(), res.end());
}
std::vector<Release::pointer>
@@ -151,44 +161,45 @@ Release::getAllOrderedByArtist(Session& session, std::optional<std::size_t> offs
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<Wt::Dbo::ptr<Release>>(
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>(
"SELECT DISTINCT r FROM release r"
" INNER JOIN track t ON r.id = t.release_id"
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id"
" INNER JOIN artist a ON t_a_l.artist_id = a.id")
.offset(offset ? static_cast<int>(*offset) : -1)
.limit(size ? static_cast<int>(*size) : -1)
.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE");
.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getAllRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> size)
Release::getAllRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<Release::pointer>(session, "SELECT DISTINCT r from release r", clusterIds,{})};
Wt::Dbo::collection<pointer> res = query
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT DISTINCT r from release r", clusterIds, {})};
auto res {query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
.limit(size ? static_cast<int>(*size) : -1)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
Release::getAllIdsRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> size)
std::vector<ReleaseId>
Release::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT DISTINCT r.id from release r", clusterIds,{})};
auto query {createQuery<ReleaseId>(session, "SELECT DISTINCT r.id from release r", clusterIds, {})};
Wt::Dbo::collection<IdType> res = query
Wt::Dbo::collection<ReleaseId> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ReleaseId>(res.begin(), res.end());
}
@@ -197,31 +208,31 @@ Release::getAllOrphans(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().query<Wt::Dbo::ptr<Release>>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL");
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL").resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Release::pointer>(session, "SELECT r from release r", clusterIds, {})};
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, {})};
if (after)
query.where("t.file_last_write > ?").bind(after);
Wt::Dbo::collection<Release::pointer> collection = query
auto collection {query
.orderBy("t.file_last_write DESC")
.groupBy("r.id")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -236,13 +247,14 @@ Release::getLastWritten(Session& session,
std::vector<Release::pointer>
Release::getByYear(Session& session, int yearFrom, int yearTo, std::optional<Range> range)
{
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().query<Release::pointer>
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>
("SELECT DISTINCT r from release r INNER JOIN track t ON r.id = t.release_id")
.where("t.year >= ?").bind(yearFrom)
.where("t.year <= ?").bind(yearTo)
.orderBy("t.year, r.name COLLATE NOCASE")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) : -1);
.limit(range ? static_cast<int>(range->limit) : -1)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
@@ -250,30 +262,31 @@ Release::getByYear(Session& session, int yearFrom, int yearTo, std::optional<Ran
std::vector<Release::pointer>
Release::getStarred(Session& session,
User::pointer user,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Release::pointer>(session, "SELECT r from release r", clusterIds, {})};
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, {})};
{
std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN user_release_starred urs ON urs.release_id = r.id"
" INNER JOIN user u ON u.id = urs.user_id WHERE u.id = ?)";
query.bind(user.id());
query.bind(user->getId());
query.where(oss.str());
}
Wt::Dbo::collection<Release::pointer> collection = query
auto collection {query
.groupBy("r.id")
.orderBy("r.name COLLATE NOCASE")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -288,7 +301,7 @@ Release::getStarred(Session& session,
}
std::vector<Release::pointer>
Release::getByClusters(Session& session, const std::set<IdType>& clusters)
Release::getByClusters(Session& session, const std::vector<ClusterId>& clusters)
{
assert(!clusters.empty());
@@ -300,21 +313,21 @@ Release::getByClusters(Session& session, const std::set<IdType>& clusters)
std::vector<Release::pointer>
Release::getByFilter(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = createQuery<Release::pointer>(session, "SELECT r from release r", clusterIds, keywords)
auto collection {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, keywords)
.groupBy("r.id")
.orderBy("r.name COLLATE NOCASE")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -326,18 +339,18 @@ Release::getByFilter(Session& session,
return res;
}
std::vector<IdType>
std::vector<ReleaseId>
Release::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
Wt::Dbo::collection<ReleaseId> res = session.getDboSession().query<ReleaseId>
("SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ReleaseId>(res.begin(), res.end());
}
@@ -345,11 +358,10 @@ std::optional<std::size_t>
Release::getTotalTrack(void) const
{
assert(session());
assert(IdIsValid(self()->id()));
int res = session()->query<int>("SELECT COALESCE(MAX(total_track),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.bind(this->id());
.bind(getId());
return (res > 0) ? std::make_optional<std::size_t>(res) : std::nullopt;
}
@@ -358,11 +370,10 @@ std::optional<std::size_t>
Release::getTotalDisc(void) const
{
assert(session());
assert(IdIsValid(self()->id()));
int res = session()->query<int>("SELECT COALESCE(MAX(total_disc),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.bind(this->id());
.bind(getId());
return (res > 0) ? std::make_optional<std::size_t>(res) : std::nullopt;
}
@@ -372,13 +383,13 @@ Release::getReleaseYear(bool original) const
{
assert(session());
const std::string field {original ? "original_year" : "year"};
const char* field {original ? "original_year" : "year"};
Wt::Dbo::collection<int> dates = session()->query<int>(
std::string {"SELECT "} + "t." + field + " FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.groupBy(field)
.bind(this->id());
.bind(getId());
// various dates => no date
if (dates.empty() || dates.size() > 1)
@@ -388,8 +399,8 @@ Release::getReleaseYear(bool original) const
if (date > 0)
return date;
else
return std::nullopt;
return std::nullopt;
}
std::optional<std::string>
@@ -401,7 +412,7 @@ Release::getCopyright() const
("SELECT copyright FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.groupBy("copyright")
.bind(this->id());
.bind(getId());
std::vector<std::string> values(copyrights.begin(), copyrights.end());
@@ -421,7 +432,7 @@ Release::getCopyrightURL() const
("SELECT copyright_url FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.groupBy("copyright_url")
.bind(this->id());
.bind(getId());
std::vector<std::string> values(copyrights.begin(), copyrights.end());
@@ -432,32 +443,29 @@ Release::getCopyrightURL() const
return values.front();
}
std::vector<Wt::Dbo::ptr<Artist>>
std::vector<Artist::pointer>
Release::getArtists(TrackArtistLinkType linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session()->query<Wt::Dbo::ptr<Artist>>(
auto res {session()->query<Wt::Dbo::ptr<Artist>>(
"SELECT DISTINCT a FROM artist a"
" INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN track t ON t.id = t_a_l.track_id"
" INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?").bind(self()->id())
.where("t_a_l.type = ?").bind(linkType);
.where("r.id = ?").bind(getId())
.where("t_a_l.type = ?").bind(linkType)
.resultList()};
return std::vector<Wt::Dbo::ptr<Artist>>(res.begin(), res.end());
return std::vector<Artist::pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getSimilarReleases(std::optional<std::size_t> offset, std::optional<std::size_t> count) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::Query<pointer> query {session()->query<pointer>(
auto res {session()->query<Wt::Dbo::ptr<Release>>(
"SELECT r FROM release r"
" INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
@@ -465,14 +473,14 @@ Release::getSimilarReleases(std::optional<std::size_t> offset, std::optional<std
" t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id WHERE r.id = ?)"
" AND r.id <> ?"
)
.bind(self()->id())
.bind(self()->id())
.bind(getId())
.bind(getId())
.groupBy("r.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(count ? static_cast<int>(*count) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)};
.offset(offset ? static_cast<int>(*offset) : -1)
.resultList()};
Wt::Dbo::collection<pointer> res = query;
return std::vector<pointer>(res.begin(), res.end());
}
@@ -483,11 +491,9 @@ Release::hasVariousArtists() const
return getArtists().size() > 1;
}
std::vector<Wt::Dbo::ptr<Track>>
Release::getTracks(const std::set<IdType>& clusterIds) const
std::vector<Track::pointer>
Release::getTracks(const std::vector<ClusterId>& clusterIds) const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Release>::invalidId() );
assert(session());
WhereClause where;
@@ -502,12 +508,12 @@ Release::getTracks(const std::set<IdType>& clusterIds) const
WhereClause clusterClause;
for (auto id : clusterIds)
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
where.And(clusterClause);
}
where.And(WhereClause("r.id = ?")).bind(std::to_string(id()));
where.And(WhereClause("r.id = ?")).bind(getId().toString());
oss << " " << where.get();
@@ -516,16 +522,12 @@ Release::getTracks(const std::set<IdType>& clusterIds) const
oss << " ORDER BY t.disc_number,t.track_number";
Wt::Dbo::Query<Track::pointer> query = session()->query<Track::pointer>( oss.str() );
auto query {session()->query<Wt::Dbo::ptr<Track>>(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
{
query.bind(bindArg);
}
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > res = query;
return std::vector< Wt::Dbo::ptr<Track> > (res.begin(), res.end());
auto res {query.resultList()};
return std::vector<Track::pointer> (res.begin(), res.end());
}
std::size_t
@@ -534,31 +536,28 @@ Release::getTracksCount() const
return _tracks.size();
}
Wt::Dbo::ptr<Track>
Track::pointer
Release::getFirstTrack() const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId());
assert(session());
return session()->query<Track::pointer>("SELECT t from track t")
return session()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t")
.join("release r ON t.release_id = r.id")
.where("r.id = ?").bind(self()->id())
.where("r.id = ?").bind(getId())
.orderBy("t.disc_number,t.track_number")
.limit(1);
.limit(1)
.resultValue();
}
std::chrono::milliseconds
Release::getDuration() const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId());
assert(session());
using milli = std::chrono::duration<int, std::milli>;
Wt::Dbo::Query<milli> query {session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN release r ON t.release_id = r.id")
.where("r.id = ?").bind(self()->id())};
.where("r.id = ?").bind(getId())};
return query.resultValue();
}
@@ -566,21 +565,17 @@ Release::getDuration() const
Wt::WDateTime
Release::getLastWritten() const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId());
assert(session());
Wt::Dbo::Query<Wt::WDateTime> query {session()->query<Wt::WDateTime>("SELECT COALESCE(MAX(file_last_write), '1970-01-01T00:00:00') FROM track t INNER JOIN release r ON t.release_id = r.id")
.where("r.id = ?").bind(self()->id())};
.where("r.id = ?").bind(getId())};
return query.resultValue();
}
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>>
Release::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
std::vector<std::vector<Cluster::pointer>>
Release::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId() );
assert(session());
WhereClause where;
@@ -589,33 +584,32 @@ Release::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::s
oss << "SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN release r ON t.release_id = r.id ";
where.And(WhereClause("r.id = ?")).bind(std::to_string(self()->id()));
where.And(WhereClause("r.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(std::to_string(clusterType.id()));
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
where.And(clusterClause);
}
oss << " " << where.get();
oss << " GROUP BY c.id ORDER BY COUNT(c.id) DESC";
Wt::Dbo::Query<Cluster::pointer> query = session()->query<Cluster::pointer>( oss.str() );
auto query {session()->query<Wt::Dbo::ptr<Cluster>>(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
Wt::Dbo::collection<Cluster::pointer> queryRes = query;
auto queryRes {query.resultList()};
std::map<IdType, std::vector<Cluster::pointer>> clusters;
for (auto cluster : queryRes)
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clustersByType;
for (const Wt::Dbo::ptr<Cluster>& cluster : queryRes)
{
if (clusters[cluster->getType().id()].size() < size)
clusters[cluster->getType().id()].push_back(cluster);
if (clustersByType[cluster->getType()->getId()].size() < size)
clustersByType[cluster->getType()->getId()].push_back(cluster);
}
std::vector<std::vector<Cluster::pointer>> res;
for (auto cluster_list : clusters)
res.push_back(cluster_list.second);
for (const auto& [clusterTypeId, clusters] : clustersByType)
res.push_back(clusters);
return res;
}
+7 -7
View File
@@ -60,14 +60,14 @@ ScanSettings::get(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<ScanSettings>();
return session.getDboSession().find<ScanSettings>().resultValue();
}
std::unordered_set<std::filesystem::path>
std::vector<std::filesystem::path>
ScanSettings::getAudioFileExtensions() const
{
auto extensions = StringUtils::splitString(_audioFileExtensions, " ");
return std::unordered_set<std::filesystem::path>(std::cbegin(extensions), std::cend(extensions));
const auto extensions {StringUtils::splitString(_audioFileExtensions, " ")};
return std::vector<std::filesystem::path>(std::cbegin(extensions), std::cend(extensions));
}
void
@@ -111,19 +111,19 @@ ScanSettings::setClusterTypes(Session& session, const std::set<std::string>& clu
// Create any missing cluster type
for (const std::string& clusterTypeName : clusterTypeNames)
{
auto clusterType {ClusterType::getByName(session, clusterTypeName)};
ClusterType::pointer clusterType {ClusterType::getByName(session, clusterTypeName)};
if (!clusterType)
{
LMS_LOG(DB, INFO) << "Creating cluster type " << clusterTypeName;
clusterType = ClusterType::create(session, clusterTypeName);
_clusterTypes.insert(clusterType);
_clusterTypes.insert(getDboPtr(clusterType));
needRescan = true;
}
}
// Delete no longer existing cluster types
for (ClusterType::pointer& clusterType : _clusterTypes)
for (Wt::Dbo::ptr<ClusterType> clusterType : _clusterTypes)
{
if (std::none_of(clusterTypeNames.begin(), clusterTypeNames.end(),
[clusterType](const std::string& name) { return name == clusterType->getName(); }))
+108 -106
View File
@@ -27,10 +27,12 @@
#include "database/TrackArtistLink.hpp"
#include "database/TrackFeatures.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
#include "Utils.hpp"
namespace Database {
@@ -40,7 +42,7 @@ static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords)
{
session.checkSharedLocked();
@@ -58,7 +60,7 @@ createQuery(Session& session,
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
for (const ClusterId clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
@@ -91,48 +93,49 @@ Track::getAll(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res {session.getDboSession().find<Track>()
.limit(limit ? static_cast<int>(*limit) : -1)};
auto res {session.getDboSession().find<Track>()
.limit(limit ? static_cast<int>(*limit) : -1)
.resultList()};
return std::vector<Track::pointer>(std::cbegin(res), std::cend(res));
}
std::vector<Track::pointer>
Track::getAllRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> limit)
Track::getAllRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
auto query {createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, {})};
Wt::Dbo::collection<Track::pointer> collection = query
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
auto collection {query
.orderBy("RANDOM()")
.limit(limit ? static_cast<int>(*limit) + 1: -1);
.limit(limit ? static_cast<int>(*limit) + 1: -1)
.resultList()};
return std::vector<pointer>(collection.begin(), collection.end());
}
std::vector<Database::IdType>
Track::getAllIdsRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> limit)
std::vector<TrackId>
Track::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT t.id from track t", clusterIds, {})};
auto query {createQuery<TrackId>(session, "SELECT t.id from track t", clusterIds, {})};
Wt::Dbo::collection<IdType> collection = query
Wt::Dbo::collection<TrackId> collection = query
.orderBy("RANDOM()")
.limit(limit ? static_cast<int>(*limit) + 1: -1);
return std::vector<IdType>(collection.begin(), collection.end());
return std::vector<TrackId>(collection.begin(), collection.end());
}
std::vector<IdType>
std::vector<TrackId>
Track::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM track");
return std::vector<IdType>(res.begin(), res.end());
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>("SELECT id FROM track");
return std::vector<TrackId>(res.begin(), res.end());
}
Track::pointer
@@ -140,16 +143,17 @@ Track::getByPath(Session& session, const std::filesystem::path& p)
{
session.checkSharedLocked();
return session.getDboSession().find<Track>().where("file_path = ?").bind(p.string());
return session.getDboSession().find<Track>().where("file_path = ?").bind(p.string()).resultValue();
}
Track::pointer
Track::getById(Session& session, IdType id)
Track::getById(Session& session, TrackId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Track>()
.where("id = ?").bind(id);
.where("id = ?").bind(id)
.resultValue();
}
std::vector<Track::pointer>
@@ -157,8 +161,9 @@ Track::getByRecordingMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res = session.getDboSession().find<Track>()
.where("recording_mbid = ?").bind(std::string {mbid.getAsString()});
auto res {session.getDboSession().find<Track>()
.where("recording_mbid = ?").bind(std::string {mbid.getAsString()})
.resultList()};
return std::vector<Track::pointer>(res.begin(), res.end());
}
@@ -174,17 +179,17 @@ Track::create(Session& session, const std::filesystem::path& p)
return res;
}
std::vector<std::pair<IdType, std::filesystem::path>>
std::vector<std::pair<TrackId, std::filesystem::path>>
Track::getAllPaths(Session& session, std::optional<std::size_t> offset, std::optional<std::size_t> size)
{
using QueryResultType = std::tuple<IdType, std::string>;
using QueryResultType = std::tuple<TrackId, std::string>;
session.checkSharedLocked();
Wt::Dbo::collection<QueryResultType> queryRes = session.getDboSession().query<QueryResultType>("SELECT id,file_path FROM track")
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
std::vector<std::pair<IdType, std::filesystem::path>> result;
std::vector<std::pair<TrackId, std::filesystem::path>> result;
result.reserve(queryRes.size());
std::transform(std::begin(queryRes), std::end(queryRes), std::back_inserter(result),
@@ -201,26 +206,29 @@ Track::getMBIDDuplicates(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<pointer>( "SELECT track FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.disc_number,track.track_number,track.mbid");
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>( "SELECT track FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)")
.orderBy("track.release_id,track.disc_number,track.track_number,track.mbid")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults)
Track::getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, {})};
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
if (after)
query.where("t.file_last_write > ?").bind(after);
Wt::Dbo::collection<Track::pointer> collection = query
auto collection {query
.orderBy("t.file_last_write DESC")
.groupBy("t.id")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -237,63 +245,65 @@ Track::getAllWithRecordingMBIDAndMissingFeatures(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<pointer>
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>
("SELECT t FROM track t")
.where("LENGTH(t.recording_mbid) > 0")
.where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)");
.where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<TrackId>
Track::getAllIdsWithFeatures(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>
("SELECT t.id FROM track t")
.where("EXISTS (SELECT * from track_features t_f WHERE t_f.track_id = t.id)")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<TrackId>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<TrackId>
Track::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>
("SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<TrackId>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getStarred(Session& session,
Wt::Dbo::ptr<User> user,
const std::set<IdType>& clusterIds,
ObjectPtr<User> user,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, {})};
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
{
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN user_track_starred uts ON uts.track_id = t.id"
" INNER JOIN user u ON u.id = uts.user_id WHERE u.id = ?)";
query.bind(user.id());
query.bind(user->getId().toString());
query.where(oss.str());
}
Wt::Dbo::collection<Track::pointer> collection = query
auto collection {query
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -308,43 +318,41 @@ Track::getStarred(Session& session,
std::vector<Cluster::pointer>
Track::getClusters() const
{
std::vector< Cluster::pointer > clusters;
std::copy(_clusters.begin(), _clusters.end(), std::back_inserter(clusters));
return clusters;
return std::vector<Cluster::pointer>(_clusters.begin(), _clusters.end());
}
std::vector<IdType>
std::vector<ClusterId>
Track::getClusterIds() const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<IdType> res = session()->query<IdType>
auto res {session()->query<ClusterId>
("SELECT DISTINCT c.id FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN track t ON t.id = t_c.track_id")
.where("t.id = ?").bind(self()->id());
.where("t.id = ?").bind(getId())
.resultList()};
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ClusterId>(res.begin(), res.end());
}
bool
Track::hasTrackFeatures() const
{
return (_trackFeatures.lock() != Database::TrackFeatures::pointer());
return (_trackFeatures.lock() != Wt::Dbo::ptr<Database::TrackFeatures> {});
}
std::vector<Track::pointer>
Track::getByFilter(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, keywords)
auto collection {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, keywords)
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && (res.size() == static_cast<std::size_t>(range->limit) + 1))
@@ -362,17 +370,18 @@ std::vector<Track::pointer>
Track::getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = session.getDboSession().query<Track::pointer>("SELECT t from track t")
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>("SELECT t from track t")
.join("release r ON t.release_id = r.id")
.where("t.name = ?").bind(trackName)
.where("r.name = ?").bind(releaseName);
return std::vector<pointer>(collection.begin(), collection.end());
.where("r.name = ?").bind(releaseName)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getSimilarTracks(Session& session,
const std::unordered_set<IdType>& tracks,
const std::vector<TrackId>& tracks,
std::optional<std::size_t> offset,
std::optional<std::size_t> size)
{
@@ -387,7 +396,7 @@ Track::getSimilarTracks(Session& session,
oss << "?";
}
Wt::Dbo::Query<pointer> query {session.getDboSession().query<pointer>(
auto query {session.getDboSession().query<Wt::Dbo::ptr<Track>>(
"SELECT t FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" AND t_c.cluster_id IN (SELECT c.id FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id WHERE t_c.track_id IN (" + oss.str() + "))"
@@ -397,19 +406,18 @@ Track::getSimilarTracks(Session& session,
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)};
for (IdType trackId : tracks)
query.bind(trackId );
for (TrackId trackId : tracks)
query.bind(trackId);
for (IdType trackId : tracks)
query.bind(trackId );
for (TrackId trackId : tracks)
query.bind(trackId);
Wt::Dbo::collection<pointer> res = query;
auto res {query.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getByClusters(Session& session,
const std::set<IdType>& clusters)
Track::getByClusters(Session& session, const std::vector<ClusterId>& clusters)
{
assert(!clusters.empty());
session.checkSharedLocked();
@@ -429,23 +437,23 @@ Track::clearArtistLinks()
}
void
Track::addArtistLink(const Wt::Dbo::ptr<TrackArtistLink>& artistLink)
Track::addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink)
{
_trackArtistLinks.insert(artistLink);
_trackArtistLinks.insert(getDboPtr(artistLink));
}
void
Track::setClusters(const std::vector<Wt::Dbo::ptr<Cluster>>& clusters)
Track::setClusters(const std::vector<ObjectPtr<Cluster>>& clusters)
{
_clusters.clear();
for (const Wt::Dbo::ptr<Cluster>& cluster : clusters)
_clusters.insert(cluster);
for (const ObjectPtr<Cluster>& cluster : clusters)
_clusters.insert(getDboPtr(cluster));
}
void
Track::setFeatures(const Wt::Dbo::ptr<TrackFeatures>& features)
Track::setFeatures(const ObjectPtr<TrackFeatures>& features)
{
_trackFeatures = features;
_trackFeatures = getDboPtr(features);
}
std::optional<std::size_t>
@@ -496,11 +504,9 @@ Track::getCopyrightURL() const
return _copyrightURL != "" ? std::make_optional<std::string>(_copyrightURL) : std::nullopt;
}
std::vector<Wt::Dbo::ptr<Artist>>
std::vector<Artist::pointer>
Track::getArtists(EnumSet<TrackArtistLinkType> linkTypes) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
std::ostringstream oss;
@@ -525,22 +531,20 @@ Track::getArtists(EnumSet<TrackArtistLinkType> linkTypes) const
oss << ")";
}
Wt::Dbo::Query<Artist::pointer> query {session()->query<Artist::pointer>(oss.str())};
auto query {session()->query<Wt::Dbo::ptr<Artist>>(oss.str())};
for (TrackArtistLinkType type : linkTypes)
query.bind(type);
query.where("t.id = ?").bind(self()->id());
query.where("t.id = ?").bind(getId());
Wt::Dbo::collection<Artist::pointer> res = query;
auto res {query.resultList()};
return std::vector<Artist::pointer>(std::begin(res), std::end(res));
}
std::vector<IdType>
std::vector<ArtistId>
Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
std::ostringstream oss;
@@ -565,33 +569,32 @@ Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
oss << ")";
}
Wt::Dbo::Query<IdType> query {session()->query<IdType>(oss.str())
.where("t.id = ?").bind(self()->id())};
Wt::Dbo::Query<ArtistId> query {session()->query<ArtistId>(oss.str())
.where("t.id = ?").bind(getId())};
for (TrackArtistLinkType type : linkTypes)
query.bind(type);
Wt::Dbo::collection<IdType> res = query;
return std::vector<IdType>(std::begin(res), std::end(res));
Wt::Dbo::collection<ArtistId> res = query;
return std::vector<ArtistId>(std::begin(res), std::end(res));
}
std::vector<Wt::Dbo::ptr<TrackArtistLink>>
std::vector<TrackArtistLink::pointer>
Track::getArtistLinks() const
{
return std::vector<Wt::Dbo::ptr<TrackArtistLink>>(_trackArtistLinks.begin(), _trackArtistLinks.end());
return std::vector<TrackArtistLink::pointer>(_trackArtistLinks.begin(), _trackArtistLinks.end());
}
Wt::Dbo::ptr<TrackFeatures>
ObjectPtr<TrackFeatures>
Track::getTrackFeatures() const
{
return _trackFeatures.lock();
}
std::vector<std::vector<Cluster::pointer>>
Track::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
Track::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
WhereClause where;
@@ -600,28 +603,27 @@ Track::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::siz
oss << "SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id";
where.And(WhereClause("t.id = ?")).bind(std::to_string(self()->id()));
where.And(WhereClause("t.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(std::to_string(clusterType.id()));
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
where.And(clusterClause);
}
oss << " " << where.get();
oss << " GROUP BY c.id ORDER BY COUNT(c.id) DESC";
Wt::Dbo::Query<Cluster::pointer> query = session()->query<Cluster::pointer>( oss.str() );
auto query {session()->query<Wt::Dbo::ptr<Cluster>>(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
Wt::Dbo::collection<Cluster::pointer> queryRes = query;
auto queryRes {query.resultList()};
std::map<IdType, std::vector<Cluster::pointer>> clusters;
for (auto cluster : queryRes)
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clusters;
for (const Wt::Dbo::ptr<Cluster>& cluster : queryRes)
{
if (clusters[cluster->getType().id()].size() < size)
clusters[cluster->getType().id()].push_back(cluster);
if (clusters[cluster->getType()->getId()].size() < size)
clusters[cluster->getType()->getId()].push_back(cluster);
}
std::vector<std::vector<Cluster::pointer>> res;
+8 -6
View File
@@ -23,17 +23,19 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "Traits.hpp"
namespace Database {
TrackArtistLink::TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type)
TrackArtistLink::TrackArtistLink(ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type)
: _type {type},
_track {track},
_artist {artist}
_track {getDboPtr(track)},
_artist {getDboPtr(artist)}
{
}
TrackArtistLink::pointer
TrackArtistLink::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type)
TrackArtistLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type)
{
session.checkUniqueLocked();
@@ -48,9 +50,9 @@ TrackArtistLink::getUsedTypes(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackArtistLinkType> collection = session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link");
auto res {session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link").resultList()};
return EnumSet<TrackArtistLinkType>(std::begin(collection), std::end(collection));
return EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res));
}
}
+17 -18
View File
@@ -22,18 +22,18 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp"
#include "Traits.hpp"
namespace Database {
TrackBookmark::TrackBookmark(Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
: _user {user},
_track {track}
TrackBookmark::TrackBookmark(ObjectPtr<User> user, ObjectPtr<Track> track)
: _user {getDboPtr(user)},
_track {getDboPtr(track)}
{
}
TrackBookmark::pointer
TrackBookmark::create(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
TrackBookmark::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track)
{
session.checkUniqueLocked();
@@ -48,42 +48,41 @@ TrackBookmark::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackBookmark::pointer> res {session.getDboSession().find<TrackBookmark>()};
auto res {session.getDboSession().find<TrackBookmark>().resultList()};
return std::vector<TrackBookmark::pointer>(std::cbegin(res), std::cend(res));
}
std::vector<TrackBookmark::pointer>
TrackBookmark::getByUser(Session& session, Wt::Dbo::ptr<User> user)
TrackBookmark::getByUser(Session& session, User::pointer user)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackBookmark::pointer> res
{
session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(user.id())
};
auto res {session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(user->getId())
.resultList()};
return std::vector<TrackBookmark::pointer>(std::cbegin(res), std::cend(res));
}
TrackBookmark::pointer
TrackBookmark::getByUser(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
TrackBookmark::getByUser(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(user.id())
.where("track_id = ?").bind(track.id());
.where("user_id = ?").bind(user->getId())
.where("track_id = ?").bind(track->getId())
.resultValue();
}
TrackBookmark::pointer
TrackBookmark::getById(Session& session, IdType id)
TrackBookmark::getById(Session& session, TrackBookmarkId id)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackBookmark>()
.where("id = ?").bind(id);
.where("id = ?").bind(id)
.resultValue();
}
+4 -4
View File
@@ -28,14 +28,14 @@
namespace Database {
TrackFeatures::TrackFeatures(Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures)
: _data(jsonEncodedFeatures),
_track(track)
TrackFeatures::TrackFeatures(ObjectPtr<Track> track, const std::string& jsonEncodedFeatures)
: _data {jsonEncodedFeatures},
_track {getDboPtr(track)}
{
}
TrackFeatures::pointer
TrackFeatures::create(Session& session, Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures)
TrackFeatures::create(Session& session, ObjectPtr<Track> track, const std::string& jsonEncodedFeatures)
{
session.checkUniqueLocked();
return session.getDboSession().add(std::make_unique<TrackFeatures>(track, jsonEncodedFeatures));
+110 -116
View File
@@ -30,32 +30,33 @@
#include "database/Track.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
namespace Database {
TrackList::TrackList(std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr<User> user)
TrackList::TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<User> user)
: _name {name},
_type {type},
_isPublic {isPublic},
_user {user}
_user {getDboPtr(user)}
{
}
TrackList::pointer
TrackList::create(Session& session, std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr<User> user)
TrackList::create(Session& session, std::string_view name, Type type, bool isPublic, ObjectPtr<User> user)
{
session.checkUniqueLocked();
assert(user);
auto res = session.getDboSession().add( std::make_unique<TrackList>(name, type, isPublic, user) );
TrackList::pointer res {session.getDboSession().add( std::make_unique<TrackList>(name, type, isPublic, user) )};
session.getDboSession().flush();
return res;
}
TrackList::pointer
TrackList::get(Session& session, std::string_view name, Type type, Wt::Dbo::ptr<User> user)
TrackList::get(Session& session, std::string_view name, Type type, ObjectPtr<User> user)
{
session.checkSharedLocked();
assert(user);
@@ -63,49 +64,51 @@ TrackList::get(Session& session, std::string_view name, Type type, Wt::Dbo::ptr<
return session.getDboSession().find<TrackList>()
.where("name = ?").bind(name)
.where("type = ?").bind(type)
.where("user_id = ?").bind(user.id());
.where("user_id = ?").bind(user->getId()).resultValue();
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackList::pointer> res = session.getDboSession().find<TrackList>();
auto res = session.getDboSession().find<TrackList>().resultList();
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session, ObjectPtr<User> user)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user->getId())
.orderBy("name COLLATE NOCASE")
.resultList()};
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session, Wt::Dbo::ptr<User> user)
TrackList::getAll(Session& session, ObjectPtr<User> user, Type type)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackList::pointer> res = session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user.id())
.orderBy("name COLLATE NOCASE");
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session, Wt::Dbo::ptr<User> user, Type type)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackList::pointer> res = session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user.id())
auto res {session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user->getId())
.where("type = ?").bind(type)
.orderBy("name COLLATE NOCASE");
.orderBy("name COLLATE NOCASE")
.resultList()};
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
TrackList::pointer
TrackList::getById(Session& session, IdType id)
TrackList::getById(Session& session, TrackListId id)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackList>().where("id = ?").bind(id);
return session.getDboSession().find<TrackList>().where("id = ?").bind(id).resultValue();
}
bool
@@ -120,10 +123,10 @@ TrackList::getCount() const
return _entries.size();
}
Wt::Dbo::ptr<TrackListEntry>
TrackListEntry::pointer
TrackList::getEntry(std::size_t pos) const
{
Wt::Dbo::ptr<TrackListEntry> res;
TrackListEntry::pointer res;
auto entries = getEntries(pos, 1);
if (!entries.empty())
@@ -132,39 +135,39 @@ TrackList::getEntry(std::size_t pos) const
return res;
}
std::vector<Wt::Dbo::ptr<TrackListEntry>>
std::vector<TrackListEntry::pointer>
TrackList::getEntries(std::optional<std::size_t> offset, std::optional<std::size_t> size) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> entries =
auto entries {
session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(self().id())
.where("tracklist_id = ?").bind(getId())
.orderBy("id")
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
.offset(offset ? static_cast<int>(*offset) : -1)
.resultList()};
return std::vector<Wt::Dbo::ptr<TrackListEntry>>(entries.begin(), entries.end());
return std::vector<TrackListEntry::pointer>(entries.begin(), entries.end());
}
Wt::Dbo::ptr<TrackListEntry>
TrackList::getEntryByTrackAndDateTime(Wt::Dbo::ptr<Track> track, const Wt::WDateTime& dateTime) const
TrackListEntry::pointer
TrackList::getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const
{
assert(session());
assert(IdIsValid(self()->id()));
return session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(self().id())
.where("track_id = ?").bind(track.id())
.where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t()));
.where("tracklist_id = ?").bind(getId())
.where("track_id = ?").bind(track->getId())
.where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t()))
.resultValue();
}
static
Wt::Dbo::Query<Artist::pointer>
createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType)
Wt::Dbo::Query<Wt::Dbo::ptr<Artist>>
createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType)
{
auto query {session.query<Artist::pointer>(queryStr)};
auto query {session.query<Wt::Dbo::ptr<Artist>>(queryStr)};
query.join("track t ON t.id = t_a_l.track_id");
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id");
query.join("tracklist_entry p_e ON p_e.track_id = t.id");
@@ -201,10 +204,10 @@ createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdTyp
}
static
Wt::Dbo::Query<Release::pointer>
createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set<IdType>& clusterIds)
Wt::Dbo::Query<Wt::Dbo::ptr<Release>>
createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds)
{
auto query {session.query<Release::pointer>(queryStr)};
auto query {session.query<Wt::Dbo::ptr<Release>>(queryStr)};
query.join("track t ON t.release_id = r.id");
query.join("tracklist_entry p_e ON p_e.track_id = t.id");
query.join("tracklist p ON p.id = p_e.tracklist_id");
@@ -220,7 +223,7 @@ createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdTy
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (auto id : clusterIds)
for (ClusterId id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(id);
@@ -236,10 +239,10 @@ createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdTy
}
static
Wt::Dbo::Query<Track::pointer>
createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set<IdType>& clusterIds)
Wt::Dbo::Query<Wt::Dbo::ptr<Track>>
createTracksQuery(Wt::Dbo::Session& session, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds)
{
auto query {session.query<Track::pointer>("SELECT t from track t INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")};
auto query {session.query<Wt::Dbo::ptr<Track>>("SELECT t from track t INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")};
query.where("p.id = ?").bind(tracklistId);
@@ -253,7 +256,7 @@ createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set<
WhereClause clusterClause;
for (auto id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
query.bind(id);
}
@@ -267,16 +270,16 @@ createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set<
}
std::vector<Artist::pointer>
TrackList::getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
TrackList::getArtistsReverse(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Artist::pointer> collection = createArtistsQuery(*session(), "SELECT a from artist a", self()->id(), clusterIds, linkType)
auto collection {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType)
.groupBy("a.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
@@ -291,18 +294,18 @@ TrackList::getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<T
}
std::vector<Release::pointer>
TrackList::getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getReleasesReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Release::pointer> collection = createReleasesQuery(*session(), "SELECT r from release r", self()->id(), clusterIds)
auto collection {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds)
.groupBy("r.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Release::pointer>(collection.begin(), collection.end())};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -315,18 +318,18 @@ TrackList::getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<
}
std::vector<Track::pointer>
TrackList::getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getTracksReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Track::pointer> collection = createTracksQuery(*session(), self()->id(), clusterIds)
auto collection {createTracksQuery(*session(), getId(), clusterIds)
.groupBy("t.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Track::pointer>(collection.begin(), collection.end())};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -338,29 +341,28 @@ TrackList::getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Ra
return res;
}
std::vector<Wt::Dbo::ptr<Cluster>>
std::vector<Cluster::pointer>
TrackList::getClusters() const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Cluster::pointer> res = session()->query<Cluster::pointer>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")
.where("p.id = ?").bind(self()->id())
auto res {session()->query<Wt::Dbo::ptr<Cluster>>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")
.where("p.id = ?").bind(getId())
.groupBy("c.id")
.orderBy("COUNT(c.id) DESC");
.orderBy("COUNT(c.id) DESC")
.resultList()};
return std::vector<Wt::Dbo::ptr<Cluster>>(res.begin(), res.end());
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
bool
TrackList::hasTrack(IdType trackId) const
TrackList::hasTrack(TrackId trackId) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<TrackListEntry::pointer> res = session()->query<TrackListEntry::pointer>("SELECT p_e from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id")
.where("p_e.track_id = ?").bind(trackId)
.where("p.id = ?").bind(self()->id());
.where("p.id = ?").bind(getId());
return res.size() > 0;
}
@@ -369,67 +371,64 @@ std::vector<Track::pointer>
TrackList::getSimilarTracks(std::optional<std::size_t> offset, std::optional<std::size_t> size) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::Query<Track::pointer> query {session()->query<Track::pointer>(
auto res {session()->query<Wt::Dbo::ptr<Track>>(
"SELECT t FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" WHERE "
" (t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id WHERE p.id = ?)"
" AND t.id NOT IN (SELECT tracklist_t.id FROM track tracklist_t INNER JOIN tracklist_entry t_e ON t_e.track_id = tracklist_t.id WHERE t_e.tracklist_id = ?))"
)
.bind(self()->id())
.bind(self()->id())
.bind(getId())
.bind(getId())
.groupBy("t.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)};
.offset(offset ? static_cast<int>(*offset) : -1)
.resultList()};
Wt::Dbo::collection<Track::pointer> tracks = query;
return std::vector<Track::pointer>(tracks.begin(), tracks.end());
return std::vector<Track::pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<TrackId>
TrackList::getTrackIds() const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<IdType> res = session()->query<IdType>("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id")
.where("p.id = ?").bind(self()->id());
Wt::Dbo::collection<TrackId> res = session()->query<TrackId>("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id")
.where("p.id = ?").bind(getId());
return std::vector<IdType>(res.begin(), res.end());
return std::vector<TrackId>(res.begin(), res.end());
}
std::chrono::milliseconds
TrackList::getDuration() const
{
assert(session());
assert(IdIsValid(self()->id()));
using milli = std::chrono::duration<int, std::milli>;
Wt::Dbo::Query<milli> query {session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id")
.where("p_e.tracklist_id = ?").bind(self()->id())};
.where("p_e.tracklist_id = ?").bind(getId())};
return query.resultValue();
}
std::vector<Artist::pointer>
TrackList::getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
TrackList::getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
auto query {createArtistsQuery(*session(), "SELECT a from artist a", self()->id(), clusterIds, linkType)};
auto query {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType)};
Wt::Dbo::collection<Artist::pointer> collection = query
auto collection {query
.orderBy("COUNT(a.id) DESC")
.groupBy("a.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
std::vector<Artist::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
@@ -439,26 +438,23 @@ TrackList::getTopArtists(const std::set<IdType>& clusterIds, std::optional<Track
else
moreResults = false;
return res;
}
std::vector<Release::pointer>
TrackList::getTopReleases(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getTopReleases(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
auto query {createReleasesQuery(*session(), "SELECT r from release r", self()->id(), clusterIds)};
Wt::Dbo::collection<Release::pointer> collection = query
auto query {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds)};
auto collection {query
.orderBy("COUNT(r.id) DESC")
.groupBy("r.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Release::pointer>(collection.begin(), collection.end())};
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -471,21 +467,19 @@ TrackList::getTopReleases(const std::set<IdType>& clusterIds, std::optional<Rang
}
std::vector<Track::pointer>
TrackList::getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
auto query {createTracksQuery(*session(), self()->id(), clusterIds)};
Wt::Dbo::collection<Track::pointer> collection = query
auto query {createTracksQuery(*session(), getId(), clusterIds)};
auto collection {query
.orderBy("COUNT(t.id) DESC")
.groupBy("t.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Track::pointer>(collection.begin(), collection.end())};
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -497,16 +491,16 @@ TrackList::getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range>
return res;
}
TrackListEntry::TrackListEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist, const Wt::WDateTime& dateTime)
TrackListEntry::TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime)
: _dateTime {Wt::WDateTime::fromTime_t(dateTime.toTime_t())} // force second resolution
, _track {track}
, _tracklist {tracklist}
, _track {getDboPtr(track)}
, _tracklist {getDboPtr(tracklist)}
{
assert(_dateTime.isValid());
}
TrackListEntry::pointer
TrackListEntry::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist, const Wt::WDateTime& dateTime)
TrackListEntry::create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime)
{
session.checkUniqueLocked();
assert(track);
@@ -519,11 +513,11 @@ TrackListEntry::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr
}
TrackListEntry::pointer
TrackListEntry::getById(Session& session, IdType id)
TrackListEntry::getById(Session& session, TrackListEntryId id)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackListEntry>().where("id = ?").bind(id);
return session.getDboSession().find<TrackListEntry>().where("id = ?").bind(id).resultValue();
}
} // namespace Database
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <type_traits>
#include <Wt/Dbo/StdSqlTraits.h>
#include "database/Types.hpp"
namespace Wt::Dbo
{
template<typename T>
struct sql_value_traits<T, typename std::enable_if<std::is_base_of<Database::IdType, T>::value>::type>
{
static_assert(!std::is_same_v<Database::IdType, T>, "Cannot use IdType, use derived types");
static const bool specialized = true;
static std::string type(SqlConnection *conn, int size)
{
return sql_value_traits<typename T::ValueType, void>::type(conn, size);
}
static void bind(const T& v, SqlStatement *statement, int column, int size)
{
sql_value_traits<typename T::ValueType>::bind(v.getValue(), statement, column, size);
}
static bool read(T& v, SqlStatement *statement, int column, int size)
{
typename T::ValueType value;
if (sql_value_traits<typename T::ValueType>::read(value, statement, column, size))
{
v = value;
return true;
}
v = {};
return false;
}
};
}
+41 -40
View File
@@ -26,25 +26,25 @@
#include "database/TrackList.hpp"
#include "utils/Logger.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
namespace Database {
AuthToken::AuthToken(const std::string& value, const Wt::WDateTime& expiry, Wt::Dbo::ptr<User> user)
AuthToken::AuthToken(const std::string& value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
: _value {value}
, _expiry {expiry}
, _user {user}
, _user {getDboPtr(user)}
{
}
AuthToken::pointer
AuthToken::create(Session& session, const std::string& value, const Wt::WDateTime& expiry, Wt::Dbo::ptr<User> user)
AuthToken::create(Session& session, const std::string& value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
{
session.checkUniqueLocked();
auto res {session.getDboSession().add(std::make_unique<AuthToken>(value, expiry, user))};
AuthToken::pointer res {session.getDboSession().add(std::make_unique<AuthToken>(value, expiry, user))};
session.getDboSession().flush();
return res;
@@ -65,7 +65,8 @@ AuthToken::getByValue(Session& session, const std::string& value)
session.checkSharedLocked();
return session.getDboSession().find<AuthToken>()
.where("value = ?").bind(value);
.where("value = ?").bind(value)
.resultValue();
}
static const std::string queuedListName {"__queued_tracks__"};
@@ -80,17 +81,17 @@ User::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<User>();
auto res {session.getDboSession().find<User>().resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<UserId>
User::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM user");
return std::vector<IdType>(res.begin(), res.end());
auto res {session.getDboSession().query<UserId>("SELECT id FROM user").resultList()};
return std::vector<UserId>(res.begin(), res.end());
}
User::pointer
@@ -98,8 +99,7 @@ User::getDemo(Session& session)
{
session.checkSharedLocked();
pointer res = session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO);
return res;
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
}
std::size_t
@@ -125,16 +125,17 @@ User::create(Session& session, std::string_view loginName)
}
User::pointer
User::getById(Session& session, IdType id)
User::getById(Session& session, UserId id)
{
return session.getDboSession().find<User>().where("id = ?").bind( id );
return session.getDboSession().find<User>().where("id = ?").bind(id).resultValue();
}
User::pointer
User::getByLoginName(Session& session, std::string_view name)
{
return session.getDboSession().find<User>()
.where("login_name = ?").bind(name);
.where("login_name = ?").bind(name)
.resultValue();
}
void
@@ -150,7 +151,7 @@ User::clearAuthTokens()
_authTokens.clear();
}
Wt::Dbo::ptr<TrackList>
TrackList::pointer
User::getQueuedTrackList(Session& session) const
{
assert(self());
@@ -160,63 +161,63 @@ User::getQueuedTrackList(Session& session) const
}
void
User::starArtist(Wt::Dbo::ptr<Artist> artist)
User::starArtist(ObjectPtr<Artist> artist)
{
if (_starredArtists.count(artist) == 0)
_starredArtists.insert(artist);
if (_starredArtists.count(getDboPtr(artist)) == 0)
_starredArtists.insert(getDboPtr(artist));
}
void
User::unstarArtist(Wt::Dbo::ptr<Artist> artist)
User::unstarArtist(ObjectPtr<Artist> artist)
{
if (_starredArtists.count(artist) != 0)
_starredArtists.erase(artist);
if (_starredArtists.count(getDboPtr(artist)) != 0)
_starredArtists.erase(getDboPtr(artist));
}
bool
User::hasStarredArtist(Wt::Dbo::ptr<Artist> artist) const
User::hasStarredArtist(ObjectPtr<Artist> artist) const
{
return _starredArtists.count(artist) != 0;
return _starredArtists.count(getDboPtr(artist)) != 0;
}
void
User::starRelease(Wt::Dbo::ptr<Release> release)
User::starRelease(ObjectPtr<Release> release)
{
if (_starredReleases.count(release) == 0)
_starredReleases.insert(release);
if (_starredReleases.count(getDboPtr(release)) == 0)
_starredReleases.insert(getDboPtr(release));
}
void
User::unstarRelease(Wt::Dbo::ptr<Release> release)
User::unstarRelease(ObjectPtr<Release> release)
{
if (_starredReleases.count(release) != 0)
_starredReleases.erase(release);
if (_starredReleases.count(getDboPtr(release)) != 0)
_starredReleases.erase(getDboPtr(release));
}
bool
User::hasStarredRelease(Wt::Dbo::ptr<Release> release) const
User::hasStarredRelease(ObjectPtr<Release> release) const
{
return _starredReleases.count(release) != 0;
return _starredReleases.count(getDboPtr(release)) != 0;
}
void
User::starTrack(Wt::Dbo::ptr<Track> track)
User::starTrack(ObjectPtr<Track> track)
{
if (_starredTracks.count(track) == 0)
_starredTracks.insert(track);
if (_starredTracks.count(getDboPtr(track)) == 0)
_starredTracks.insert(getDboPtr(track));
}
void
User::unstarTrack(Wt::Dbo::ptr<Track> track)
User::unstarTrack(ObjectPtr<Track> track)
{
if (_starredTracks.count(track) != 0)
_starredTracks.erase(track);
if (_starredTracks.count(getDboPtr(track)) != 0)
_starredTracks.erase(getDboPtr(track));
}
bool
User::hasStarredTrack(Wt::Dbo::ptr<Track> track) const
User::hasStarredTrack(ObjectPtr<Track> track) const
{
return _starredTracks.count(track) != 0;
return _starredTracks.count(getDboPtr(track)) != 0;
}
} // namespace Database
+24 -29
View File
@@ -22,17 +22,15 @@
#include <optional>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include <Wt/WDateTime.h>
#include <Wt/Dbo/Dbo.h>
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
#include "utils/UUID.hpp"
#include "Types.hpp"
namespace Database
{
@@ -44,10 +42,9 @@ class Track;
class TrackArtistLink;
class User;
class Artist : public Wt::Dbo::Dbo<Artist>
class Artist : public Object<Artist, ArtistId>
{
public:
enum class SortMethod
{
None,
@@ -55,43 +52,41 @@ class Artist : public Wt::Dbo::Dbo<Artist>
BySortName,
};
using pointer = Wt::Dbo::ptr<Artist>;
Artist() {}
Artist() = default;
Artist(const std::string& name, const std::optional<UUID>& MBID = {});
// Accessors
static pointer getByMBID(Session& session, const UUID& MBID);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, ArtistId id);
static std::vector<pointer> getByName(Session& session, const std::string& name); // exact match on name field
static std::vector<pointer> getByClusters(Session& session,
const std::set<IdType>& clusters, // at least one track that belongs to these clusters
const std::vector<ClusterId>& clusters, // at least one track that belongs to these clusters
SortMethod sortMethod
);
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, at least one artist that belongs to these clusters
const std::vector<ClusterId>& clusters, // if non empty, at least one artist that belongs to these clusters
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords (name + sort name fields)
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
SortMethod sortMethod,
std::optional<Range> range,
bool& moreExpected);
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod, std::optional<Range> range, bool& moreResults);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastWritten(Session& session,
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod, std::optional<Range> range, bool& moreResults);
static std::vector<ArtistId> getAllIds(Session& session);
static std::vector<ArtistId> getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusters,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
std::optional<Range>,
bool& moreResults);
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<ArtistId> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<pointer> getStarred(Session& session,
Wt::Dbo::ptr<User> user,
const std::set<IdType>& clusters,
ObjectPtr<User> user,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
SortMethod sortMethod,
std::optional<Range>, bool& moreResults);
@@ -101,12 +96,12 @@ class Artist : public Wt::Dbo::Dbo<Artist>
const std::string& getSortName() const { return _sortName; }
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
std::vector<Wt::Dbo::ptr<Release>> getReleases(const std::set<IdType>& clusterIds = {}) const; // if non empty, get the releases that match all these clusters
std::size_t getReleaseCount() const;
std::vector<Wt::Dbo::ptr<Track>> getTracks(std::optional<TrackArtistLinkType> linkType = {}) const;
std::vector<ObjectPtr<Release>> getReleases(const std::vector<ClusterId>& clusterIds = {}) const; // if non empty, get the releases that match all these clusters
std::size_t getReleaseCount() const;
std::vector<ObjectPtr<Track>> getTracks(std::optional<TrackArtistLinkType> linkType = {}) const;
bool hasNonReleaseTracks(std::optional<TrackArtistLinkType> linkType = std::nullopt) const;
std::vector<Wt::Dbo::ptr<Track>> getNonReleaseTracks(std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getRandomTracks(std::optional<std::size_t> count) const;
std::vector<ObjectPtr<Track>> getNonReleaseTracks(std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Track>> getRandomTracks(std::optional<std::size_t> count) const;
// No artistLinkTypes means get them all
std::vector<pointer> getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes = {}, std::optional<Range> range = std::nullopt) const;
@@ -114,14 +109,14 @@ class Artist : public Wt::Dbo::Dbo<Artist>
// Get the cluster of the tracks made by this artist
// Each clusters are grouped by cluster type, sorted by the number of occurence
// size is the max number of cluster per cluster type
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>> getClusterGroups(std::vector<Wt::Dbo::ptr<ClusterType>> clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(std::vector<ObjectPtr<ClusterType>> clusterTypes, std::size_t size) const;
void setName(std::string_view name) { _name = name; }
void setMBID(const std::optional<UUID>& mbid) { _MBID = mbid ? mbid->getAsString() : ""; }
void setSortName(const std::string& sortName);
// Create
static pointer create(Session& session, const std::string& name, const std::optional<UUID>& UUID = {});
static pointer create(Session& session, const std::string& name, const std::optional<UUID>& UUID = {});
template<class Action>
void persist(Action& a)
+15 -21
View File
@@ -24,10 +24,9 @@
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -36,31 +35,29 @@ class ClusterType;
class ScanSettings;
class Session;
class Cluster : public Wt::Dbo::Dbo<Cluster>
class Cluster : public Object<Cluster, ClusterId>
{
public:
using pointer = Wt::Dbo::ptr<Cluster>;
Cluster();
Cluster(Wt::Dbo::ptr<ClusterType> type, std::string_view name);
Cluster() = default;
Cluster(ObjectPtr<ClusterType> type, std::string_view name);
// Find utility
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAllOrphans(Session& session);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, ClusterId id);
// Create utility
static pointer create(Session& session, Wt::Dbo::ptr<ClusterType> type, std::string_view name);
static pointer create(Session& session, ObjectPtr<ClusterType> type, std::string_view name);
// Accessors
const std::string& getName() const { return _name; }
Wt::Dbo::ptr<ClusterType> getType() const { return _clusterType; }
ObjectPtr<ClusterType> getType() const { return _clusterType; }
std::size_t getTracksCount() const { return _tracks.size(); }
std::vector<Wt::Dbo::ptr<Track>> getTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> limit = {}) const;
std::set<IdType> getTrackIds() const;
std::vector<ObjectPtr<Track>> getTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> limit = {}) const;
std::vector<TrackId> getTrackIds() const;
std::size_t getReleasesCount() const;
void addTrack(Wt::Dbo::ptr<Track> track);
void addTrack(ObjectPtr<Track> track);
template<class Action>
void persist(Action& a)
@@ -72,7 +69,6 @@ class Cluster : public Wt::Dbo::Dbo<Cluster>
}
private:
static const std::size_t _maxNameLength = 128;
std::string _name;
@@ -82,19 +78,17 @@ class Cluster : public Wt::Dbo::Dbo<Cluster>
};
class ClusterType : public Wt::Dbo::Dbo<ClusterType>
class ClusterType : public Object<ClusterType, ClusterTypeId>
{
public:
ClusterType() = default;
ClusterType(std::string_view name);
using pointer = Wt::Dbo::ptr<ClusterType>;
ClusterType() {}
ClusterType(std::string name);
// Getters
static std::vector<pointer> getAllOrphans(Session& session);
static std::vector<pointer> getAllUsed(Session& session);
static pointer getByName(Session& session, const std::string& name);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, ClusterTypeId id);
static std::vector<pointer> getAll(Session& session);
static pointer create(Session& session, const std::string& name);
+20 -23
View File
@@ -20,13 +20,13 @@
#pragma once
#include <optional>
#include <set>
#include <vector>
#include <Wt/WDateTime.h>
#include <Wt/Dbo/Dbo.h>
#include "database/Types.hpp"
#include "utils/UUID.hpp"
#include "Types.hpp"
namespace Database
{
@@ -39,49 +39,46 @@ class Session;
class Track;
class User;
class Release : public Wt::Dbo::Dbo<Release>
class Release : public Object<Release, ReleaseId>
{
public:
using pointer = Wt::Dbo::ptr<Release>;
Release() {}
Release() = default;
Release(const std::string& name, const std::optional<UUID>& MBID = {});
// Accessors
static std::size_t getCount(Session& session);
static pointer getByMBID(Session& session, const UUID& MBID);
static std::vector<pointer> getByName(Session& session, const std::string& name);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, ReleaseId id);
static std::vector<pointer> getAllOrphans(Session& session); // no track related
static std::vector<pointer> getAll(Session& session, std::optional<Range> range = std::nullopt);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<ReleaseId> getAllIds(Session& session);
static std::vector<pointer> getAllOrderedByArtist(Session& session, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> size = {});
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> size = {});
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getAllRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<std::size_t> size = {});
static std::vector<ReleaseId> getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<std::size_t> size = {});
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getByYear(Session& session, int yearFrom, int yearTo, std::optional<Range> range = std::nullopt);
static std::vector<pointer> getStarred(Session& session, Wt::Dbo::ptr<User> user, const std::set<IdType>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getStarred(Session& session, ObjectPtr<User> user, const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getByClusters(Session& session, const std::set<IdType>& clusters);
static std::vector<pointer> getByClusters(Session& session, const std::vector<ClusterId>& clusters);
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, at least one release that belongs to these clusters
const std::vector<ClusterId>& clusters, // if non empty, at least one release that belongs to these clusters
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords
std::optional<Range> range,
bool& moreExpected);
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<ReleaseId> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
std::vector<Wt::Dbo::ptr<Track>> getTracks(const std::set<IdType>& clusters = std::set<IdType>()) const;
std::size_t getTracksCount() const;
Wt::Dbo::ptr<Track> getFirstTrack() const;
std::vector<ObjectPtr<Track>> getTracks(const std::vector<ClusterId>& clusters = {}) const;
std::size_t getTracksCount() const;
ObjectPtr<Track> getFirstTrack() const;
// Get the cluster of the tracks that belong to this release
// Each clusters are grouped by cluster type, sorted by the number of occurence (max to min)
// size is the max number of cluster per cluster type
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>> getClusterGroups(std::vector<Wt::Dbo::ptr<ClusterType>> clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
// Create
static pointer create(Session& session, const std::string& name, const std::optional<UUID>& MBID = {});
static pointer create(Session& session, const std::string& name, const std::optional<UUID>& MBID = {});
// Utility functions
std::optional<int> getReleaseYear(bool originalDate = false) const; // 0 if unknown or various
@@ -97,8 +94,8 @@ class Release : public Wt::Dbo::Dbo<Release>
Wt::WDateTime getLastWritten() const;
// Get the artists of this release
std::vector<Wt::Dbo::ptr<Artist> > getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
std::vector<Wt::Dbo::ptr<Artist> > getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
std::vector<ObjectPtr<Artist> > getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
std::vector<ObjectPtr<Artist> > getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
bool hasVariousArtists() const;
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
@@ -19,23 +19,22 @@
#pragma once
#include <unordered_set>
#include <filesystem>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WTime.h>
#include "utils/Path.hpp"
#include "database/Types.hpp"
namespace Database {
class ClusterType;
class Session;
class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
class ScanSettings : public Object<ScanSettings, ScanSettingsId>
{
public:
using pointer = Wt::Dbo::ptr<ScanSettings>;
// Do not modify values (just add)
enum class UpdatePeriod {
Never = 0,
@@ -61,8 +60,8 @@ class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
std::filesystem::path getMediaDirectory() const { return _mediaDirectory; }
Wt::WTime getUpdateStartTime() const { return _startTime; }
UpdatePeriod getUpdatePeriod() const { return _updatePeriod; }
std::vector<Wt::Dbo::ptr<ClusterType>> getClusterTypes() const;
std::unordered_set<std::filesystem::path> getAudioFileExtensions() const;
std::vector<ObjectPtr<ClusterType>> getClusterTypes() const;
std::vector<std::filesystem::path> getAudioFileExtensions() const;
RecommendationEngineType getRecommendationEngineType() const { return _recommendationEngineType; }
// Setters
+29 -32
View File
@@ -34,7 +34,7 @@
#include "utils/EnumSet.hpp"
#include "utils/UUID.hpp"
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -49,46 +49,43 @@ class TrackListEntry;
class TrackStats;
class User;
class Track : public Wt::Dbo::Dbo<Track>
class Track : public Object<Track, TrackId>
{
public:
using pointer = Wt::Dbo::ptr<Track>;
Track() {}
Track() = default;
Track(const std::filesystem::path& p);
// Find utility functions
static std::size_t getCount(Session& session);
static pointer getByPath(Session& session, const std::filesystem::path& p);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, TrackId id);
static std::vector<pointer> getByRecordingMBID(Session& session, const UUID& MBID);
static std::vector<pointer> getSimilarTracks(Session& session,
const std::unordered_set<IdType>& trackIds,
const std::vector<TrackId>& trackIds,
std::optional<std::size_t> offset = {},
std::optional<std::size_t> size = {});
static std::vector<pointer> getByClusters(Session& session,
const std::set<IdType>& clusters); // tracks that belong to these clusters
const std::vector<ClusterId>& clusters); // tracks that belong to these clusters
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, tracks that belong to these clusters
const std::vector<ClusterId>& clusters, // if non empty, tracks that belong to these clusters
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords
std::optional<Range> range,
bool& moreExpected);
static std::vector<pointer> getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName);
static std::vector<pointer> getAll(Session& session, std::optional<std::size_t> limit = std::nullopt);
static std::vector<pointer> getAllRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<std::pair<IdType, std::filesystem::path>> getAllPaths(Session& session, std::optional<std::size_t> offset = std::nullopt, std::optional<std::size_t> size = std::nullopt);
static std::vector<pointer> getAllRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<TrackId> getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<TrackId> getAllIds(Session& session);
static std::vector<std::pair<TrackId, std::filesystem::path>> getAllPaths(Session& session, std::optional<std::size_t> offset = std::nullopt, std::optional<std::size_t> size = std::nullopt);
static std::vector<pointer> getMBIDDuplicates(Session& session);
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getAllWithRecordingMBIDAndMissingFeatures(Session& session);
static std::vector<IdType> getAllIdsWithFeatures(Session& session, std::optional<std::size_t> limit = {});
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<TrackId> getAllIdsWithFeatures(Session& session, std::optional<std::size_t> limit = {});
static std::vector<TrackId> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<pointer> getStarred(Session& session,
Wt::Dbo::ptr<User> user,
const std::set<IdType>& clusters,
ObjectPtr<User> user,
const std::vector<ClusterId>& clusters,
std::optional<Range> range, bool& hasMore);
// Create utility
@@ -115,10 +112,10 @@ class Track : public Wt::Dbo::Dbo<Track>
void setTrackReplayGain(float replayGain) { _trackReplayGain = replayGain; }
void setReleaseReplayGain(float replayGain) { _releaseReplayGain = replayGain; }
void clearArtistLinks();
void addArtistLink(const Wt::Dbo::ptr<TrackArtistLink>& artistLink);
void setRelease(Wt::Dbo::ptr<Release> release) { _release = release; }
void setClusters(const std::vector<Wt::Dbo::ptr<Cluster>>& clusters );
void setFeatures(const Wt::Dbo::ptr<TrackFeatures>& features);
void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink);
void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); }
void setClusters(const std::vector<ObjectPtr<Cluster>>& clusters );
void setFeatures(const ObjectPtr<TrackFeatures>& features);
std::size_t getScanVersion() const { return _scanVersion; }
std::optional<std::size_t> getTrackNumber() const;
@@ -143,16 +140,16 @@ class Track : public Wt::Dbo::Dbo<Track>
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
// no artistLinkTypes means get all
std::vector<Wt::Dbo::ptr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<IdType> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<Wt::Dbo::ptr<TrackArtistLink>> getArtistLinks() const;
Wt::Dbo::ptr<Release> getRelease() const { return _release; }
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
std::vector<IdType> getClusterIds() const;
bool hasTrackFeatures() const;
Wt::Dbo::ptr<TrackFeatures> getTrackFeatures() const;
std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const;
ObjectPtr<Release> getRelease() const { return _release; }
std::vector<ObjectPtr<Cluster>> getClusters() const;
std::vector<ClusterId> getClusterIds() const;
bool hasTrackFeatures() const;
ObjectPtr<TrackFeatures> getTrackFeatures() const;
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>> getClusterGroups(std::vector<Wt::Dbo::ptr<ClusterType>> clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
template<class Action>
void persist(Action& a)
@@ -23,7 +23,7 @@
#include <Wt/Dbo/Dbo.h>
#include "Types.hpp"
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
namespace Database
@@ -33,20 +33,18 @@ namespace Database
class Session;
class Track;
class TrackArtistLink
class TrackArtistLink : public Object<TrackArtistLink, TrackArtistLinkId>
{
public:
using pointer = Wt::Dbo::ptr<TrackArtistLink>;
TrackArtistLink() = default;
TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type);
TrackArtistLink(ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type);
static pointer create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type);
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type);
static EnumSet<TrackArtistLinkType> getUsedTypes(Session& session);
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
Wt::Dbo::ptr<Artist> getArtist() const { return _artist; }
ObjectPtr<Track> getTrack() const { return _track; }
ObjectPtr<Artist> getArtist() const { return _artist; }
TrackArtistLinkType getType() const { return _type; }
template<class Action>
@@ -23,7 +23,7 @@
#include <Wt/Dbo/Dbo.h>
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -31,22 +31,20 @@ class Session;
class Track;
class User;
class TrackBookmark : public Wt::Dbo::Dbo<TrackBookmark>
class TrackBookmark : public Object<TrackBookmark, TrackBookmarkId>
{
public:
using pointer = Wt::Dbo::ptr<TrackBookmark>;
TrackBookmark () = default;
TrackBookmark(Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
TrackBookmark(ObjectPtr<User> user, ObjectPtr<Track> track);
// utility
static pointer create(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
static pointer create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track);
// Find utility functions
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getByUser(Session& session, Wt::Dbo::ptr<User> user);
static pointer getByUser(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
static pointer getById(Session& session, IdType id);
static std::vector<pointer> getByUser(Session& session, ObjectPtr<User> user);
static pointer getByUser(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track);
static pointer getById(Session& session, TrackBookmarkId id);
// Setters
void setOffset(std::chrono::milliseconds offset) { _offset = offset; }
@@ -55,8 +53,8 @@ class TrackBookmark : public Wt::Dbo::Dbo<TrackBookmark>
// Getters
std::chrono::milliseconds getOffset() const { return _offset; }
std::string_view getComment() const { return _comment; }
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
Wt::Dbo::ptr<User> getUser() const { return _user; }
ObjectPtr<Track> getTrack() const { return _track; }
ObjectPtr<User> getUser() const { return _user; }
template<class Action>
void persist(Action& a)
@@ -26,7 +26,7 @@
#include <Wt/Dbo/Dbo.h>
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -37,17 +37,14 @@ using FeatureName = std::string;
using FeatureValues = std::vector<double>;
using FeatureValuesMap = std::unordered_map<FeatureName, FeatureValues>;
class TrackFeatures : public Wt::Dbo::Dbo<TrackFeatures>
class TrackFeatures : public Object<TrackFeatures, TrackFeaturesId>
{
public:
using pointer = Wt::Dbo::ptr<TrackFeatures>;
TrackFeatures() = default;
TrackFeatures(Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures);
TrackFeatures(ObjectPtr<Track> track, const std::string& jsonEncodedFeatures);
// Create utility
static pointer create(Session& session, Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures);
static pointer create(Session& session, ObjectPtr<Track> track, const std::string& jsonEncodedFeatures);
FeatureValues getFeatureValues(const FeatureName& feature) const;
FeatureValuesMap getFeatureValuesMap(const std::unordered_set<FeatureName>& featureNames) const;
@@ -27,7 +27,7 @@
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -39,11 +39,9 @@ class Track;
class TrackListEntry;
class User;
class TrackList : public Wt::Dbo::Dbo<TrackList>
class TrackList : public Object<TrackList, TrackListId>
{
public:
using pointer = Wt::Dbo::ptr<TrackList>;
enum class Type
{
Playlist, // user controlled playlists
@@ -51,28 +49,28 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
};
TrackList() = default;
TrackList(std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<User> user);
// Stats utility
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Release>> getTopReleases(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Artist>> getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Release>> getTopReleases(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Track>> getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
// Search utility
static pointer get(Session& session, std::string_view name, Type type, Wt::Dbo::ptr<User> user);
static pointer getById(Session& session, IdType tracklistId);
static pointer get(Session& session, std::string_view name, Type type, ObjectPtr<User> user);
static pointer getById(Session& session, TrackListId tracklistId);
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAll(Session& session, Wt::Dbo::ptr<User> user);
static std::vector<pointer> getAll(Session& session, Wt::Dbo::ptr<User> user, Type type);
static std::vector<pointer> getAll(Session& session, ObjectPtr<User> user);
static std::vector<pointer> getAll(Session& session, ObjectPtr<User> user, Type type);
// Create utility
static pointer create(Session& session, std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
static pointer create(Session& session, std::string_view name, Type type, bool isPublic, ObjectPtr<User> user);
// Accessors
std::string getName() const { return _name; }
bool isPublic() const { return _isPublic; }
Type getType() const { return _type; }
Wt::Dbo::ptr<User> getUser() const { return _user; }
ObjectPtr<User> getUser() const { return _user; }
// Modifiers
void setName(const std::string& name) { _name = name; }
@@ -80,29 +78,29 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
void clear() { _entries.clear(); }
// Get tracks, ordered by position
bool isEmpty() const;
std::size_t getCount() const;
Wt::Dbo::ptr<TrackListEntry> getEntry(std::size_t pos) const;
std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntries(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
Wt::Dbo::ptr<TrackListEntry> getEntryByTrackAndDateTime(Wt::Dbo::ptr<Track> track, const Wt::WDateTime& dateTime) const;
bool isEmpty() const;
std::size_t getCount() const;
ObjectPtr<TrackListEntry> getEntry(std::size_t pos) const;
std::vector<ObjectPtr<TrackListEntry>> getEntries(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
ObjectPtr<TrackListEntry> getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const;
// Get track bya
std::vector<Wt::Dbo::ptr<Artist>> getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Release>> getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Artist>> getArtistsReverse(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Release>> getReleasesReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Track>> getTracksReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<IdType> getTrackIds() const;
std::vector<TrackId> getTrackIds() const;
std::chrono::milliseconds getDuration() const;
// Get clusters, order by occurence
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
std::vector<ObjectPtr<Cluster>> getClusters() const;
bool hasTrack(IdType trackId) const;
bool hasTrack(TrackId trackId) const;
// Ordered from most clusters in common
std::vector<Wt::Dbo::ptr<Track>> getSimilarTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
std::vector<ObjectPtr<Track>> getSimilarTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
template<class Action>
void persist(Action& a)
@@ -122,27 +120,24 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
bool _isPublic {false};
Wt::Dbo::ptr<User> _user;
Wt::Dbo::collection< Wt::Dbo::ptr<TrackListEntry> > _entries;
Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> _entries;
};
class TrackListEntry : public Wt::Dbo::Dbo<TrackListEntry>
class TrackListEntry : public Object<TrackListEntry, TrackListEntryId>
{
public:
using pointer = Wt::Dbo::ptr<TrackListEntry>;
TrackListEntry() = default;
TrackListEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist, const Wt::WDateTime& dateTime);
TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime);
// find utility
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, TrackListEntryId id);
// Create utility
static pointer create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist, const Wt::WDateTime& dateTime = Wt::WDateTime::currentDateTime());
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime = Wt::WDateTime::currentDateTime());
// Accessors
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
ObjectPtr<Track> getTrack() const { return _track; }
const Wt::WDateTime& getDateTime() const { return _dateTime; }
template<class Action>
+99 -5
View File
@@ -20,16 +20,32 @@
#pragma once
#include <cstdint>
#include <cassert>
#include <functional>
#include <Wt/Dbo/ptr.h>
namespace Database
{
using IdType = Wt::Dbo::dbo_default_traits::IdType;
static inline bool IdIsValid(IdType id)
class IdType
{
return id != Wt::Dbo::dbo_default_traits::invalidId();
}
public:
using ValueType = Wt::Dbo::dbo_default_traits::IdType;
IdType() = default;
IdType(ValueType id) : _id {id} { assert(isValid()); }
bool isValid() const { return _id != Wt::Dbo::dbo_default_traits::invalidId(); }
std::string toString() const { assert(isValid()); return std::to_string(_id); }
ValueType getValue() const { return _id; }
bool operator==(IdType other) const { return other._id == _id; }
bool operator!=(IdType other) const { return !(*this == other); }
bool operator<(IdType other) const { return other._id < _id; }
private:
Wt::Dbo::dbo_default_traits::IdType _id {Wt::Dbo::dbo_default_traits::invalidId()};
};
struct Range
{
@@ -78,5 +94,83 @@ namespace Database
ADMIN = 1,
DEMO = 2,
};
template <typename T>
class ObjectPtr
{
public:
ObjectPtr() = default;
ObjectPtr(Wt::Dbo::ptr<T> obj) : _obj {obj} {}
const T* operator->() const { return _obj.get(); }
operator bool() const { return _obj.get(); }
bool operator!() const { return !_obj.get(); }
auto modify() { return _obj.modify(); }
void remove() { _obj.remove(); }
private:
template <typename, typename> friend class Object;
Wt::Dbo::ptr<T> _obj;
};
template <typename T, typename ObjectIdType>
class Object : public Wt::Dbo::Dbo<T>
{
static_assert(std::is_base_of_v<Database::IdType, ObjectIdType>);
static_assert(!std::is_same_v<Database::IdType, ObjectIdType>);
public:
using pointer = ObjectPtr<T>;
using IdType = ObjectIdType;
IdType getId() const { return Wt::Dbo::Dbo<T>::self()->Wt::Dbo::Dbo<T>::id(); }
// catch some misuses
typename Wt::Dbo::dbo_traits<T>::IdType id() const = delete;
protected:
// Can get raw dbo ptr only from Objects
template <typename SomeObject>
static
Wt::Dbo::ptr<SomeObject> getDboPtr(ObjectPtr<SomeObject> ptr) { return ptr._obj; }
};
}
// TODO factorize hash with std::enable_if
#define LMS_DECLARE_IDTYPE(name) \
namespace Database { \
class name : public IdType \
{ \
public: \
using IdType::IdType; \
};\
} \
namespace std \
{ \
template<> \
class hash<Database::name> \
{ \
public: \
size_t operator()(Database::name id) const \
{ \
return std::hash<Database::name::ValueType>()(id.getValue()); \
} \
}; \
} // ns std
LMS_DECLARE_IDTYPE(ArtistId)
LMS_DECLARE_IDTYPE(AuthTokenId)
LMS_DECLARE_IDTYPE(ClusterId)
LMS_DECLARE_IDTYPE(ClusterTypeId)
LMS_DECLARE_IDTYPE(ReleaseId)
LMS_DECLARE_IDTYPE(ScanSettingsId)
LMS_DECLARE_IDTYPE(TrackArtistLinkId)
LMS_DECLARE_IDTYPE(TrackBookmarkId)
LMS_DECLARE_IDTYPE(TrackFeaturesId)
LMS_DECLARE_IDTYPE(TrackId)
LMS_DECLARE_IDTYPE(TrackListId)
LMS_DECLARE_IDTYPE(TrackListEntryId)
LMS_DECLARE_IDTYPE(UserId)
+19 -25
View File
@@ -26,8 +26,8 @@
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "database/Types.hpp"
#include "utils/UUID.hpp"
#include "Types.hpp"
namespace Database {
@@ -39,24 +39,21 @@ class TrackList;
class Track;
class User;
class AuthToken
class AuthToken : public Object<AuthToken, AuthTokenId>
{
public:
using pointer = Wt::Dbo::ptr<AuthToken>;
AuthToken() = default;
AuthToken(const std::string& value, const Wt::WDateTime& expiry, Wt::Dbo::ptr<User> user);
AuthToken(const std::string& value, const Wt::WDateTime& expiry, ObjectPtr<User> user);
// Utility
static pointer create(Session& session, const std::string& value, const Wt::WDateTime&expiry, Wt::Dbo::ptr<User> user);
static pointer create(Session& session, const std::string& value, const Wt::WDateTime&expiry, ObjectPtr<User> user);
static void removeExpiredTokens(Session& session, const Wt::WDateTime& now);
static pointer getByValue(Session& session, const std::string& value);
static pointer getById(Session& session, IdType tokenId);
static pointer getById(Session& session, AuthTokenId tokenId);
// Accessors
const Wt::WDateTime& getExpiry() const { return _expiry; }
Wt::Dbo::ptr<User> getUser() const { return _user; }
ObjectPtr<User> getUser() const { return _user; }
const std::string& getValue() const { return _value; }
template<class Action>
@@ -75,11 +72,9 @@ class AuthToken
Wt::Dbo::ptr<User> _user;
};
class User : public Wt::Dbo::Dbo<User>
class User : public Object<User, UserId>
{
public:
using pointer = Wt::Dbo::ptr<User>;
struct PasswordHash
{
std::string salt;
@@ -120,17 +115,16 @@ class User : public Wt::Dbo::Dbo<User>
static inline const SubsonicArtistListMode defaultSubsonicArtistListMode {SubsonicArtistListMode::AllArtists};
static inline const Scrobbler defaultScrobbler {Scrobbler::Internal};
User() = default;
User(std::string_view loginName);
// utility
static pointer create(Session& session, std::string_view loginName);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, UserId id);
static pointer getByLoginName(Session& session, std::string_view loginName);
static std::vector<pointer> getAll(Session& session);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<UserId> getAllIds(Session& session);
static pointer getDemo(Session& session);
static std::size_t getCount(Session& session);
@@ -171,20 +165,20 @@ class User : public Wt::Dbo::Dbo<User>
Scrobbler getScrobbler() const { return _scrobbler; }
std::optional<UUID> getListenBrainzToken() const { return UUID::fromString(_listenbrainzToken); }
Wt::Dbo::ptr<TrackList> getQueuedTrackList(Session& session) const;
ObjectPtr<TrackList> getQueuedTrackList(Session& session) const;
void starArtist(Wt::Dbo::ptr<Artist> artist);
void unstarArtist(Wt::Dbo::ptr<Artist> artist);
bool hasStarredArtist(Wt::Dbo::ptr<Artist> artist) const;
void starArtist(ObjectPtr<Artist> artist);
void unstarArtist(ObjectPtr<Artist> artist);
bool hasStarredArtist(ObjectPtr<Artist> artist) const;
void starRelease(Wt::Dbo::ptr<Release> release);
void unstarRelease(Wt::Dbo::ptr<Release> release);
bool hasStarredRelease(Wt::Dbo::ptr<Release> release) const;
void starRelease(ObjectPtr<Release> release);
void unstarRelease(ObjectPtr<Release> release);
bool hasStarredRelease(ObjectPtr<Release> release) const;
// Stars
void starTrack(Wt::Dbo::ptr<Track> track);
void unstarTrack(Wt::Dbo::ptr<Track> track);
bool hasStarredTrack(Wt::Dbo::ptr<Track> track) const;
void starTrack(ObjectPtr<Track> track);
void unstarTrack(ObjectPtr<Track> track);
bool hasStarredTrack(ObjectPtr<Track> track) const;
template<class Action>
void persist(Action& a)
+2 -2
View File
@@ -1,8 +1,8 @@
add_library(lmsrecommendation SHARED
impl/clusters/ClustersClassifier.cpp
impl/features/FeaturesClassifierCache.cpp
impl/features/FeaturesClassifier.cpp
impl/features/FeaturesEngineCache.cpp
impl/features/FeaturesEngine.cpp
impl/features/FeaturesDefs.cpp
impl/Engine.cpp
)
+14 -17
View File
@@ -23,7 +23,7 @@
#include <vector>
#include "ClustersClassifierCreator.hpp"
#include "FeaturesClassifierCreator.hpp"
#include "FeaturesEngineCreator.hpp"
#include "database/Db.hpp"
#include "database/Session.hpp"
@@ -45,7 +45,7 @@ createClassifier(ClassifierType type)
break;
case ClassifierType::Features:
return createFeaturesClassifier();
return createFeaturesEngine();
break;
}
@@ -63,10 +63,10 @@ Engine::Engine(Database::Db& db)
{
}
std::unordered_set<Database::IdType>
Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::IdType trackListId, std::size_t maxCount)
Engine::TrackContainer
Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId trackListId, std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
TrackContainer res;
std::shared_lock lock {_classifiersMutex};
for (const auto& classifierName : _classifierPriorities)
@@ -83,10 +83,10 @@ Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::IdTy
return res;
}
std::unordered_set<Database::IdType>
Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<Database::IdType>& trackIds, std::size_t maxCount)
Engine::TrackContainer
Engine::getSimilarTracks(Database::Session& dbSession, const std::vector<Database::TrackId>& trackIds, std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
TrackContainer res;
std::shared_lock lock {_classifiersMutex};
for (ClassifierType classifierType : _classifierPriorities)
@@ -107,10 +107,10 @@ Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<
return res;
}
std::unordered_set<Database::IdType>
Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount)
Engine::ReleaseContainer
Engine::getSimilarReleases(Database::Session& dbSession, Database::ReleaseId releaseId, std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
ReleaseContainer res;
std::shared_lock lock {_classifiersMutex};
for (ClassifierType classifierType : _classifierPriorities)
@@ -131,13 +131,10 @@ Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releas
return res;
}
std::unordered_set<Database::IdType>
Engine::getSimilarArtists(Database::Session& dbSession,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount)
Engine::ArtistContainer
Engine::getSimilarArtists(Database::Session& dbSession, Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
ArtistContainer res;
std::shared_lock lock {_classifiersMutex};
for (ClassifierType classifierType : _classifierPriorities)
+6 -6
View File
@@ -55,12 +55,13 @@ namespace Recommendation
private:
void load(bool forceReload, const ProgressCallback& progressCallback) override;
void cancelLoad() override;
void requestCancelLoad() override {};
ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) override;
ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) override;
ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) override;
ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
ResultContainer<Database::TrackId> getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) override;
ResultContainer<Database::TrackId> getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) override;
ResultContainer<Database::ReleaseId> getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) override;
ResultContainer<Database::ArtistId> getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) override;
@@ -79,7 +80,6 @@ namespace Recommendation
using ClassifierContainer = std::unordered_map<ClassifierType, std::unique_ptr<IClassifier>>;
ClassifierContainer _classifiers;
std::vector<ClassifierType> _classifierPriorities; // ordered by priority
};
} // ns Recommendation
@@ -24,6 +24,6 @@
namespace Recommendation
{
std::unique_ptr<IClassifier> createFeaturesClassifier();
std::unique_ptr<IClassifier> createFeaturesEngine();
}
+10 -8
View File
@@ -21,9 +21,10 @@
#include <functional>
#include <string_view>
#include <unordered_set>
#include <vector>
#include "database/Types.hpp"
#include "recommendation/IRecommendation.hpp"
#include "utils/EnumSet.hpp"
namespace Database
@@ -34,7 +35,7 @@ namespace Database
namespace Recommendation
{
class IClassifier
class IClassifier : public IRecommendation
{
public:
virtual ~IClassifier() = default;
@@ -50,13 +51,14 @@ namespace Recommendation
virtual bool load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback) = 0;
virtual void requestCancelLoad() = 0;
using ResultContainer = std::unordered_set<Database::IdType>;
template <typename IdType>
using ResultContainer = std::vector<IdType>;
virtual ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
virtual ResultContainer<Database::TrackId> getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) const = 0;
virtual ResultContainer<Database::TrackId> getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const = 0;
virtual ResultContainer<Database::ReleaseId> getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) const = 0;
virtual ResultContainer<Database::ArtistId> getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const = 0;
};
@@ -33,23 +33,23 @@ std::unique_ptr<IClassifier> createClustersClassifier()
return std::make_unique<ClusterClassifier>();
}
std::unordered_set<Database::IdType>
ClusterClassifier::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<Database::IdType>& trackIds, std::size_t maxCount) const
IClassifier::ResultContainer<Database::TrackId>
ClusterClassifier::getSimilarTracks(Database::Session& dbSession, const std::vector<Database::TrackId>& trackIds, std::size_t maxCount) const
{
ResultContainer<Database::TrackId> res;
auto transaction {dbSession.createSharedTransaction()};
const auto tracks {Database::Track::getSimilarTracks(dbSession, trackIds, 0, maxCount)};
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const auto& track) { return track->getId(); });
std::unordered_set<Database::IdType> res;
std::transform(std::cbegin(tracks), std::cend(tracks), std::inserter(res, std::end(res)),
[](const auto& track) { return track.id(); });
return res;
}
std::unordered_set<Database::IdType>
ClusterClassifier::getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const
IClassifier::ResultContainer<Database::TrackId>
ClusterClassifier::getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
ResultContainer<Database::TrackId> res;
auto transaction {session.createSharedTransaction()};
@@ -58,16 +58,15 @@ ClusterClassifier::getSimilarTracksFromTrackList(Database::Session& session, Dat
return res;
const auto tracks {trackList->getSimilarTracks(0, maxCount)};
std::transform(std::cbegin(tracks), std::cend(tracks), std::inserter(res, std::end(res)),
[](const Database::Track::pointer& track) { return track.id(); });
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const auto& track) { return track->getId(); });
return res;
}
std::unordered_set<Database::IdType>
ClusterClassifier::getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount) const
IClassifier::ResultContainer<Database::ReleaseId>
ClusterClassifier::getSimilarReleases(Database::Session& dbSession, Database::ReleaseId releaseId, std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
ResultContainer<Database::ReleaseId> res;
auto transaction {dbSession.createSharedTransaction()};
@@ -76,19 +75,18 @@ ClusterClassifier::getSimilarReleases(Database::Session& dbSession, Database::Id
return res;
const auto releases {release->getSimilarReleases(0, maxCount)};
std::transform(std::cbegin(releases), std::cend(releases), std::inserter(res, std::end(res)),
[](const auto& release) { return release.id(); });
std::transform(std::cbegin(releases), std::cend(releases), std::back_inserter(res), [](const auto& release) { return release->getId(); });
return res;
}
std::unordered_set<Database::IdType>
IClassifier::ResultContainer<Database::ArtistId>
ClusterClassifier::getSimilarArtists(Database::Session& dbSession,
Database::IdType artistId,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> artistLinkTypes,
std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
ResultContainer<Database::ArtistId> res;
auto transaction {dbSession.createSharedTransaction()};
@@ -97,8 +95,7 @@ ClusterClassifier::getSimilarArtists(Database::Session& dbSession,
return res;
const auto artists {artist->getSimilarArtists(artistLinkTypes, Database::Range {0, maxCount})};
std::transform(std::cbegin(artists), std::cend(artists), std::inserter(res, std::end(res)),
[](const auto& artist) { return artist.id(); });
std::transform(std::cbegin(artists), std::cend(artists), std::back_inserter(res), [](const auto& artist) { return artist->getId(); });
return res;
}
@@ -40,11 +40,11 @@ namespace Recommendation
bool load(Database::Session&, bool, const ProgressCallback&) override { return true; }
void requestCancelLoad() override {}
ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override;
ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override;
ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const override;
ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
ResultContainer<Database::TrackId> getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) const override;
ResultContainer<Database::TrackId> getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const override;
ResultContainer<Database::ReleaseId> getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) const override;
ResultContainer<Database::ArtistId> getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const override;
};
@@ -1,117 +0,0 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <functional>
#include <unordered_map>
#include <optional>
#include <string>
#include "som/DataNormalizer.hpp"
#include "som/Network.hpp"
#include "FeaturesClassifierCache.hpp"
#include "FeaturesDefs.hpp"
#include "IClassifier.hpp"
namespace Database
{
class Session;
}
namespace Recommendation {
using FeatureWeight = double;
class FeaturesClassifier : public IClassifier
{
public:
FeaturesClassifier() = default;
FeaturesClassifier(const FeaturesClassifier&) = delete;
FeaturesClassifier(FeaturesClassifier&&) = delete;
FeaturesClassifier& operator=(const FeaturesClassifier&) = delete;
FeaturesClassifier& operator=(FeaturesClassifier&&) = delete;
using FeaturesFetchFunc = std::function<std::optional<std::unordered_map<std::string, std::vector<double>>>(Database::IdType /*trackId*/, const std::unordered_set<std::string>& /*features*/)>;
// Default is to retrieve the features from the database (may be slow).
// Use this only if you want to train different searchers with some cached data
static void setFeaturesFetchFunc(FeaturesFetchFunc func) { _featuresFetchFunc = func; }
static const FeatureSettingsMap& getDefaultTrainFeatureSettings();
private:
std::string_view getName() const override { return "Features"; }
bool load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback) override;
void requestCancelLoad() override;
std::unordered_set<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const override;
bool loadFromCache(Database::Session& session, const FeaturesClassifierCache& cache);
// Use training (may be very slow)
struct TrainSettings
{
std::size_t iterationCount {10};
float sampleCountPerNeuron {4};
FeatureSettingsMap featureSettingsMap;
};
bool loadFromTraining(Database::Session& session, const TrainSettings& trainSettings, const ProgressCallback& progressCallback);
using ObjectPositions = std::unordered_map<Database::IdType, std::unordered_set<SOM::Position>>;
using MatrixOfObjects = SOM::Matrix<std::unordered_set<Database::IdType>>;
bool load(Database::Session& session,
SOM::Network network,
const ObjectPositions& tracksPosition);
FeaturesClassifierCache toCache() const;
static std::unordered_set<SOM::Position> getMatchingRefVectorsPosition(const std::unordered_set<Database::IdType>& ids, const ObjectPositions& objectPositions);
static std::unordered_set<Database::IdType> getObjectsIds(const std::unordered_set<SOM::Position>& positionSet, const MatrixOfObjects& objectsMap);
std::unordered_set<Database::IdType> getSimilarObjects(const std::unordered_set<Database::IdType>& ids,
const SOM::Matrix<std::unordered_set<Database::IdType>>& objectsMap,
const ObjectPositions& objectPosition,
std::size_t maxCount) const;
bool _loadCancelled {};
std::unique_ptr<SOM::Network> _network;
double _networkRefVectorsDistanceMedian {};
ObjectPositions _artistPositions;
std::unordered_map<Database::TrackArtistLinkType, MatrixOfObjects> _artistsMap;
MatrixOfObjects _releasesMap;
ObjectPositions _releasePositions;
MatrixOfObjects _tracksMap;
ObjectPositions _trackPositions;
static inline FeaturesFetchFunc _featuresFetchFunc;
};
} // ns Recommendation
@@ -17,7 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FeaturesClassifier.hpp"
#include "FeaturesEngine.hpp"
#include <numeric>
@@ -35,13 +35,13 @@
namespace Recommendation {
std::unique_ptr<IClassifier> createFeaturesClassifier()
std::unique_ptr<IClassifier> createFeaturesEngine()
{
return std::make_unique<FeaturesClassifier>();
return std::make_unique<FeaturesEngine>();
}
const FeatureSettingsMap&
FeaturesClassifier::getDefaultTrainFeatureSettings()
FeaturesEngine::getDefaultTrainFeatureSettings()
{
static const FeatureSettingsMap defaultTrainFeatureSettings
{
@@ -57,16 +57,16 @@ FeaturesClassifier::getDefaultTrainFeatureSettings()
static
std::optional<FeatureValuesMap>
getTrackFeatureValues(FeaturesClassifier::FeaturesFetchFunc func, Database::IdType trackId, const std::unordered_set<FeatureName>& featureNames)
getTrackFeatureValues(FeaturesEngine::FeaturesFetchFunc func, Database::TrackId trackId, const std::unordered_set<FeatureName>& featureNames)
{
return func(trackId, featureNames);
}
static
std::optional<FeatureValuesMap>
getTrackFeatureValuesFromDb(Database::Session& session, Database::IdType trackId, const std::unordered_set<FeatureName>& featureNames)
getTrackFeatureValuesFromDb(Database::Session& session, Database::TrackId trackId, const std::unordered_set<FeatureName>& featureNames)
{
auto func = [&](Database::IdType trackId, const std::unordered_set<FeatureName>& featureNames)
auto func = [&](Database::TrackId trackId, const std::unordered_set<FeatureName>& featureNames)
{
std::optional<FeatureValuesMap> res;
@@ -128,7 +128,7 @@ getInputVectorWeights(const FeatureSettingsMap& featureSettingsMap, std::size_t
}
bool
FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSettings& trainSettings, const ProgressCallback& progressCallback)
FeaturesEngine::loadFromTraining(Database::Session& session, const TrainSettings& trainSettings, const ProgressCallback& progressCallback)
{
LMS_LOG(RECOMMENDATION, INFO) << "Constructing features classifier...";
@@ -141,7 +141,7 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
LMS_LOG(RECOMMENDATION, DEBUG) << "Features dimension = " << nbDimensions;
std::vector<Database::IdType> trackIds;
std::vector<Database::TrackId> trackIds;
{
auto transaction {session.createSharedTransaction()};
@@ -151,13 +151,13 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
}
std::vector<SOM::InputVector> samples;
std::vector<Database::IdType> samplesTrackIds;
std::vector<Database::TrackId> samplesTrackIds;
samples.reserve(trackIds.size());
samplesTrackIds.reserve(trackIds.size());
LMS_LOG(RECOMMENDATION, DEBUG) << "Extracting features...";
for (Database::IdType trackId : trackIds)
for (Database::TrackId trackId : trackIds)
{
if (_loadCancelled)
return false;
@@ -223,7 +223,7 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
return false;
LMS_LOG(RECOMMENDATION, DEBUG) << "Classifying tracks...";
ObjectPositions trackPositions;
TrackPositions trackPositions;
for (std::size_t i {}; i < samples.size(); ++i)
{
if (_loadCancelled)
@@ -231,7 +231,7 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
const SOM::Position position {network.getClosestRefVectorPosition(samples[i])};
trackPositions[samplesTrackIds[i]].insert(position);
trackPositions[samplesTrackIds[i]].push_back(position);
}
LMS_LOG(RECOMMENDATION, DEBUG) << "Classifying tracks DONE";
@@ -240,28 +240,25 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
}
bool
FeaturesClassifier::loadFromCache(Database::Session& session, const FeaturesClassifierCache& cache)
FeaturesEngine::loadFromCache(Database::Session& session, const FeaturesEngineCache& cache)
{
LMS_LOG(RECOMMENDATION, INFO) << "Constructing features classifier from cache...";
return load(session, std::move(cache._network), cache._trackPositions);
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarTracksFromTrackList(Database::Session& session, Database::IdType trackListId, std::size_t maxCount) const
IClassifier::ResultContainer<Database::TrackId>
FeaturesEngine::getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId trackListId, std::size_t maxCount) const
{
const std::unordered_set<Database::IdType> trackIds {[&]
const std::vector<Database::TrackId> trackIds {[&]
{
std::unordered_set<Database::IdType> res;
std::vector<Database::TrackId> res;
auto transaction {session.createSharedTransaction()};
const Database::TrackList::pointer trackList {Database::TrackList::getById(session, trackListId)};
if (trackList)
{
const std::vector<Database::IdType> orderedTrackIds {trackList->getTrackIds()};
res = std::unordered_set<Database::IdType>(std::cbegin(orderedTrackIds), std::cend(orderedTrackIds));
}
res = trackList->getTrackIds();
return res;
}()};
@@ -269,72 +266,64 @@ FeaturesClassifier::getSimilarTracksFromTrackList(Database::Session& session, Da
return getSimilarTracks(session, trackIds, maxCount);
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksIds, std::size_t maxCount) const
std::vector<Database::TrackId>
FeaturesEngine::getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksIds, std::size_t maxCount) const
{
auto similarTrackIds {getSimilarObjects(tracksIds, _tracksMap, _trackPositions, maxCount)};
if (!similarTrackIds.empty())
auto similarTrackIds {getSimilarObjects(tracksIds, _trackMatrix, _trackPositions, maxCount)};
{
// Report only existing ids
// Report only existing ids, as tracks may have been removed a long time ago (refreshing the SOM takes some time)
auto transaction {session.createSharedTransaction()};
for (auto it {std::begin(similarTrackIds)}; it != std::end(similarTrackIds);)
{
const Database::IdType trackId {*it};
if (!Database::Track::getById(session, trackId))
it = similarTrackIds.erase(it);
else
it++;
}
similarTrackIds.erase(std::remove_if(std::begin(similarTrackIds), std::end(similarTrackIds),
[&](Database::TrackId trackId)
{
return Database::Track::getById(session, trackId); // TODO exists
}), std::end(similarTrackIds));
}
return similarTrackIds;
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const
std::vector<Database::ReleaseId>
FeaturesEngine::getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) const
{
auto similarReleaseIds {getSimilarObjects({releaseId}, _releasesMap, _releasePositions, maxCount)};
if (!similarReleaseIds.empty())
auto similarReleaseIds {getSimilarObjects<Database::ReleaseId>({releaseId}, _releaseMatrix, _releasePositions, maxCount)};
{
// Report only existing ids
auto transaction {session.createSharedTransaction()};
for (auto it {std::begin(similarReleaseIds)}; it != std::end(similarReleaseIds);)
{
const Database::IdType similarReleaseId {*it};
if (!Database::Release::getById(session, similarReleaseId))
it = similarReleaseIds.erase(it);
else
it++;
}
similarReleaseIds.erase(std::remove_if(std::begin(similarReleaseIds), std::end(similarReleaseIds),
[&](Database::ReleaseId releaseId)
{
return Database::Release::getById(session, releaseId); // TODO exists
}), std::end(similarReleaseIds));
}
return similarReleaseIds;
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarArtists(Database::Session& session,
Database::IdType artistId,
std::vector<Database::ArtistId>
FeaturesEngine::getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const
{
auto getSimilarArtistIdsForLinkType {[&] (Database::TrackArtistLinkType linkType)
{
std::unordered_set<Database::IdType> similarArtistIds;
std::vector<Database::ArtistId> similarArtistIds;
const auto itArtists {_artistsMap.find(linkType)};
if (itArtists == std::cend(_artistsMap))
const auto itArtists {_artistMatrix.find(linkType)};
if (itArtists == std::cend(_artistMatrix))
{
return similarArtistIds;
}
similarArtistIds = getSimilarObjects({artistId}, itArtists->second, _artistPositions, maxCount);
return similarArtistIds;
return getSimilarObjects({artistId}, itArtists->second, _artistPositions, maxCount);
}};
std::unordered_set<Database::IdType> similarArtistIds;
std::unordered_set<Database::ArtistId> similarArtistIds;
for (Database::TrackArtistLinkType linkType : linkTypes)
{
@@ -342,44 +331,42 @@ FeaturesClassifier::getSimilarArtists(Database::Session& session,
similarArtistIds.insert(std::begin(similarArtistIdsForLinkType), std::end(similarArtistIdsForLinkType));
}
if (!similarArtistIds.empty())
std::vector<Database::ArtistId> res(std::cbegin(similarArtistIds), std::cend(similarArtistIds));
{
// Report only existing ids
auto transaction {session.createSharedTransaction()};
for (auto it {std::begin(similarArtistIds)}; it != std::end(similarArtistIds);)
{
const Database::IdType similarArtistId {*it};
if (!Database::Artist::getById(session, similarArtistId))
it = similarArtistIds.erase(it);
else
it++;
}
res.erase(std::remove_if(std::begin(res), std::end(res),
[&](Database::ArtistId artistId)
{
return Database::Artist::getById(session, artistId); // TODO exists
}), std::end(res));
}
while (similarArtistIds.size() > maxCount)
similarArtistIds.erase(Random::pickRandom(similarArtistIds));
while (res.size() > maxCount)
res.erase(Random::pickRandom(res));
return similarArtistIds;
return res;
}
FeaturesClassifierCache
FeaturesClassifier::toCache() const
FeaturesEngineCache
FeaturesEngine::toCache() const
{
return FeaturesClassifierCache {*_network, _trackPositions};
return FeaturesEngineCache {*_network, _trackPositions};
}
bool
FeaturesClassifier::load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback)
FeaturesEngine::load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback)
{
if (forceReload)
{
FeaturesClassifierCache::invalidate();
FeaturesEngineCache::invalidate();
}
else
{
const std::optional<FeaturesClassifierCache> cache {FeaturesClassifierCache::read()};
const std::optional<FeaturesEngineCache> cache {FeaturesEngineCache::read()};
if (cache)
return loadFromCache(session, *cache);
}
@@ -395,64 +382,65 @@ FeaturesClassifier::load(Database::Session& session, bool forceReload, const Pro
}
void
FeaturesClassifier::requestCancelLoad()
FeaturesEngine::requestCancelLoad()
{
LMS_LOG(RECOMMENDATION, DEBUG) << "Requesting init cancellation";
_loadCancelled = true;
}
bool
FeaturesClassifier::load(Database::Session& session,
FeaturesEngine::load(Database::Session& session,
SOM::Network network,
const ObjectPositions& tracksPosition)
const TrackPositions& trackPositions)
{
using namespace Database;
_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()};
_releasesMap = MatrixOfObjects {width, height};
_tracksMap = MatrixOfObjects {width, height};
_releaseMatrix = ReleaseMatrix {width, height};
_trackMatrix = TrackMatrix {width, height};
LMS_LOG(RECOMMENDATION, DEBUG) << "Constructing maps...";
for (auto itTrackCoord : tracksPosition)
for (const auto& [trackId, positions] : trackPositions)
{
if (_loadCancelled)
return false;
auto transaction {session.createSharedTransaction()};
Database::IdType trackId {itTrackCoord.first};
const std::unordered_set<SOM::Position>& positionSet {itTrackCoord.second};
const Database::Track::pointer track {Database::Track::getById(session, trackId)};
const Track::pointer track {Database::Track::getById(session, trackId)};
if (!track)
continue;
for (const SOM::Position& position : positionSet)
for (const SOM::Position& position : positions)
{
_tracksMap[position].insert(trackId);
_trackPositions[trackId].insert(position);
Utils::push_back_if_not_present(_trackPositions[trackId], position);
Utils::push_back_if_not_present(_trackMatrix[position], trackId);
if (track->getRelease())
if (Release::pointer release {track->getRelease()})
{
_releasePositions[track->getRelease().id()].insert(position);
_releasesMap[position].insert(track->getRelease().id());
const ReleaseId releaseId {release->getId()};
Utils::push_back_if_not_present(_releasePositions[releaseId], position);
Utils::push_back_if_not_present(_releaseMatrix[position], releaseId);
}
for (const auto& artistLink : track->getArtistLinks())
for (const TrackArtistLink::pointer& artistLink : track->getArtistLinks())
{
_artistPositions[artistLink->getArtist().id()].insert(position);
auto itArtists {_artistsMap.find(artistLink->getType())};
if (itArtists == std::cend(_artistsMap))
const ArtistId artistId {artistLink->getArtist()->getId()};
Utils::push_back_if_not_present(_artistPositions[artistId], position);
auto itArtists {_artistMatrix.find(artistLink->getType())};
if (itArtists == std::cend(_artistMatrix))
{
auto [it, inserted] = _artistsMap.try_emplace(artistLink->getType(), MatrixOfObjects {});
auto [it, inserted] = _artistMatrix.try_emplace(artistLink->getType(), ArtistMatrix {width, height});
assert(inserted);
itArtists = it;
itArtists->second = MatrixOfObjects {width, height};
}
itArtists->second[position].insert(artistLink->getArtist().id());
Utils::push_back_if_not_present(itArtists->second[position], artistId);
}
}
}
@@ -464,83 +452,4 @@ FeaturesClassifier::load(Database::Session& session,
return true;
}
std::unordered_set<SOM::Position>
FeaturesClassifier::getMatchingRefVectorsPosition(const std::unordered_set<Database::IdType>& ids, const ObjectPositions& objectPositions)
{
std::unordered_set<SOM::Position> res;
if (ids.empty())
return res;
for (auto id : ids)
{
auto it = objectPositions.find(id);
if (it == objectPositions.end())
continue;
for (const auto& position : it->second)
res.insert(position);
}
return res;
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getObjectsIds(const std::unordered_set<SOM::Position>& positionSet, const MatrixOfObjects& objectsMap)
{
std::unordered_set<Database::IdType> res;
for (const auto& position : positionSet)
{
for (auto id : objectsMap.get(position))
res.insert(id);
}
return res;
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarObjects(const std::unordered_set<Database::IdType>& ids,
const MatrixOfObjects& objectsMap,
const ObjectPositions& objectPosition,
std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
std::unordered_set<SOM::Position> searchedRefVectorsPosition {getMatchingRefVectorsPosition(ids, objectPosition)};
if (searchedRefVectorsPosition.empty())
return res;
while (1)
{
std::unordered_set<Database::IdType> closestObjectIds {getObjectsIds(searchedRefVectorsPosition, objectsMap)};
// Remove objects that are already in input or already reported
for (auto id : ids)
closestObjectIds.erase(id);
for (auto it {std::cbegin(closestObjectIds)}; it != std::cend(closestObjectIds); ++it)
{
if (res.size() == maxCount)
break;
res.insert(*it);
}
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;
searchedRefVectorsPosition.insert(closestRefVectorPosition.value());
}
return res;
}
} // ns Recommendation
@@ -0,0 +1,216 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <algorithm>
#include <functional>
#include <unordered_map>
#include <optional>
#include <string>
#include <vector>
#include "som/DataNormalizer.hpp"
#include "som/Network.hpp"
#include "utils/Utils.hpp"
#include "FeaturesEngineCache.hpp"
#include "FeaturesDefs.hpp"
#include "IClassifier.hpp"
namespace Database
{
class Session;
}
namespace Recommendation {
using FeatureWeight = double;
class FeaturesEngine : public IClassifier
{
public:
FeaturesEngine() = default;
FeaturesEngine(const FeaturesEngine&) = delete;
FeaturesEngine(FeaturesEngine&&) = delete;
FeaturesEngine& operator=(const FeaturesEngine&) = delete;
FeaturesEngine& operator=(FeaturesEngine&&) = delete;
using FeaturesFetchFunc = std::function<std::optional<std::unordered_map<std::string, std::vector<double>>>(Database::TrackId, const std::unordered_set<std::string>& /*features*/)>;
// Default is to retrieve the features from the database (may be slow).
// Use this only if you want to train different searchers with some cached data
static void setFeaturesFetchFunc(FeaturesFetchFunc func) { _featuresFetchFunc = func; }
static const FeatureSettingsMap& getDefaultTrainFeatureSettings();
private:
std::string_view getName() const override { return "Features"; }
bool load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback) override;
void requestCancelLoad() override;
ResultContainer<Database::TrackId> getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) const override;
ResultContainer<Database::TrackId> getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const override;
ResultContainer<Database::ReleaseId> getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) const override;
ResultContainer<Database::ArtistId> getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const override;
bool loadFromCache(Database::Session& session, const FeaturesEngineCache& cache);
// Use training (may be very slow)
struct TrainSettings
{
std::size_t iterationCount {10};
float sampleCountPerNeuron {4};
FeatureSettingsMap featureSettingsMap;
};
bool loadFromTraining(Database::Session& session, const TrainSettings& trainSettings, const ProgressCallback& progressCallback);
template <typename IdType>
using ObjectPositions = std::unordered_map<IdType, std::vector<SOM::Position>>;
using ArtistPositions = ObjectPositions<Database::ArtistId>;
using ReleasePositions = ObjectPositions<Database::ReleaseId>;
using TrackPositions = ObjectPositions<Database::TrackId>;
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>;
bool load(Database::Session& session, SOM::Network network, const TrackPositions& tracksPosition);
FeaturesEngineCache toCache() const;
template <typename IdType>
static std::vector<SOM::Position> getMatchingRefVectorsPosition(const std::vector<IdType>& ids, const ObjectPositions<IdType>& objectPositions);
template <typename IdType>
static std::vector<IdType> getObjectsIds(const std::vector<SOM::Position>& positions, const ObjectMatrix<IdType>& objectsMatrix);
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;
bool _loadCancelled {};
std::unique_ptr<SOM::Network> _network;
double _networkRefVectorsDistanceMedian {};
ArtistPositions _artistPositions;
std::unordered_map<Database::TrackArtistLinkType, ArtistMatrix> _artistMatrix;
ReleasePositions _releasePositions;
ReleaseMatrix _releaseMatrix;
TrackPositions _trackPositions;
TrackMatrix _trackMatrix;
static inline FeaturesFetchFunc _featuresFetchFunc;
};
template <typename IdType>
std::vector<SOM::Position>
FeaturesEngine::getMatchingRefVectorsPosition(const std::vector<IdType>& ids, const ObjectPositions<IdType>& objectPositions)
{
std::vector<SOM::Position> res;
if (ids.empty())
return res;
for (const IdType id : ids)
{
auto it = objectPositions.find(id);
if (it == objectPositions.end())
continue;
for (const SOM::Position& position : it->second)
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))
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
@@ -17,7 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FeaturesClassifierCache.hpp"
#include "FeaturesEngineCache.hpp"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
@@ -90,7 +90,7 @@ networkToCacheFile(const SOM::Network& network, std::filesystem::path path)
}
std::optional<SOM::Network>
FeaturesClassifierCache::createNetworkFromCacheFile(const std::filesystem::path& path)
FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path)
{
if (!std::filesystem::exists(path))
return std::nullopt;
@@ -143,19 +143,19 @@ FeaturesClassifierCache::createNetworkFromCacheFile(const std::filesystem::path&
}
bool
FeaturesClassifierCache::objectPositionToCacheFile(const ObjectPositions& objectsPosition, const std::filesystem::path& path)
FeaturesEngineCache::objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path)
{
try
{
boost::property_tree::ptree root;
for (const auto& objectPosition : objectsPosition)
for (const auto& [id, positions] : trackPositions)
{
boost::property_tree::ptree node;
node.put("id", objectPosition.first);
node.put("id", id.getValue());
for (const auto& position : objectPosition.second)
for (const SOM::Position& position : positions)
{
boost::property_tree::ptree positionNode;
positionNode.put("x", position.x);
@@ -177,8 +177,8 @@ FeaturesClassifierCache::objectPositionToCacheFile(const ObjectPositions& object
}
}
std::optional<FeaturesClassifierCache::ObjectPositions>
FeaturesClassifierCache::createObjectPositionsFromCacheFile(const std::filesystem::path& path)
std::optional<FeaturesEngineCache::TrackPositions>
FeaturesEngineCache::createObjectPositionsFromCacheFile(const std::filesystem::path& path)
{
try
{
@@ -188,17 +188,17 @@ FeaturesClassifierCache::createObjectPositionsFromCacheFile(const std::filesyste
boost::property_tree::read_xml(path.string(), root);
ObjectPositions res;
TrackPositions res;
for (const auto& object : root.get_child("objects"))
{
auto id = object.second.get<Database::IdType>("id");
const Database::TrackId id {object.second.get<Database::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");
res[id].insert({x, y});
res[id].push_back({x, y});
}
}
@@ -214,14 +214,14 @@ FeaturesClassifierCache::createObjectPositionsFromCacheFile(const std::filesyste
}
void
FeaturesClassifierCache::invalidate()
FeaturesEngineCache::invalidate()
{
std::filesystem::remove(getCacheNetworkFilePath());
std::filesystem::remove(getCacheTrackPositionsFilePath());
}
std::optional<FeaturesClassifierCache>
FeaturesClassifierCache::read()
std::optional<FeaturesEngineCache>
FeaturesEngineCache::read()
{
auto network{createNetworkFromCacheFile(getCacheNetworkFilePath())};
if (!network)
@@ -231,11 +231,11 @@ FeaturesClassifierCache::read()
if (!trackPositions)
return std::nullopt;
return FeaturesClassifierCache {std::move(*network), std::move(*trackPositions)};
return FeaturesEngineCache {std::move(*network), std::move(*trackPositions)};
}
void
FeaturesClassifierCache::write() const
FeaturesEngineCache::write() const
{
std::filesystem::create_directories(Service<IConfig>::get()->getPath("working-dir") / "cache" / "features");
@@ -246,7 +246,7 @@ FeaturesClassifierCache::write() const
}
}
FeaturesClassifierCache::FeaturesClassifierCache(SOM::Network network, ObjectPositions trackPositions)
FeaturesEngineCache::FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions)
: _network {std::move(network)},
_trackPositions {std::move(trackPositions)}
{
@@ -28,27 +28,27 @@
namespace Recommendation {
class FeaturesClassifierCache
class FeaturesEngineCache
{
public:
static void invalidate();
static std::optional<FeaturesClassifierCache> read();
static std::optional<FeaturesEngineCache> read();
void write() const;
private:
using ObjectPositions = std::unordered_map<Database::IdType, std::unordered_set<SOM::Position>>;
using TrackPositions = std::unordered_map<Database::TrackId, std::vector<SOM::Position>>;
FeaturesClassifierCache(SOM::Network network, ObjectPositions trackPositions);
FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions);
static std::optional<SOM::Network> createNetworkFromCacheFile(const std::filesystem::path& path);
static std::optional<ObjectPositions> createObjectPositionsFromCacheFile(const std::filesystem::path& path);
static bool objectPositionToCacheFile(const ObjectPositions& objectsPosition, 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);
friend class FeaturesClassifier;
friend class FeaturesEngine;
SOM::Network _network;
ObjectPositions _trackPositions;
TrackPositions _trackPositions;
};
} // namespace Recommendation
@@ -20,9 +20,7 @@
#pragma once
#include <functional>
#include <optional>
#include <unordered_set>
#include <memory>
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
@@ -48,15 +46,20 @@ namespace Recommendation
virtual void load(bool forceReload, const ProgressCallback& progressCallback = {}) = 0;
virtual void cancelLoad() = 0;
using ResultContainer = std::unordered_set<Database::IdType>;
template <typename IdType>
using ResultContainer = std::vector<IdType>;
virtual ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) = 0;
using ArtistContainer = ResultContainer<Database::ArtistId>;
using ReleaseContainer = ResultContainer<Database::ReleaseId>;
using TrackContainer = ResultContainer<Database::TrackId>;
virtual TrackContainer getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) = 0;
virtual TrackContainer getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) = 0;
virtual ReleaseContainer getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) = 0;
virtual ArtistContainer getSimilarArtists(Database::Session& session, Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) = 0;
protected:
virtual void requestCancelLoad() = 0;
};
std::unique_ptr<IEngine> createEngine(Database::Db& db);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 Emeric Poupon
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
@@ -17,27 +17,22 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ParameterParsing.hpp"
#pragma once
namespace StringUtils
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
namespace Database
{
template<>
std::optional<API::Subsonic::Id>
readAs(std::string_view str)
{
return API::Subsonic::IdFromString(str);
}
template<>
std::optional<bool>
readAs(std::string_view str)
{
if (str == "true")
return true;
else if (str == "false")
return false;
return {};
}
class Db;
class Session;
}
namespace Recommendation
{
class IRecommendation
{
};
} // ns Recommendation
+10 -10
View File
@@ -111,7 +111,7 @@ createArtist(Session& session, const MetaData::Artist& artistInfo)
static
void
updateArtistIfNeeded(const Artist::pointer& artist, const MetaData::Artist& artistInfo)
updateArtistIfNeeded(Artist::pointer artist, const MetaData::Artist& artistInfo)
{
// Name may have been updated
if (artist->getName() != artistInfo.name)
@@ -543,7 +543,7 @@ Scanner::scan(bool forceScan)
}
bool
Scanner::fetchTrackFeatures(Database::IdType trackId, const UUID& recordingMBID)
Scanner::fetchTrackFeatures(Database::TrackId trackId, const UUID& recordingMBID)
{
std::map<std::string, double> features;
@@ -551,14 +551,14 @@ Scanner::fetchTrackFeatures(Database::IdType trackId, const UUID& recordingMBID)
const std::string data {AcousticBrainz::extractLowLevelFeatures(recordingMBID)};
if (data.empty())
{
LMS_LOG(DBUPDATER, ERROR) << "Track " << trackId << ", recording MBID = '" << recordingMBID.getAsString() << "': cannot extract features using AcousticBrainz";
LMS_LOG(DBUPDATER, ERROR) << "Track " << trackId.getValue() << ", recording MBID = '" << recordingMBID.getAsString() << "': cannot extract features using AcousticBrainz";
return false;
}
{
auto uniqueTransaction {_dbSession.createUniqueTransaction()};
Wt::Dbo::ptr<Database::Track> track {Database::Track::getById(_dbSession, trackId)};
Database::Track::pointer track {Database::Track::getById(_dbSession, trackId)};
if (!track)
return false;
@@ -580,7 +580,7 @@ Scanner::fetchTrackFeatures(ScanStats& stats)
struct TrackInfo
{
Database::IdType id;
Database::TrackId id;
UUID recordingMBID;
};
@@ -592,7 +592,7 @@ Scanner::fetchTrackFeatures(ScanStats& stats)
auto tracks {Database::Track::getAllWithRecordingMBIDAndMissingFeatures(_dbSession)};
for (const auto& track : tracks)
res.emplace_back(TrackInfo {track.id(), *track->getRecordingMBID()});
res.emplace_back(TrackInfo {track->getId(), *track->getRecordingMBID()});
return res;
}()};
@@ -922,8 +922,8 @@ Scanner::removeMissingTracks(ScanStats& stats)
stepStats.totalElems = trackCount;
notifyInProgress(stepStats);
std::vector<std::pair<Database::IdType, std::filesystem::path>> trackPaths;
std::vector<IdType> tracksToRemove;
std::vector<std::pair<Database::TrackId, std::filesystem::path>> trackPaths;
std::vector<TrackId> tracksToRemove;
for (std::size_t i {trackCount < batchSize ? 0 : trackCount - batchSize}; ; i -= (i > batchSize ? batchSize : i))
{
@@ -950,7 +950,7 @@ Scanner::removeMissingTracks(ScanStats& stats)
{
auto transaction {_dbSession.createUniqueTransaction()};
for (const IdType trackId : tracksToRemove)
for (const TrackId trackId : tracksToRemove)
{
Track::pointer track {Track::getById(_dbSession, trackId)};
if (track)
@@ -1026,7 +1026,7 @@ Scanner::checkDuplicatedAudioFiles(ScanStats& stats)
if (auto trackMBID {track->getTrackMBID()})
{
LMS_LOG(DBUPDATER, INFO) << "Found duplicated Track MBID [" << trackMBID->getAsString() << "], file: " << track->getPath().string() << " - " << track->getName();
stats.duplicates.emplace_back(ScanDuplicate {track.id(), DuplicateReason::SameMBID});
stats.duplicates.emplace_back(ScanDuplicate {track->getId(), DuplicateReason::SameMBID});
}
}
+5 -4
View File
@@ -22,6 +22,7 @@
#include <chrono>
#include <shared_mutex>
#include <optional>
#include <unordered_set>
#include <Wt/WDateTime.h>
#include <Wt/WIOService.h>
@@ -34,6 +35,7 @@
#include "database/Session.hpp"
#include "metadata/IParser.hpp"
#include "scanner/IScanner.hpp"
#include "utils/Path.hpp"
class UUID;
@@ -75,7 +77,7 @@ class Scanner : public IScanner
void scan(bool force);
void scanMediaDirectory( const std::filesystem::path& mediaDirectory, bool forceScan, ScanStats& stats);
bool fetchTrackFeatures(Database::IdType trackId, const UUID& MBID);
bool fetchTrackFeatures(Database::TrackId trackId, const UUID& MBID);
void fetchTrackFeatures(ScanStats& stats);
// Helpers
@@ -86,7 +88,6 @@ class Scanner : public IScanner
void removeOrphanEntries();
void checkDuplicatedAudioFiles(ScanStats& stats);
void scanAudioFile(const std::filesystem::path& file, bool forceScan, ScanStats& stats);
Database::IdType doScanAudioFile(const std::filesystem::path& file, ScanStats& stats);
void notifyInProgressIfNeeded(const ScanStepStats& stats);
void notifyInProgress(const ScanStepStats& stats);
void reloadSimilarityEngine(ScanStats& stats);
@@ -112,8 +113,8 @@ class Scanner : public IScanner
std::size_t _scanVersion {};
Wt::WTime _startTime;
Database::ScanSettings::UpdatePeriod _updatePeriod {Database::ScanSettings::UpdatePeriod::Never};
std::unordered_set<std::filesystem::path> _fileExtensions;
std::filesystem::path _mediaDirectory;
std::unordered_set<std::filesystem::path> _fileExtensions;
std::filesystem::path _mediaDirectory;
Database::ScanSettings::RecommendationEngineType _recommendationEngineType;
};
@@ -53,7 +53,7 @@ namespace Scanner {
struct ScanDuplicate
{
Database::IdType trackId;
Database::TrackId trackId;
DuplicateReason reason;
};
+1 -2
View File
@@ -29,7 +29,6 @@
namespace Database
{
class Db;
class Session;
class TrackList;
class User;
@@ -48,7 +47,7 @@ namespace Scrobbling
virtual void addTimedListen(const TimedListen& listen) = 0;
virtual Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user) = 0;
virtual Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user) = 0;
};
std::unique_ptr<IScrobbler> createScrobbler(std::string_view backendName);
+33 -33
View File
@@ -64,7 +64,7 @@ namespace Scrobbling
}
std::optional<Database::Scrobbler>
Scrobbling::getUserScrobbler(Database::IdType userId)
Scrobbling::getUserScrobbler(Database::UserId userId)
{
std::optional<Database::Scrobbler> scrobbler;
@@ -76,49 +76,49 @@ namespace Scrobbling
return scrobbler;
}
std::vector<Wt::Dbo::ptr<Database::Artist>>
std::vector<Database::ObjectPtr<Database::Artist>>
Scrobbling::getRecentArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Artist>> res;
std::vector<Database::ObjectPtr<Database::Artist>> res;
if (history)
res = history->getArtistsReverse(clusterIds, linkType, range, moreResults);
return res;
}
std::vector<Wt::Dbo::ptr<Database::Release>>
std::vector<Database::ObjectPtr<Database::Release>>
Scrobbling::getRecentReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Release>> res;
std::vector<Database::ObjectPtr<Database::Release>> res;
if (history)
res = history->getReleasesReverse(clusterIds, range, moreResults);
return res;
}
std::vector<Wt::Dbo::ptr<Database::Track>>
std::vector<Database::ObjectPtr<Database::Track>>
Scrobbling::getRecentTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Track>> res;
std::vector<Database::ObjectPtr<Database::Track>> res;
if (history)
res = history->getTracksReverse(clusterIds, range, moreResults);
@@ -127,57 +127,57 @@ namespace Scrobbling
// Top
std::vector<Wt::Dbo::ptr<Database::Artist>>
std::vector<Database::ObjectPtr<Database::Artist>>
Scrobbling::getTopArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Artist>> res;
std::vector<Database::ObjectPtr<Database::Artist>> res;
if (history)
res = history->getTopArtists(clusterIds, linkType, range, moreResults);
return res;
}
std::vector<Wt::Dbo::ptr<Database::Release>>
std::vector<Database::ObjectPtr<Database::Release>>
Scrobbling::getTopReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Release>> res;
std::vector<Database::ObjectPtr<Database::Release>> res;
if (history)
res = history->getTopReleases(clusterIds, range, moreResults);
return res;
}
std::vector<Wt::Dbo::ptr<Database::Track>>
std::vector<Database::ObjectPtr<Database::Track>>
Scrobbling::getTopTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Track>> res;
std::vector<Database::ObjectPtr<Database::Track>> res;
if (history)
res = history->getTopTracks(clusterIds, range, moreResults);
return res;
}
Wt::Dbo::ptr<Database::TrackList>
Scrobbling::getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user)
Database::ObjectPtr<Database::TrackList>
Scrobbling::getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user)
{
return _scrobblers[user->getScrobbler()]->getListensTrackList(session, user);
}
+20 -20
View File
@@ -38,47 +38,47 @@ namespace Scrobbling
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
void addTimedListen(const TimedListen& listen) override;
std::vector<Wt::Dbo::ptr<Database::Artist>> getRecentArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Artist>> getRecentArtists(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Release>> getRecentReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Release>> getRecentReleases(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Track>> getRecentTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Track>> getRecentTracks(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Artist>> getTopArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Artist>> getTopArtists(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Release>> getTopReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Release>> getTopReleases(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Track>> getTopTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Track>> getTopTracks(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) override;
Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user);
Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user);
std::optional<Database::Scrobbler> getUserScrobbler(Database::IdType userId);
std::optional<Database::Scrobbler> getUserScrobbler(Database::UserId userId);
Database::Db& _db;
std::unordered_map<Database::Scrobbler, std::unique_ptr<IScrobbler>> _scrobblers;
@@ -61,7 +61,7 @@ namespace Scrobbling
if (!user)
return;
Wt::Dbo::ptr<Database::TrackList> tracklist {getListensTrackList(session, user)};
Database::TrackList::pointer tracklist {getListensTrackList(session, user)};
if (!tracklist)
tracklist = Database::TrackList::create(session, historyTracklistName, Database::TrackList::Type::Internal, false, user);
@@ -72,8 +72,8 @@ namespace Scrobbling
Database::TrackListEntry::create(session, track, getListensTrackList(session, user), listen.listenedAt);
}
Wt::Dbo::ptr<Database::TrackList>
InternalScrobbler::getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user)
Database::TrackList::pointer
InternalScrobbler::getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user)
{
return Database::TrackList::get(session, historyTracklistName, Database::TrackList::Type::Internal, user);
}
@@ -21,6 +21,11 @@
#include "IScrobbler.hpp"
namespace Database
{
class Db;
}
namespace Scrobbling
{
class InternalScrobbler final : public IScrobbler
@@ -34,7 +39,7 @@ namespace Scrobbling
void addTimedListen(const TimedListen& listen) override;
Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user) override;
Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user) override;
Database::Db& _db;
};
@@ -41,7 +41,7 @@
namespace
{
bool
canBeScrobbled(Database::Session& session, Database::IdType trackId, std::chrono::seconds duration)
canBeScrobbled(Database::Session& session, Database::TrackId trackId, std::chrono::seconds duration)
{
auto transaction {session.createSharedTransaction()};
@@ -50,7 +50,7 @@ namespace Scrobbling::ListenBrainz
void listenStarted(const Listen& listen) override;
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
void addTimedListen(const TimedListen& listen) override;
Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user) override;
Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user) override;
// Submit listens
void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint);
@@ -195,7 +195,7 @@ namespace
std::vector<Scrobbling::TimedListen> matchedListens;
};
ParseGetListensResult
parseGetListens(Database::Session& session, std::string_view msgBody, Database::IdType userId)
parseGetListens(Database::Session& session, std::string_view msgBody, Database::UserId userId)
{
ParseGetListensResult result;
@@ -233,7 +233,7 @@ namespace
result.oldestEntry = listenedAt;
if (const Database::Track::pointer track {tryMatchListen(session, metadata)})
result.matchedListens.emplace_back(Scrobbling::TimedListen {userId, track.id(), listenedAt});
result.matchedListens.emplace_back(Scrobbling::TimedListen {userId, track->getId(), listenedAt});
}
}
catch (const Wt::WException& error)
@@ -285,7 +285,7 @@ namespace Scrobbling::ListenBrainz
}
ListensSynchronizer::UserContext&
ListensSynchronizer::getUserContext(Database::IdType userId)
ListensSynchronizer::getUserContext(Database::UserId userId)
{
auto itContext {_userContexts.find(userId)};
if (itContext == std::cend(_userContexts))
@@ -338,14 +338,14 @@ namespace Scrobbling::ListenBrainz
assert(!isFetching());
std::vector<Database::IdType> userIds;
std::vector<Database::UserId> userIds;
{
Database::Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()};
userIds = Database::User::getAllIds(_db.getTLSSession());
}
for (const Database::IdType userId : userIds)
for (const Database::UserId userId : userIds)
{
if (Utils::getListenBrainzToken(_db.getTLSSession(), userId))
startGetListens(getUserContext(userId));
@@ -373,7 +373,7 @@ namespace Scrobbling::ListenBrainz
{
_strand.dispatch([this, &context]
{
LOG(DEBUG) << "Fetch done for user " << context.userId << ", fetched: " << context.fetchedListenCount << ", matched: " << context.matchedListenCount << ", imported: " << context.importedListenCount;
LOG(DEBUG) << "Fetch done for user " << context.userId.getValue() << ", fetched: " << context.fetchedListenCount << ", matched: " << context.matchedListenCount << ", imported: " << context.importedListenCount;
context.fetching = false;
if (!isFetching())
@@ -473,7 +473,7 @@ namespace Scrobbling::ListenBrainz
}
std::optional<SendQueue::RequestData>
ListensSynchronizer::createValidateTokenRequestData(Database::IdType userId)
ListensSynchronizer::createValidateTokenRequestData(Database::UserId userId)
{
Database::Session& session {_db.getTLSSession()};
@@ -50,14 +50,14 @@ namespace Scrobbling::ListenBrainz
private:
struct UserContext
{
UserContext(Database::IdType id) : userId {id} {}
UserContext(Database::UserId id) : userId {id} {}
UserContext(const UserContext&) = delete;
UserContext(UserContext&&) = delete;
UserContext& operator=(const UserContext&) = delete;
UserContext& operator=(UserContext&&) = delete;
const Database::IdType userId;
const Database::UserId userId;
bool fetching {};
std::optional<std::size_t> listenCount {};
@@ -69,7 +69,7 @@ namespace Scrobbling::ListenBrainz
std::size_t importedListenCount{};
};
UserContext& getUserContext(Database::IdType userId);
UserContext& getUserContext(Database::UserId userId);
bool isFetching() const;
void scheduleGetListens(std::chrono::seconds fromNow);
void startGetListens();
@@ -78,7 +78,7 @@ namespace Scrobbling::ListenBrainz
void enqueValidateToken(UserContext& context);
void enqueGetListenCount(UserContext& context);
void enqueGetListens(UserContext& context);
std::optional<SendQueue::RequestData> createValidateTokenRequestData(Database::IdType userId);
std::optional<SendQueue::RequestData> createValidateTokenRequestData(Database::UserId userId);
std::optional<SendQueue::RequestData> createGetListensRequestData(std::string_view listenBrainzUserName, const Wt::WDateTime& maxDateTime);
void processGetListensResponse(std::string_view body, UserContext& context);
@@ -88,7 +88,7 @@ namespace Scrobbling::ListenBrainz
SendQueue& _sendQueue;
boost::asio::steady_timer _getListensTimer {_ioContext};
std::unordered_map<Database::IdType, UserContext> _userContexts;
std::unordered_map<Database::UserId, UserContext> _userContexts;
const std::size_t _maxSyncListenCount;
const std::chrono::hours _syncListensPeriod;
@@ -30,7 +30,7 @@ static constexpr std::string_view historyTracklistName {"__scrobbler_listenbrain
namespace Scrobbling::ListenBrainz::Utils
{
std::optional<UUID>
getListenBrainzToken(Database::Session& session, Database::IdType userId)
getListenBrainzToken(Database::Session& session, Database::UserId userId)
{
auto transaction {session.createSharedTransaction()};
@@ -21,6 +21,7 @@
#include <Wt/Dbo/ptr.h>
#include "utils/UUID.hpp"
#include "database/Types.hpp"
namespace Database
@@ -32,7 +33,7 @@ namespace Database
namespace Scrobbling::ListenBrainz::Utils
{
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::IdType userId);
Wt::Dbo::ptr<Database::TrackList> getOrCreateListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user);
Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user);
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId);
Database::ObjectPtr<Database::TrackList> getOrCreateListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user);
Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user);
}
@@ -24,12 +24,12 @@
#include <chrono>
#include <memory>
#include <optional>
#include <set>
#include <vector>
#include <Wt/WDateTime.h>
#include "scrobbling/Listen.hpp"
#include "database/Types.hpp"
namespace Database
{
@@ -57,42 +57,42 @@ namespace Scrobbling
// Stats
// From most recent to oldest
virtual std::vector<Wt::Dbo::ptr<Database::Artist>> getRecentArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Artist>> getRecentArtists(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults) = 0;
virtual std::vector<Wt::Dbo::ptr<Database::Release>> getRecentReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Release>> getRecentReleases(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) = 0;
virtual std::vector<Wt::Dbo::ptr<Database::Track>> getRecentTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Track>> getRecentTracks(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) = 0;
// Top
virtual std::vector<Wt::Dbo::ptr<Database::Artist>> getTopArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Artist>> getTopArtists(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults) = 0;
virtual std::vector<Wt::Dbo::ptr<Database::Release>> getTopReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Release>> getTopReleases(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) = 0;
virtual std::vector<Wt::Dbo::ptr<Database::Track>> getTopTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Track>> getTopTracks(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) = 0;
};
@@ -27,8 +27,8 @@ namespace Scrobbling
{
struct Listen
{
Database::IdType userId {};
Database::IdType trackId {};
Database::UserId userId {};
Database::TrackId trackId {};
};
struct TimedListen : public Listen
+2 -1
View File
@@ -24,6 +24,7 @@
#include <cmath>
#include <random>
#include <sstream>
#include <unordered_set>
#include "utils/Logger.hpp"
#include "utils/Random.hpp"
@@ -197,7 +198,7 @@ Network::getClosestRefVectorPosition(const InputVector& data, InputVector::Dista
}
std::optional<Position>
Network::getClosestRefVectorPosition(const std::unordered_set<Position>& refVectorsPosition, InputVector::Distance maxDistance) const
Network::getClosestRefVectorPosition(const std::vector<Position>& refVectorsPosition, InputVector::Distance maxDistance) const
{
std::unordered_set<Position> neighboursPosition;
for (const Position& refVectorPosition : refVectorsPosition)
+1 -2
View File
@@ -20,7 +20,6 @@
#pragma once
#include <vector>
#include <unordered_set>
#include <optional>
#include <ostream>
#include <functional>
@@ -70,7 +69,7 @@ class Network
Position getClosestRefVectorPosition(const InputVector& data) const;
std::optional<Position> getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const;
std::optional<Position> getClosestRefVectorPosition(const std::unordered_set<Position>& refVectorsPosition, InputVector::Distance maxDistance) const;
std::optional<Position> getClosestRefVectorPosition(const std::vector<Position>& refVectorsPosition, InputVector::Distance maxDistance) const;
InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const;
-1
View File
@@ -1,6 +1,5 @@
add_library(lmssubsonic SHARED
impl/ParameterParsing.cpp
impl/Scan.cpp
impl/Stream.cpp
impl/SubsonicId.cpp
+1 -14
View File
@@ -20,8 +20,8 @@
#include <Wt/Http/Request.h>
#include "database/Types.hpp"
#include "utils/String.hpp"
#include "SubsonicId.hpp"
#include "SubsonicResponse.hpp"
namespace API::Subsonic
@@ -82,18 +82,5 @@ namespace API::Subsonic
return *res;
}
}
namespace StringUtils
{
template<>
std::optional<API::Subsonic::Id>
readAs(std::string_view str);
template<>
std::optional<bool>
readAs(std::string_view str);
}
+1 -1
View File
@@ -36,7 +36,7 @@ namespace API::Subsonic
{
const Wt::Http::ParameterMap& parameters;
Database::Session& dbSession;
Database::IdType userId;
Database::UserId userId;
std::string clientName;
};
}
+5 -5
View File
@@ -63,7 +63,7 @@ StreamParameters
getStreamParameters(RequestContext& context)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
const TrackId id {getMandatoryParameterAs<TrackId>(context.parameters, "id")};
// Optional params
std::optional<std::size_t> maxBitRate {getParameterAs<std::size_t>(context.parameters, "maxBitRate")};
@@ -74,7 +74,7 @@ getStreamParameters(RequestContext& context)
auto transaction {context.dbSession.createSharedTransaction()};
{
auto track {Track::getById(context.dbSession, id.value)};
auto track {Track::getById(context.dbSession, id)};
if (!track)
throw RequestedDataNotFoundError {};
@@ -94,7 +94,7 @@ getStreamParameters(RequestContext& context)
// "If set to zero, no limit is imposed"
if (maxBitRate && *maxBitRate != 0)
bitRate = clamp(*maxBitRate, std::size_t {48}, bitRate);
bitRate = Utils::clamp(*maxBitRate, std::size_t {48}, bitRate);
Av::TranscodeParameters transcodeParameters;
@@ -118,13 +118,13 @@ handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Ht
if (!continuation)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
Database::TrackId id {getMandatoryParameterAs<Database::TrackId>(context.parameters, "id")};
std::filesystem::path trackPath;
{
auto transaction {context.dbSession.createSharedTransaction()};
auto track {Track::getById(context.dbSession, id.value)};
auto track {Track::getById(context.dbSession, id)};
if (!track)
throw RequestedDataNotFoundError {};
+119 -56
View File
@@ -26,64 +26,127 @@
namespace API::Subsonic
{
std::optional<Id>
IdFromString(const std::string_view id)
{
if (id == "root")
return Id {Id::Type::Root};
std::vector<std::string_view> values {StringUtils::splitString(id, "-")};
if (values.size() != 2)
return std::nullopt;
Id res;
const std::string type {std::move(values[0])};
if (type == "ar")
res.type = Id::Type::Artist;
else if (type == "al")
res.type = Id::Type::Release;
else if (type == "tr")
res.type = Id::Type::Track;
else if (type == "pl")
res.type = Id::Type::Playlist;
else
return std::nullopt;
auto optId {StringUtils::readAs<Database::IdType>(values[1])};
if (!optId)
return std::nullopt;
res.value = *optId;
return res;
}
std::string
IdToString(const Id& id)
{
std::string res;
switch (id.type)
std::string
idToString(Database::ArtistId id)
{
case Id::Type::Root:
return "root";
case Id::Type::Artist:
res = "ar-";
break;
case Id::Type::Release:
res = "al-";
break;
case Id::Type::Track:
res = "tr-";
break;
case Id::Type::Playlist:
res = "pl-";
break;
return "ar-" + id.toString();
}
return res + std::to_string(id.value);
std::string
idToString(Database::ReleaseId id)
{
return "al-" + id.toString();
}
std::string
idToString(RootId)
{
return "root";
}
std::string
idToString(Database::TrackId id)
{
return "tr-" + id.toString();
}
std::string
idToString(Database::TrackListId id)
{
return "pl-" + id.toString();
}
} // namespace API::Subsonic
namespace StringUtils
{
template<>
std::optional<Database::ArtistId>
readAs(std::string_view str)
{
std::vector<std::string_view> values {StringUtils::splitString(str, "-")};
if (values.size() != 2)
return std::nullopt;
if (values[0] != "ar")
return std::nullopt;
if (const auto value {StringUtils::readAs<Database::ArtistId::ValueType>(values[1])})
return Database::ArtistId {*value};
return std::nullopt;
}
template<>
std::optional<Database::ReleaseId>
readAs(std::string_view str)
{
std::vector<std::string_view> values {StringUtils::splitString(str, "-")};
if (values.size() != 2)
return std::nullopt;
if (values[0] != "al")
return std::nullopt;
if (const auto value {StringUtils::readAs<Database::ReleaseId::ValueType>(values[1])})
return Database::ReleaseId {*value};
return std::nullopt;
}
template<>
std::optional<API::Subsonic::RootId>
readAs(std::string_view str)
{
if (str == "root")
return API::Subsonic::RootId {};
return std::nullopt;
}
template<>
std::optional<Database::TrackId>
readAs(std::string_view str)
{
std::vector<std::string_view> values {StringUtils::splitString(str, "-")};
if (values.size() != 2)
return std::nullopt;
if (values[0] != "tr")
return std::nullopt;
if (const auto value {StringUtils::readAs<Database::TrackId::ValueType>(values[1])})
return Database::TrackId {*value};
return std::nullopt;
}
template<>
std::optional<Database::TrackListId>
readAs(std::string_view str)
{
std::vector<std::string_view> values {StringUtils::splitString(str, "-")};
if (values.size() != 2)
return std::nullopt;
if (values[0] != "pl")
return std::nullopt;
if (const auto value {StringUtils::readAs<Database::TrackListId::ValueType>(values[1])})
return Database::TrackListId {*value};
return std::nullopt;
}
template<>
std::optional<bool>
readAs(std::string_view str)
{
if (str == "true")
return true;
else if (str == "false")
return false;
return {};
}
}
} // namespace API::Subsonic
+36 -20
View File
@@ -19,29 +19,45 @@
#pragma once
#include <optional>
#include "database/Types.hpp"
#include "utils/String.hpp"
namespace API::Subsonic
{
struct RootId {};
struct Id
{
enum class Type
{
Root, // Where all artists artistless albums reside
Track,
Release,
Artist,
Playlist,
};
Type type;
Database::IdType value {};
};
std::optional<Id> IdFromString(std::string_view id);
std::string IdToString(const Id& id);
std::string idToString(Database::ArtistId id);
std::string idToString(Database::ReleaseId id);
std::string idToString(Database::TrackId id);
std::string idToString(Database::TrackListId id);
std::string idToString(RootId);
} // namespace API::Subsonic
// Used to parse parameters
namespace StringUtils
{
template<>
std::optional<API::Subsonic::RootId>
readAs(std::string_view str);
template<>
std::optional<Database::ArtistId>
readAs(std::string_view str);
template<>
std::optional<Database::ReleaseId>
readAs(std::string_view str);
template<>
std::optional<Database::TrackId>
readAs(std::string_view str);
template<>
std::optional<Database::TrackListId>
readAs(std::string_view str);
template<>
std::optional<bool>
readAs(std::string_view str);
}
+141 -206
View File
@@ -48,6 +48,7 @@
#include "RequestContext.hpp"
#include "Scan.hpp"
#include "Stream.hpp"
#include "SubsonicId.hpp"
#include "SubsonicResponse.hpp"
using namespace Database;
@@ -321,7 +322,7 @@ trackToResponseNode(const Track::pointer& track, Session& dbSession, const User:
{
Response::Node trackResponse;
trackResponse.setAttribute("id", IdToString({Id::Type::Track, track.id()}));
trackResponse.setAttribute("id", idToString(track->getId()));
trackResponse.setAttribute("isDir", false);
trackResponse.setAttribute("title", track->getName());
if (track->getTrackNumber())
@@ -348,7 +349,7 @@ trackToResponseNode(const Track::pointer& track, Session& dbSession, const User:
if (user->getSubsonicTranscodeEnable())
trackResponse.setAttribute("transcodedSuffix", formatToSuffix(user->getSubsonicTranscodeFormat()));
trackResponse.setAttribute("coverArt", IdToString({Id::Type::Track, track.id()}));
trackResponse.setAttribute("coverArt", idToString(track->getId()));
const std::vector<Artist::pointer>& artists {track->getArtists({TrackArtistLinkType::Artist})};
if (!artists.empty())
@@ -356,14 +357,14 @@ trackToResponseNode(const Track::pointer& track, Session& dbSession, const User:
trackResponse.setAttribute("artist", getArtistNames(artists));
if (artists.size() == 1)
trackResponse.setAttribute("artistId", IdToString({Id::Type::Artist, artists.front().id()}));
trackResponse.setAttribute("artistId", idToString(artists.front()->getId()));
}
if (track->getRelease())
{
trackResponse.setAttribute("album", track->getRelease()->getName());
trackResponse.setAttribute("albumId", IdToString({Id::Type::Release, track->getRelease().id()}));
trackResponse.setAttribute("parent", IdToString({Id::Type::Release, track->getRelease().id()}));
trackResponse.setAttribute("albumId", idToString(track->getRelease()->getId()));
trackResponse.setAttribute("parent", idToString(track->getRelease()->getId()));
}
trackResponse.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count());
@@ -420,8 +421,8 @@ releaseToResponseNode(const Release::pointer& release, Session& dbSession, const
}
albumNode.setAttribute("created", dateTimeToCreatedString(release->getLastWritten()));
albumNode.setAttribute("id", IdToString({Id::Type::Release, release.id()}));
albumNode.setAttribute("coverArt", IdToString({Id::Type::Release, release.id()}));
albumNode.setAttribute("id", idToString(release->getId()));
albumNode.setAttribute("coverArt", idToString(release->getId()));
auto releaseYear {release->getReleaseYear()};
if (releaseYear)
albumNode.setAttribute("year", *releaseYear);
@@ -432,7 +433,7 @@ releaseToResponseNode(const Release::pointer& release, Session& dbSession, const
if (artists.empty() && !id3)
{
albumNode.setAttribute("parent", IdToString({Id::Type::Root}));
albumNode.setAttribute("parent", idToString(RootId {}));
}
else if (!artists.empty())
{
@@ -440,15 +441,12 @@ releaseToResponseNode(const Release::pointer& release, Session& dbSession, const
if (artists.size() == 1)
{
if (id3)
albumNode.setAttribute("artistId", IdToString({Id::Type::Artist, artists.front().id()}));
else
albumNode.setAttribute("parent", IdToString({Id::Type::Artist, artists.front().id()}));
albumNode.setAttribute(id3 ? "artistId" : "parent", idToString(artists.front()->getId()));
}
else
{
if (!id3)
albumNode.setAttribute("parent", IdToString({Id::Type::Root}));
albumNode.setAttribute("parent", idToString(RootId {}));
}
}
@@ -476,7 +474,7 @@ artistToResponseNode(const User::pointer& user, const Artist::pointer& artist, b
{
Response::Node artistNode;
artistNode.setAttribute("id", IdToString({Id::Type::Artist, artist.id()}));
artistNode.setAttribute("id", idToString(artist->getId()));
artistNode.setAttribute("name", artist->getName());
if (id3)
@@ -544,7 +542,7 @@ handleChangePassword(RequestContext& context)
try
{
Database::IdType userId;
Database::UserId userId;
{
auto transaction {context.dbSession.createSharedTransaction()};
@@ -554,7 +552,7 @@ handleChangePassword(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
userId = user.id();
userId = user->getId();
}
Service<Auth::IPasswordService>::get()->setPassword(context.dbSession, userId, password);
@@ -580,15 +578,10 @@ Response
handleCreatePlaylistRequest(RequestContext& context)
{
// Optional params
auto id {getParameterAs<Id>(context.parameters, "playlistId")};
if (id && id->type != Id::Type::Playlist)
throw BadParameterGenericError {"playlistId"};
const auto id {getParameterAs<TrackListId>(context.parameters, "playlistId")};
auto name {getParameterAs<std::string>(context.parameters, "name")};
std::vector<Id> trackIds {getMultiParametersAs<Id>(context.parameters, "songId")};
if (!std::all_of(std::cbegin(trackIds), std::cend(trackIds ), [](const Id& id) { return id.type == Id::Type::Track; }))
throw BadParameterGenericError {"songId"};
std::vector<TrackId> trackIds {getMultiParametersAs<TrackId>(context.parameters, "songId")};
if (!name && !id)
throw RequiredParameterMissingError {"name or id"};
@@ -602,7 +595,7 @@ handleCreatePlaylistRequest(RequestContext& context)
TrackList::pointer tracklist;
if (id)
{
tracklist = TrackList::getById(context.dbSession, id->value);
tracklist = TrackList::getById(context.dbSession, *id);
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
@@ -618,9 +611,9 @@ handleCreatePlaylistRequest(RequestContext& context)
tracklist = TrackList::create(context.dbSession, *name, TrackList::Type::Playlist, false, user);
}
for (const Id& trackId : trackIds)
for (const TrackId trackId : trackIds)
{
Track::pointer track {Track::getById(context.dbSession, trackId.value)};
Track::pointer track {Track::getById(context.dbSession, trackId)};
if (!track)
continue;
@@ -638,7 +631,7 @@ handleCreateUserRequest(RequestContext& context)
std::string password {decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(context.parameters, "password"))};
// Just ignore all the other fields as we don't handle them
Database::IdType userId;
Database::UserId userId;
{
auto transaction {context.dbSession.createUniqueTransaction()};
@@ -647,7 +640,7 @@ handleCreateUserRequest(RequestContext& context)
throw UserAlreadyExistsGenericError {};
user = User::create(context.dbSession, username);
userId = user.id();
userId = user->getId();
}
auto removeCreatedUser {[&]()
@@ -685,9 +678,7 @@ static
Response
handleDeletePlaylistRequest(RequestContext& context)
{
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Playlist)
throw BadParameterGenericError {"id"};
TrackListId id {getMandatoryParameterAs<TrackListId>(context.parameters, "id")};
auto transaction {context.dbSession.createUniqueTransaction()};
@@ -695,7 +686,7 @@ handleDeletePlaylistRequest(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, id)};
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
@@ -721,7 +712,7 @@ handleDeleteUserRequest(RequestContext& context)
throw RequestedDataNotFoundError {};
// cannot delete ourself
if (user.id() == context.userId)
if (user->getId() == context.userId)
throw UserNotAuthorizedError {};
user.remove();
@@ -809,7 +800,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
if (cluster)
{
bool more;
releases = Release::getByFilter(context.dbSession, {cluster.id()}, {}, range, more);
releases = Release::getByFilter(context.dbSession, {cluster->getId()}, {}, range, more);
}
}
}
@@ -876,14 +867,11 @@ Response
handleGetAlbumRequest(RequestContext& context)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Release)
throw BadParameterGenericError {"id"};
ReleaseId id {getMandatoryParameterAs<ReleaseId>(context.parameters, "id")};
auto transaction {context.dbSession.createSharedTransaction()};
Release::pointer release {Release::getById(context.dbSession, id.value)};
Release::pointer release {Release::getById(context.dbSession, id)};
if (!release)
throw RequestedDataNotFoundError {};
@@ -908,14 +896,11 @@ Response
handleGetArtistRequest(RequestContext& context)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Artist)
throw BadParameterGenericError {"id"};
ArtistId id {getMandatoryParameterAs<ArtistId>(context.parameters, "id")};
auto transaction {context.dbSession.createSharedTransaction()};
Artist::pointer artist {Artist::getById(context.dbSession, id.value)};
Artist::pointer artist {Artist::getById(context.dbSession, id)};
if (!artist)
throw RequestedDataNotFoundError {};
@@ -940,9 +925,7 @@ Response
handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Artist)
throw BadParameterGenericError {"id"};
ArtistId id {getMandatoryParameterAs<ArtistId>(context.parameters, "id")};
// Optional params
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").value_or(20)};
@@ -953,7 +936,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
{
auto transaction {context.dbSession.createSharedTransaction()};
Artist::pointer artist {Artist::getById(context.dbSession, id.value)};
Artist::pointer artist {Artist::getById(context.dbSession, id)};
if (!artist)
throw RequestedDataNotFoundError {};
@@ -963,7 +946,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
}
auto similarArtistsId {Service<Recommendation::IEngine>::get()->getSimilarArtists(context.dbSession,
id.value,
id,
{TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist},
count)};
@@ -974,7 +957,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
if (!user)
throw UserNotAuthorizedError {};
for ( const auto& similarArtistId : similarArtistsId )
for ( const ArtistId similarArtistId : similarArtistsId )
{
Artist::pointer similarArtist {Artist::getById(context.dbSession, similarArtistId)};
if (similarArtist)
@@ -1049,66 +1032,63 @@ Response
handleGetMusicDirectoryRequest(RequestContext& context)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
const auto artistId {getParameterAs<ArtistId>(context.parameters, "id")};
const auto releaseId {getParameterAs<ReleaseId>(context.parameters, "id")};
const auto trackId {getParameterAs<TrackId>(context.parameters, "id")};
const auto root {getParameterAs<RootId>(context.parameters, "id")};
if (!root && !artistId && !releaseId && !trackId)
throw BadParameterGenericError {"id"};
Response response {Response::createOkResponse(context)};
Response::Node& directoryNode {response.createNode("directory")};
directoryNode.setAttribute("id", IdToString(id));
auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user)
throw UserNotAuthorizedError {};
switch (id.type)
if (root)
{
case Id::Type::Root:
{
directoryNode.setAttribute("name", "Music");
directoryNode.setAttribute("id", idToString(RootId {}));
directoryNode.setAttribute("name", "Music");
bool moreResults{};
auto artists {Artist::getAll(context.dbSession, Artist::SortMethod::BySortName, std::nullopt, moreResults)};
for (const Artist::pointer& artist : artists)
directoryNode.addArrayChild("child", artistToResponseNode(user, artist, false /* no id3 */));
break;
}
case Id::Type::Artist:
{
auto artist {Artist::getById(context.dbSession, id.value)};
if (!artist)
throw RequestedDataNotFoundError {};
directoryNode.setAttribute("name", makeNameFilesystemCompatible(artist->getName()));
auto releases {artist->getReleases()};
for (const Release::pointer& release : releases)
directoryNode.addArrayChild("child", releaseToResponseNode(release, context.dbSession, user, false /* no id3 */));
break;
}
case Id::Type::Release:
{
auto release {Release::getById(context.dbSession, id.value)};
if (!release)
throw RequestedDataNotFoundError {};
directoryNode.setAttribute("name", makeNameFilesystemCompatible(release->getName()));
auto tracks {release->getTracks()};
for (const Track::pointer& track : tracks)
directoryNode.addArrayChild("child", trackToResponseNode(track, context.dbSession, user));
break;
}
default:
throw BadParameterGenericError {"id"};
bool moreResults{};
auto artists {Artist::getAll(context.dbSession, Artist::SortMethod::BySortName, std::nullopt, moreResults)};
for (const Artist::pointer& artist : artists)
directoryNode.addArrayChild("child", artistToResponseNode(user, artist, false /* no id3 */));
}
else if (artistId)
{
directoryNode.setAttribute("id", idToString(*artistId));
auto artist {Artist::getById(context.dbSession, *artistId)};
if (!artist)
throw RequestedDataNotFoundError {};
directoryNode.setAttribute("name", makeNameFilesystemCompatible(artist->getName()));
auto releases {artist->getReleases()};
for (const Release::pointer& release : releases)
directoryNode.addArrayChild("child", releaseToResponseNode(release, context.dbSession, user, false /* no id3 */));
}
else if (releaseId)
{
directoryNode.setAttribute("id", idToString(*releaseId));
auto release {Release::getById(context.dbSession, *releaseId)};
if (!release)
throw RequestedDataNotFoundError {};
directoryNode.setAttribute("name", makeNameFilesystemCompatible(release->getName()));
auto tracks {release->getTracks()};
for (const Track::pointer& track : tracks)
directoryNode.addArrayChild("child", trackToResponseNode(track, context.dbSession, user));
}
else
throw BadParameterGenericError {"id"};
return response;
}
@@ -1199,21 +1179,19 @@ Response
handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
{
// Mandatory params
const Id artistId {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (artistId.type != Id::Type::Artist)
throw BadParameterGenericError {"id"};
const ArtistId artistId {getMandatoryParameterAs<ArtistId>(context.parameters, "id")};
// Optional params
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").value_or(50)};
auto similarArtistIds {Service<Recommendation::IEngine>::get()->getSimilarArtists(context.dbSession,
artistId.value,
const auto similarArtistIds {Service<Recommendation::IEngine>::get()->getSimilarArtists(context.dbSession,
artistId,
{TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist},
5)};
auto transaction {context.dbSession.createSharedTransaction()};
const Artist::pointer artist {Artist::getById(context.dbSession, artistId.value)};
const Artist::pointer artist {Artist::getById(context.dbSession, artistId)};
if (!artist)
throw RequestedDataNotFoundError {};
@@ -1223,7 +1201,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
// "Returns a random collection of songs from the given artist and similar artists"
auto tracks {artist->getRandomTracks(count / 2)};
for (const Database::IdType similarArtistId : similarArtistIds)
for (const ArtistId similarArtistId : similarArtistIds)
{
const Artist::pointer similarArtist {Artist::getById(context.dbSession, similarArtistId)};
if (!similarArtist)
@@ -1318,7 +1296,7 @@ tracklistToResponseNode(const TrackList::pointer& tracklist, Session&)
{
Response::Node playlistNode;
playlistNode.setAttribute("id", IdToString({Id::Type::Playlist, tracklist.id()}));
playlistNode.setAttribute("id", idToString(tracklist->getId()));
playlistNode.setAttribute("name", tracklist->getName());
playlistNode.setAttribute("songCount", tracklist->getCount());
playlistNode.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(tracklist->getDuration()).count());
@@ -1334,9 +1312,7 @@ Response
handleGetPlaylistRequest(RequestContext& context)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Playlist)
throw BadParameterGenericError {"id"};
TrackListId trackListId {getMandatoryParameterAs<TrackListId>(context.parameters, "id")};
auto transaction {context.dbSession.createSharedTransaction()};
@@ -1344,7 +1320,7 @@ handleGetPlaylistRequest(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, trackListId)};
if (!tracklist)
throw RequestedDataNotFoundError {};
@@ -1411,7 +1387,7 @@ handleGetSongsByGenreRequest(RequestContext& context)
Response::Node& songsByGenreNode {response.createNode("songsByGenre")};
bool more;
auto tracks {Track::getByFilter(context.dbSession, {cluster.id()}, {}, Range {offset, size}, more)};
auto tracks {Track::getByFilter(context.dbSession, {cluster->getId()}, {}, Range {offset, size}, more)};
for (const Track::pointer& track : tracks)
songsByGenreNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
@@ -1504,9 +1480,9 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
struct StarParameters
{
std::vector<Id> artistIds;
std::vector<Id> releaseIds;
std::vector<Id> trackIds;
std::vector<ArtistId> artistIds;
std::vector<ReleaseId> releaseIds;
std::vector<TrackId> trackIds;
};
static
@@ -1515,34 +1491,10 @@ getStarParameters(const Wt::Http::ParameterMap& parameters)
{
StarParameters res;
std::vector<Id> ids {getMultiParametersAs<Id>(parameters, "id")};
res.artistIds = getMultiParametersAs<Id>(parameters, "artistId");
res.releaseIds = getMultiParametersAs<Id>(parameters, "albumId");
if (!std::all_of(std::cbegin(res.releaseIds ), std::cend(res.releaseIds ), [](const Id& id) { return id.type == Id::Type::Release; }))
throw BadParameterGenericError {"albumId"};
if (!std::all_of(std::cbegin(res.artistIds ), std::cend(res.artistIds ), [](const Id& id) { return id.type == Id::Type::Artist; }))
throw BadParameterGenericError {"artistId"};
// Redispatch the old "id" parameter in new lists
for (const Id& id : ids)
{
switch (id.type)
{
case Id::Type::Artist:
res.artistIds.emplace_back(id);
break;
case Id::Type::Release:
res.releaseIds.emplace_back(id);
break;
case Id::Type::Track:
res.trackIds.emplace_back(id);
break;
default:
throw BadParameterGenericError {"id"};
}
}
// TODO handle parameters for legacy file browsing
res.trackIds = getMultiParametersAs<TrackId>(parameters, "id");
res.artistIds = getMultiParametersAs<ArtistId>(parameters, "artistId");
res.releaseIds = getMultiParametersAs<ReleaseId>(parameters, "albumId");
return res;
}
@@ -1559,27 +1511,27 @@ handleStarRequest(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
for (const Id& id : params.artistIds)
for (const ArtistId id : params.artistIds)
{
Artist::pointer artist {Artist::getById(context.dbSession, id.value)};
Artist::pointer artist {Artist::getById(context.dbSession, id)};
if (!artist)
continue;
user.modify()->starArtist(artist);
}
for (const Id& id : params.releaseIds)
for (const ReleaseId id : params.releaseIds)
{
Release::pointer release {Release::getById(context.dbSession, id.value)};
Release::pointer release {Release::getById(context.dbSession, id)};
if (!release)
continue;
user.modify()->starRelease(release);
}
for (const Id& id : params.trackIds)
for (const TrackId id : params.trackIds)
{
Track::pointer track {Track::getById(context.dbSession, id.value)};
Track::pointer track {Track::getById(context.dbSession, id)};
if (!track)
continue;
@@ -1615,27 +1567,27 @@ handleUnstarRequest(RequestContext& context)
if (!user)
throw RequestedDataNotFoundError {};
for (const Id& id : params.artistIds)
for (const ArtistId id : params.artistIds)
{
Artist::pointer artist {Artist::getById(context.dbSession, id.value)};
Artist::pointer artist {Artist::getById(context.dbSession, id)};
if (!artist)
continue;
user.modify()->unstarArtist(artist);
}
for (const Id& id : params.releaseIds)
for (const ReleaseId id : params.releaseIds)
{
Release::pointer release {Release::getById(context.dbSession, id.value)};
Release::pointer release {Release::getById(context.dbSession, id)};
if (!release)
continue;
user.modify()->unstarRelease(release);
}
for (const Id& id : params.trackIds)
for (const TrackId id : params.trackIds)
{
Track::pointer track {Track::getById(context.dbSession, id.value)};
Track::pointer track {Track::getById(context.dbSession, id)};
if (!track)
continue;
@@ -1650,14 +1602,10 @@ static
Response
handleScrobble(RequestContext& context)
{
const std::vector<Id> ids {getMandatoryMultiParametersAs<Id>(context.parameters, "id")};
const std::vector<TrackId> ids {getMandatoryMultiParametersAs<TrackId>(context.parameters, "id")};
const std::vector<unsigned long> times {getMultiParametersAs<unsigned long>(context.parameters, "time")};
const bool submission{getParameterAs<bool>(context.parameters, "submission").value_or(true)};
// only for tracks
if (!std::all_of(std::cbegin(ids), std::cend(ids), [](const Id& id) { return id.type == Id::Type::Track; }))
throw BadParameterGenericError {"id"};
// playing now => no time to be provided
if (!submission && !times.empty())
throw BadParameterGenericError {"time"};
@@ -1672,19 +1620,19 @@ handleScrobble(RequestContext& context)
if (!submission)
{
Service<Scrobbling::IScrobbling>::get()->listenStarted({context.userId, ids.front().value});
Service<Scrobbling::IScrobbling>::get()->listenStarted({context.userId, ids.front()});
}
else
{
if (times.empty())
{
Service<Scrobbling::IScrobbling>::get()->listenFinished({context.userId, ids.front().value});
Service<Scrobbling::IScrobbling>::get()->listenFinished({context.userId, ids.front()});
}
else
{
for (std::size_t i {}; i < ids.size(); ++i)
{
const Database::IdType trackId {ids[i].value};
const TrackId trackId {ids[i]};
const unsigned long time {times[i]};
Service<Scrobbling::IScrobbling>::get()->addTimedListen({context.userId, trackId, Wt::WDateTime::fromTime_t(static_cast<std::time_t>(time / 1000))});
}
@@ -1701,7 +1649,7 @@ handleUpdateUserRequest(RequestContext& context)
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
std::optional<std::string> password {getParameterAs<std::string>(context.parameters, "password")};
Database::IdType userId;
UserId userId;
{
auto transaction {context.dbSession.createSharedTransaction()};
@@ -1709,7 +1657,7 @@ handleUpdateUserRequest(RequestContext& context)
if (!user)
throw RequestedDataNotFoundError {};
userId = user.id();
userId = user->getId();
}
if (password)
@@ -1742,18 +1690,13 @@ Response
handleUpdatePlaylistRequest(RequestContext& context)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "playlistId")};
if (id.type != Id::Type::Playlist)
throw BadParameterGenericError {"playlistId"};
TrackListId id {getMandatoryParameterAs<TrackListId>(context.parameters, "playlistId")};
// Optional parameters
auto name {getParameterAs<std::string>(context.parameters, "name")};
auto isPublic {getParameterAs<bool>(context.parameters, "public")};
std::vector<Id> trackIdsToAdd {getMultiParametersAs<Id>(context.parameters, "songIdToAdd")};
if (!std::all_of(std::cbegin(trackIdsToAdd), std::cend(trackIdsToAdd), [](const Id& id) { return id.type == Id::Type::Track; }))
throw BadParameterGenericError {"songIdToAdd"};
std::vector<TrackId> trackIdsToAdd {getMultiParametersAs<TrackId>(context.parameters, "songIdToAdd")};
std::vector<std::size_t> trackPositionsToRemove {getMultiParametersAs<std::size_t>(context.parameters, "songIndexToRemove")};
auto transaction {context.dbSession.createUniqueTransaction()};
@@ -1762,7 +1705,7 @@ handleUpdatePlaylistRequest(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, id)};
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
@@ -1789,13 +1732,13 @@ handleUpdatePlaylistRequest(RequestContext& context)
}
// Add tracks
for (const Id& trackIdToAdd : trackIdsToAdd)
for (const TrackId trackIdToAdd : trackIdsToAdd)
{
Track::pointer track {Track::getById(context.dbSession, trackIdToAdd.value)};
Track::pointer track {Track::getById(context.dbSession, trackIdToAdd)};
if (!track)
continue;
TrackListEntry::create(context.dbSession, track, tracklist );
TrackListEntry::create(context.dbSession, track, tracklist);
}
return Response::createOkResponse(context);
@@ -1832,10 +1775,7 @@ Response
handleCreateBookmark(RequestContext& context)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Track)
throw BadParameterGenericError {"id"};
TrackId id {getMandatoryParameterAs<TrackId>(context.parameters, "id")};
unsigned long position {getMandatoryParameterAs<unsigned long>(context.parameters, "position")};
const std::optional<std::string> comment {getParameterAs<std::string>(context.parameters, "comment")};
@@ -1845,7 +1785,7 @@ handleCreateBookmark(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
const Track::pointer track {Track::getById(context.dbSession, id.value)};
const Track::pointer track {Track::getById(context.dbSession, id)};
if (!track)
throw RequestedDataNotFoundError {};
@@ -1866,9 +1806,7 @@ Response
handleDeleteBookmark(RequestContext& context)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Track)
throw BadParameterGenericError {"id"};
TrackId id {getMandatoryParameterAs<TrackId>(context.parameters, "id")};
auto transaction {context.dbSession.createUniqueTransaction()};
@@ -1876,7 +1814,7 @@ handleDeleteBookmark(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
const Track::pointer track {Track::getById(context.dbSession, id.value)};
const Track::pointer track {Track::getById(context.dbSession, id)};
if (!track)
throw RequestedDataNotFoundError {};
@@ -1901,23 +1839,20 @@ void
handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
const auto trackId {getParameterAs<TrackId>(context.parameters, "id")};
const auto releaseId {getParameterAs<ReleaseId>(context.parameters, "id")};
if (!trackId && !releaseId)
throw BadParameterGenericError {"id"};
std::size_t size {getParameterAs<std::size_t>(context.parameters, "size").value_or(256)};
size = clamp(size, std::size_t {32}, std::size_t {1024});
size = Utils::clamp(size, std::size_t {32}, std::size_t {1024});
std::shared_ptr<CoverArt::IEncodedImage> cover;
switch (id.type)
{
case Id::Type::Track:
cover = Service<CoverArt::IGrabber>::get()->getFromTrack(context.dbSession, id.value, size);
break;
case Id::Type::Release:
cover = Service<CoverArt::IGrabber>::get()->getFromRelease(context.dbSession, id.value, size);
break;
default:
throw BadParameterGenericError {"id"};
}
if (trackId)
cover = Service<CoverArt::IGrabber>::get()->getFromTrack(context.dbSession, *trackId, size);
else if (releaseId)
cover = Service<CoverArt::IGrabber>::get()->getFromRelease(context.dbSession, *releaseId, size);
response.out().write(reinterpret_cast<const char*>(cover->getData()), cover->getDataSize());
response.setMimeType(std::string {cover->getMimeType()});
@@ -1928,7 +1863,7 @@ using CheckImplementedFunc = std::function<void()>;
struct RequestEntryPointInfo
{
RequestHandlerFunc func;
EnumSet<Database::UserType> allowedUserTypes {Database::UserType::DEMO, Database::UserType::REGULAR, Database::UserType::ADMIN};
EnumSet<UserType> allowedUserTypes {UserType::DEMO, UserType::REGULAR, UserType::ADMIN};
CheckImplementedFunc checkFunc {};
};
@@ -2019,11 +1954,11 @@ static std::unordered_map<std::string, RequestEntryPointInfo> requestEntryPoints
// User management
{"getUser", {handleGetUserRequest}},
{"getUsers", {handleGetUsersRequest, {Database::UserType::ADMIN}}},
{"createUser", {handleCreateUserRequest, {Database::UserType::ADMIN}, &checkSetPasswordImplemented}},
{"updateUser", {handleUpdateUserRequest, {Database::UserType::ADMIN}}},
{"deleteUser", {handleDeleteUserRequest, {Database::UserType::ADMIN}}},
{"changePassword", {handleChangePassword, {Database::UserType::REGULAR, Database::UserType::ADMIN}, &checkSetPasswordImplemented}},
{"getUsers", {handleGetUsersRequest, {UserType::ADMIN}}},
{"createUser", {handleCreateUserRequest, {UserType::ADMIN}, &checkSetPasswordImplemented}},
{"updateUser", {handleUpdateUserRequest, {UserType::ADMIN}}},
{"deleteUser", {handleDeleteUserRequest, {UserType::ADMIN}}},
{"changePassword", {handleChangePassword, {UserType::REGULAR, UserType::ADMIN}, &checkSetPasswordImplemented}},
// Bookmarks
{"getBookmarks", {handleGetBookmarks}},
@@ -2033,8 +1968,8 @@ static std::unordered_map<std::string, RequestEntryPointInfo> requestEntryPoints
{"savePlayQueue", {handleNotImplemented}},
// Media library scanning
{"getScanStatus", {Scan::handleGetScanStatus, {Database::UserType::ADMIN}}},
{"startScan", {Scan::handleStartScan, {Database::UserType::ADMIN}}},
{"getScanStatus", {Scan::handleGetScanStatus, {UserType::ADMIN}}},
{"startScan", {Scan::handleStartScan, {UserType::ADMIN}}},
};
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
@@ -2047,7 +1982,7 @@ static std::unordered_map<std::string, MediaRetrievalHandlerFunc> mediaRetrieval
};
static
Database::IdType
Database::UserId
authenticateUser(const Wt::Http::Request &request, const ClientInfo& clientInfo, Session& dbSession)
{
if (auto *authEnvService {Service<::Auth::IEnvService>::get()})
@@ -2108,7 +2043,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
Session& dbSession {_db.getTLSSession()};
const Database::IdType userId {authenticateUser(request, clientInfo, dbSession)};
const Database::UserId userId {authenticateUser(request, clientInfo, dbSession)};
RequestContext requestContext {parameters, dbSession, userId, clientInfo.name};
auto itEntryPoint {requestEntryPoints.find(requestPath)};
+18 -5
View File
@@ -19,12 +19,25 @@
#pragma once
#include <algorithm>
#include <functional>
template<class T, class Compare = std::less<>>
constexpr T clamp(T v, T lo, T hi, Compare comp = {})
namespace Utils
{
assert(!comp(hi, lo));
return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
}
template<class T, class Compare = std::less<>>
constexpr T clamp(T v, T lo, T hi, Compare comp = {})
{
assert(!comp(hi, lo));
return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
}
template <typename Container, typename T>
void
push_back_if_not_present(Container& container, const T& val)
{
if (std::find(std::cbegin(container), std::cend(container), val) == std::cend(container))
container.push_back(val);
}
}
+5 -5
View File
@@ -47,7 +47,7 @@ static const std::string authCookieName {"LmsAuth"};
static
void
createAuthToken(Database::IdType userId, const Wt::WDateTime& expiry)
createAuthToken(Database::UserId userId, const Wt::WDateTime& expiry)
{
const std::string secret {Service<::Auth::IAuthTokenService>::get()->createAuthToken(LmsApp->getDbSession(), userId, expiry)};
@@ -60,7 +60,7 @@ createAuthToken(Database::IdType userId, const Wt::WDateTime& expiry)
}
std::optional<Database::IdType>
std::optional<Database::UserId>
processAuthToken(const Wt::WEnvironment& env)
{
const std::string* authCookie {env.getCookie(authCookieName)};
@@ -111,7 +111,7 @@ class AuthModel : public Wt::WFormModel
Database::User::pointer user {Database::User::getByLoginName(LmsApp->getDbSession(), valueText(LoginNameField).toUTF8())};
user.modify()->setLastLogin(Wt::WDateTime::currentDateTime());
_userId = user.id();
_userId = user->getId();
isDemo = user->isDemo();
}
@@ -158,11 +158,11 @@ class AuthModel : public Wt::WFormModel
return (validation(field).state() == Wt::ValidationState::Valid);
}
std::optional<Database::IdType> getUserId() const { return _userId; }
std::optional<Database::UserId> getUserId() const { return _userId; }
private:
std::optional<Database::IdType> _userId;
std::optional<Database::UserId> _userId;
};
const AuthModel::Field AuthModel::LoginNameField {"login-name"};
+2 -2
View File
@@ -25,7 +25,7 @@
namespace UserInterface {
std::optional<Database::IdType>
std::optional<Database::UserId>
processAuthToken(const Wt::WEnvironment& env);
class Auth : public Wt::WTemplateFormView
@@ -33,7 +33,7 @@ class Auth : public Wt::WTemplateFormView
public:
Auth();
Wt::Signal<Database::IdType /*userId*/> userLoggedIn;
Wt::Signal<Database::UserId> userLoggedIn;
};
} // namespace UserInterface
+14 -14
View File
@@ -95,7 +95,7 @@ LmsApplication::getDbSession()
return _db.getTLSSession();
}
Wt::Dbo::ptr<Database::User>
Database::User::pointer
LmsApplication::getUser()
{
if (!_authenticatedUser)
@@ -104,7 +104,7 @@ LmsApplication::getUser()
return Database::User::getById(getDbSession(), _authenticatedUser->userId);
}
Database::IdType
Database::UserId
LmsApplication::getUserId()
{
return _authenticatedUser->userId;
@@ -135,7 +135,7 @@ LmsApplication::getUserLoginName()
LmsApplication::LmsApplication(const Wt::WEnvironment& env,
Database::Db& db,
LmsApplicationManager& appManager,
std::optional<Database::IdType> userId)
std::optional<Database::UserId> userId)
: Wt::WApplication {env}
, _db {db}
, _appManager {appManager}
@@ -208,7 +208,7 @@ void
LmsApplication::processPasswordAuth()
{
{
std::optional<Database::IdType> userId {processAuthToken(environment())};
std::optional<Database::UserId> userId {processAuthToken(environment())};
if (userId)
{
LMS_LOG(UI, DEBUG) << "User authenticated using Auth token!";
@@ -236,7 +236,7 @@ LmsApplication::processPasswordAuth()
else
{
Auth* auth {root()->addNew<Auth>()};
auth->userLoggedIn.connect(this, [this](Database::IdType userId)
auth->userLoggedIn.connect(this, [this](Database::UserId userId)
{
_authenticatedUser = {userId, true};
onUserLoggedIn();
@@ -272,7 +272,7 @@ LmsApplication::createArtistLink(Database::Artist::pointer artist)
if (const auto mbid {artist->getMBID()})
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()}};
else
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/" + std::to_string(artist.id())};
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString()};
}
std::unique_ptr<Wt::WAnchor>
@@ -296,7 +296,7 @@ LmsApplication::createReleaseLink(Database::Release::pointer release)
if (const auto mbid {release->getMBID()})
return Wt::WLink {Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()}};
else
return Wt::WLink {Wt::LinkType::InternalPath, "/release/" + std::to_string(release.id())};
return Wt::WLink {Wt::LinkType::InternalPath, "/release/" + release->getId().toString()};
}
std::unique_ptr<Wt::WAnchor>
@@ -320,7 +320,7 @@ LmsApplication::createCluster(Database::Cluster::pointer cluster, bool canDelete
{
auto getStyleClass = [](const Database::Cluster::pointer cluster)
{
switch (cluster->getType().id() % 6)
switch (cluster->getType()->getId().getValue() % 6)
{
case 0: return "label-info";
case 1: return "label-warning";
@@ -529,7 +529,7 @@ LmsApplication::createHome()
mainStack->addNew<UserView>();
}
explore->tracksAction.connect([this] (PlayQueueAction action, const std::vector<Database::IdType>& trackIds)
explore->tracksAction.connect([this] (PlayQueueAction action, const std::vector<Database::TrackId>& trackIds)
{
_playQueue->processTracks(action, trackIds);
});
@@ -544,15 +544,15 @@ LmsApplication::createHome()
_playQueue->playPrevious();
});
_mediaPlayer->scrobbleListenNow.connect([this](Database::IdType trackId)
_mediaPlayer->scrobbleListenNow.connect([this](Database::TrackId trackId)
{
LMS_LOG(UI, DEBUG) << "Received ScrobbleListenNow from player for trackId = " << trackId;
LMS_LOG(UI, DEBUG) << "Received ScrobbleListenNow from player for trackId = " << trackId.toString();
const Scrobbling::Listen listen {getUserId(), trackId};
Service<Scrobbling::IScrobbling>::get()->listenStarted(listen);
});
_mediaPlayer->scrobbleListenFinished.connect([this](Database::IdType trackId, unsigned durationMs)
_mediaPlayer->scrobbleListenFinished.connect([this](Database::TrackId trackId, unsigned durationMs)
{
LMS_LOG(UI, DEBUG) << "Received ScrobbleListenFinished from player for trackId = " << trackId << ", duration = " << (durationMs / 1000) << "s";
LMS_LOG(UI, DEBUG) << "Received ScrobbleListenFinished from player for trackId = " << trackId.toString() << ", duration = " << (durationMs / 1000) << "s";
const std::chrono::milliseconds duration {durationMs};
const Scrobbling::Listen listen {getUserId(), trackId};
Service<Scrobbling::IScrobbling>::get()->listenFinished(listen, std::chrono::duration_cast<std::chrono::seconds>(duration));
@@ -563,7 +563,7 @@ LmsApplication::createHome()
_playQueue->playNext();
});
_playQueue->trackSelected.connect([this] (Database::IdType trackId, bool play, float replayGain)
_playQueue->trackSelected.connect([this] (Database::TrackId trackId, bool play, float replayGain)
{
_mediaPlayer->loadTrack(trackId, play, replayGain);
});
+9 -9
View File
@@ -51,7 +51,7 @@ class LmsApplication : public Wt::WApplication
{
public:
LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager, std::optional<Database::IdType> userId = std::nullopt);
LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager, std::optional<Database::UserId> userId = std::nullopt);
~LmsApplication();
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager);
@@ -62,8 +62,8 @@ class LmsApplication : public Wt::WApplication
std::shared_ptr<CoverResource> getCoverResource() { return _coverResource; }
Database::Session& getDbSession(); // always thread safe
Wt::Dbo::ptr<Database::User> getUser();
Database::IdType getUserId();
Database::ObjectPtr<Database::User> getUser();
Database::UserId getUserId();
bool isUserAuthStrong() const; // user must be logged in prior this call
Database::UserType getUserType(); // user must be logged in prior this call
std::string getUserLoginName(); // user must be logged in prior this call
@@ -84,11 +84,11 @@ class LmsApplication : public Wt::WApplication
};
void notifyMsg(MsgType type, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds {4000});
static Wt::WLink createArtistLink(Wt::Dbo::ptr<Database::Artist> artist);
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Wt::Dbo::ptr<Database::Artist> artist, bool addText = true);
static Wt::WLink createReleaseLink(Wt::Dbo::ptr<Database::Release> release);
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Wt::Dbo::ptr<Database::Release> release, bool addText = true);
static std::unique_ptr<Wt::WText> createCluster(Wt::Dbo::ptr<Database::Cluster> cluster, bool canDelete = false);
static Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist);
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool addText = true);
static Wt::WLink createReleaseLink(Database::ObjectPtr<Database::Release> release);
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Database::ObjectPtr<Database::Release> release, bool addText = true);
static std::unique_ptr<Wt::WText> createCluster(Database::ObjectPtr<Database::Cluster> cluster, bool canDelete = false);
Wt::WPopupMenu* createPopupMenu();
MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; }
@@ -119,7 +119,7 @@ class LmsApplication : public Wt::WApplication
Scanner::Events _scannerEvents;
struct UserAuthInfo
{
Database::IdType userId;
Database::UserId userId;
bool strongAuth {};
};
std::optional<UserAuthInfo> _authenticatedUser;
+1 -1
View File
@@ -43,6 +43,6 @@ namespace UserInterface
void unregisterApplication(LmsApplication& application);
std::mutex _mutex;
std::unordered_map<Database::IdType /* user */, std::unordered_set<LmsApplication*>> m_applications;
std::unordered_map<Database::UserId, std::unordered_set<LmsApplication*>> m_applications;
};
} // UserInterface
+4 -4
View File
@@ -154,7 +154,7 @@ replayGainPreAmpGainFromString(const std::string& str)
if (!value)
return std::nullopt;
return clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
return Utils::clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
}
static MediaPlayer::Settings settingsfromJSString(const std::string& strSettings)
@@ -233,9 +233,9 @@ MediaPlayer::MediaPlayer()
}
void
MediaPlayer::loadTrack(Database::IdType trackId, bool play, float replayGain)
MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain)
{
LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId;
LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId.toString();
std::ostringstream oss;
{
@@ -252,7 +252,7 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play, float replayGain)
oss
<< "var params = {"
<< " trackId :\"" << trackId << "\","
<< " trackId :\"" << trackId.toString() << "\","
<< " nativeResource: \"" << nativeResource << "\","
<< " transcodeResource: \"" << transcodeResource << "\","
<< " duration: " << std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count() << ","
+9 -9
View File
@@ -94,22 +94,22 @@ class MediaPlayer : public Wt::WTemplate
MediaPlayer& operator=(const MediaPlayer&) = delete;
MediaPlayer& operator=(MediaPlayer&&) = delete;
std::optional<Database::IdType> getTrackLoaded() const { return _trackIdLoaded; }
std::optional<Database::TrackId> getTrackLoaded() const { return _trackIdLoaded; }
void loadTrack(Database::IdType trackId, bool play, float replayGain);
void loadTrack(Database::TrackId trackId, bool play, float replayGain);
void stop();
std::optional<Settings> getSettings() const { return _settings; }
void setSettings(const Settings& settings);
// Signals
Wt::JSignal<> playPrevious;
Wt::JSignal<> playNext;
Wt::Signal<Database::IdType> trackLoaded;
Wt::Signal<> settingsLoaded;
Wt::JSignal<> playPrevious;
Wt::JSignal<> playNext;
Wt::Signal<Database::TrackId> trackLoaded;
Wt::Signal<> settingsLoaded;
Wt::JSignal<Database::IdType> scrobbleListenNow;
Wt::JSignal<Database::IdType, unsigned /* ms */> scrobbleListenFinished;
Wt::JSignal<Database::TrackId::ValueType> scrobbleListenNow;
Wt::JSignal<Database::TrackId::ValueType, unsigned /* ms */> scrobbleListenFinished;
Wt::JSignal<> playbackEnded;
@@ -117,7 +117,7 @@ class MediaPlayer : public Wt::WTemplate
std::unique_ptr<AudioFileResource> _audioFileResource;
std::unique_ptr<AudioTranscodeResource> _audioTranscodeResource;
std::optional<Database::IdType> _trackIdLoaded;
std::optional<Database::TrackId> _trackIdLoaded;
std::optional<Settings> _settings;
Wt::JSignal<std::string> _settingsLoaded;
+14 -13
View File
@@ -23,6 +23,7 @@
#include <Wt/WText.h>
#include "database/Cluster.hpp"
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/TrackList.hpp"
@@ -129,7 +130,7 @@ PlayQueue::PlayQueue()
if (LmsApp->getUser()->isDemo())
{
LMS_LOG(UI, DEBUG) << "Removing tracklist id " << _tracklistId;
LMS_LOG(UI, DEBUG) << "Removing tracklist id " << _tracklistId.toString();
auto tracklist = Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId);
if (tracklist)
tracklist.remove();
@@ -167,7 +168,7 @@ PlayQueue::PlayQueue()
trackList = Database::TrackList::create(LmsApp->getDbSession(), currentPlayQueueName, Database::TrackList::Type::Internal, false, LmsApp->getUser());
}
_tracklistId = trackList.id();
_tracklistId = trackList->getId();
}
updateInfo();
@@ -226,7 +227,7 @@ PlayQueue::loadTrack(std::size_t pos, bool play)
{
updateCurrentTrack(false);
Database::IdType trackId {};
Database::TrackId trackId {};
bool addRadioTrack {};
std::optional<float> replayGain {};
{
@@ -253,7 +254,7 @@ PlayQueue::loadTrack(std::size_t pos, bool play)
_trackPos = pos;
auto track = tracklist->getEntry(*_trackPos)->getTrack();
trackId = track.id();
trackId = track->getId();
replayGain = getReplayGain(pos, track);
@@ -313,7 +314,7 @@ PlayQueue::updateCurrentTrack(bool selected)
}
std::size_t
PlayQueue::enqueueTracks(const std::vector<Database::IdType>& trackIds)
PlayQueue::enqueueTracks(const std::vector<Database::TrackId>& trackIds)
{
std::size_t nbTracksQueued {};
@@ -323,7 +324,7 @@ PlayQueue::enqueueTracks(const std::vector<Database::IdType>& trackIds)
auto tracklist {getTrackList()};
std::size_t nbTracksToEnqueue {tracklist->getCount() + trackIds.size() > _nbMaxEntries ? _nbMaxEntries - tracklist->getCount() : trackIds.size()};
for (Database::IdType trackId : trackIds)
for (const Database::TrackId trackId : trackIds)
{
Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
if (!track)
@@ -344,7 +345,7 @@ PlayQueue::enqueueTracks(const std::vector<Database::IdType>& trackIds)
}
void
PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::IdType>& trackIds)
PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::TrackId>& trackIds)
{
std::size_t nbAddedTracks {};
@@ -367,7 +368,7 @@ PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::IdT
{
clearTracks();
{
std::vector<Database::IdType> shuffledTrackIds {trackIds};
std::vector<Database::TrackId> shuffledTrackIds {trackIds};
Random::shuffleContainer(shuffledTrackIds);
nbAddedTracks = enqueueTracks(shuffledTrackIds);
}
@@ -402,9 +403,9 @@ PlayQueue::addSome()
void
PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
{
const auto tracklistEntryId {tracklistEntry.id()};
const Database::TrackListEntryId tracklistEntryId {tracklistEntry->getId()};
const auto track {tracklistEntry->getTrack()};
const Database::IdType trackId {track->id()};
const Database::TrackId trackId {track->getId()};
Wt::WTemplate* entry = _entriesContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.PlayQueue.template.entry"));
@@ -434,7 +435,7 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
{
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
auto cover = std::make_unique<Wt::WImage>();
cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(release.id(), CoverResource::Size::Large));
cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(release->getId(), CoverResource::Size::Large));
cover->setStyleClass("Lms-cover");
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
anchor->setImage(std::move(cover));
@@ -443,7 +444,7 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
else
{
auto cover = entry->bindNew<Wt::WImage>("cover");
cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(track.id(), CoverResource::Size::Large));
cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(track->getId(), CoverResource::Size::Large));
cover->setStyleClass("Lms-cover");
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
}
@@ -520,7 +521,7 @@ PlayQueue::enqueueRadioTracks()
{
const auto similarTrackIds {Service<Recommendation::IEngine>::get()->getSimilarTracksFromTrackList(LmsApp->getDbSession(), _tracklistId, 3)};
std::vector<Database::IdType> trackToAddIds(std::cbegin(similarTrackIds), std::cend(similarTrackIds));
std::vector<Database::TrackId> trackToAddIds(std::cbegin(similarTrackIds), std::cend(similarTrackIds));
Random::shuffleContainer(trackToAddIds);
enqueueTracks(trackToAddIds);
}
+7 -7
View File
@@ -49,7 +49,7 @@ class PlayQueue : public Wt::WTemplate
public:
PlayQueue();
void processTracks(PlayQueueAction action, const std::vector<Database::IdType>& trackIds);
void processTracks(PlayQueueAction action, const std::vector<Database::TrackId>& trackIds);
// play the next track in the queue
void playNext();
@@ -58,19 +58,19 @@ class PlayQueue : public Wt::WTemplate
void playPrevious();
// Signal emitted when a track is to be load(and optionally played)
Wt::Signal<Database::IdType /*trackId*/, bool /*play*/, float /* replayGain */> trackSelected;
Wt::Signal<Database::TrackId, bool /*play*/, float /* replayGain */> trackSelected;
// Signal emitted when track is unselected (has to be stopped)
Wt::Signal<> trackUnselected;
private:
Wt::Dbo::ptr<Database::TrackList> getTrackList() const;
Database::ObjectPtr<Database::TrackList> getTrackList() const;
bool isFull() const;
void clearTracks();
std::size_t enqueueTracks(const std::vector<Database::IdType>& trackIds);
std::size_t enqueueTracks(const std::vector<Database::TrackId>& trackIds);
void addSome();
void addEntry(const Wt::Dbo::ptr<Database::TrackListEntry>& entry);
void addEntry(const Database::ObjectPtr<Database::TrackListEntry>& entry);
void enqueueRadioTracks();
void updateInfo();
void updateCurrentTrack(bool selected);
@@ -82,7 +82,7 @@ class PlayQueue : public Wt::WTemplate
void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Finder> similarityFinder);
void addRadioTrackFromClusters();
std::optional<float> getReplayGain(std::size_t pos, const Wt::Dbo::ptr<Database::Track>& track) const;
std::optional<float> getReplayGain(std::size_t pos, const Database::ObjectPtr<Database::Track>& track) const;
static inline constexpr std::size_t _nbMaxEntries {1000};
static inline constexpr std::size_t _batchSize {12};
@@ -90,7 +90,7 @@ class PlayQueue : public Wt::WTemplate
bool _repeatAll {};
bool _radioMode {};
bool _mediaPlayerSettingsLoaded {};
Database::IdType _tracklistId {};
Database::TrackListId _tracklistId {};
InfiniteScrollingContainer* _entriesContainer {};
Wt::WText* _nbTracks {};
Wt::WText* _repeatBtn {};
+3 -2
View File
@@ -34,7 +34,8 @@ namespace UserInterface
PlayShuffled,
};
using PlayQueueActionSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&>;
using PlayQueueActionArtistSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::ArtistId>&>;
using PlayQueueActionReleaseSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::ReleaseId>&>;
using PlayQueueActionTrackSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::TrackId>&>;
}
+1 -1
View File
@@ -197,7 +197,7 @@ class SettingsModel : public Wt::WFormModel
if (_authPasswordService && !valueText(PasswordField).empty())
{
_authPasswordService->setPassword(LmsApp->getDbSession(), user.id(), valueText(PasswordField).toUTF8());
_authPasswordService->setPassword(LmsApp->getDbSession(), user->getId(), valueText(PasswordField).toUTF8());
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ class InitWizardModel : public Wt::WFormModel
Database::User::pointer user {Database::User::create(LmsApp->getDbSession(), valueText(AdminLoginField).toUTF8())};
user.modify()->setType(Database::UserType::ADMIN);
Service<::Auth::IPasswordService>::get()->setPassword(LmsApp->getDbSession(), user.id(), valueText(PasswordField).toUTF8());
Service<::Auth::IPasswordService>::get()->setPassword(LmsApp->getDbSession(), user->getId(), valueText(PasswordField).toUTF8());
}
bool validateField(Field field)
+5 -5
View File
@@ -53,7 +53,7 @@ class UserModel : public Wt::WFormModel
static inline const Field PasswordField {"password"};
static inline const Field DemoField {"demo"};
UserModel(std::optional<Database::IdType> userId, ::Auth::IPasswordService* authPasswordService)
UserModel(std::optional<Database::UserId> userId, ::Auth::IPasswordService* authPasswordService)
: _userId {userId}
, _authPasswordService {authPasswordService}
{
@@ -87,7 +87,7 @@ class UserModel : public Wt::WFormModel
throw UserNotFoundException {};
if (_authPasswordService && !valueText(PasswordField).empty())
_authPasswordService->setPassword(LmsApp->getDbSession(), user.id(), valueText(PasswordField).toUTF8());
_authPasswordService->setPassword(LmsApp->getDbSession(), user->getId(), valueText(PasswordField).toUTF8());
}
else
{
@@ -103,7 +103,7 @@ class UserModel : public Wt::WFormModel
user.modify()->setType(Database::UserType::DEMO);
if (_authPasswordService)
_authPasswordService->setPassword(LmsApp->getDbSession(), user.id(), valueText(PasswordField).toUTF8());
_authPasswordService->setPassword(LmsApp->getDbSession(), user->getId(), valueText(PasswordField).toUTF8());
}
}
@@ -176,7 +176,7 @@ class UserModel : public Wt::WFormModel
return false;
}
std::optional<Database::IdType> _userId;
std::optional<Database::UserId> _userId;
::Auth::IPasswordService* _authPasswordService {};
};
@@ -196,7 +196,7 @@ UserView::refreshView()
if (!wApp->internalPathMatches("/admin/user"))
return;
auto userId = StringUtils::readAs<Database::IdType>(wApp->internalPathNextPart("/admin/user/"));
const std::optional<Database::UserId> userId {StringUtils::readAs<Database::UserId::ValueType>(wApp->internalPathNextPart("/admin/user/"))};
clear();
+2 -2
View File
@@ -72,7 +72,7 @@ UsersView::refreshView()
auto users = Database::User::getAll(LmsApp->getDbSession());
for (const auto& user : users)
{
const Database::IdType userId {user.id()};
const Database::UserId userId {user->getId()};
Wt::WTemplate* entry {_container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"))};
@@ -93,7 +93,7 @@ UsersView::refreshView()
Wt::WPushButton* editBtn = entry->bindNew<Wt::WPushButton>("edit-btn", Wt::WString::tr("Lms.Admin.Users.edit"));
editBtn->clicked().connect([=]()
{
LmsApp->setInternalPath("/admin/user/" + std::to_string(userId), true);
LmsApp->setInternalPath("/admin/user/" + userId.toString(), true);
});
Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del"));
+1 -1
View File
@@ -31,7 +31,7 @@ namespace UserInterface
{
using namespace Database;
std::vector<Wt::Dbo::ptr<Database::Artist>>
std::vector<Database::ObjectPtr<Database::Artist>>
ArtistCollector::get(std::optional<Database::Range> range, bool& moreResults)
{
range = getActualRange(range);
+3 -3
View File
@@ -38,13 +38,13 @@ namespace UserInterface
public:
using DatabaseCollectorBase::DatabaseCollectorBase;
std::vector<Wt::Dbo::ptr<Database::Artist>> get(std::optional<Database::Range> range, bool& moreResults);
std::vector<Database::ObjectPtr<Database::Artist>> get(std::optional<Database::Range> range, bool& moreResults);
void reset() { _randomArtists.clear(); }
void setArtistLinkType(std::optional<Database::TrackArtistLinkType> linkType) { _linkType = linkType; }
private:
std::vector<Wt::Dbo::ptr<Database::Artist>> getRandomArtists(std::optional<Range> range, bool& moreResults);
std::vector<Database::IdType> _randomArtists;
std::vector<Database::ObjectPtr<Database::Artist>> getRandomArtists(std::optional<Range> range, bool& moreResults);
std::vector<Database::ArtistId> _randomArtists;
std::optional<Database::TrackArtistLinkType> _linkType;
};
} // ns UserInterface
+2 -2
View File
@@ -26,7 +26,7 @@
namespace UserInterface::ArtistListHelpers
{
std::unique_ptr<Wt::WTemplate>
createEntry(const Wt::Dbo::ptr<Database::Artist>& artist)
createEntry(const Database::ObjectPtr<Database::Artist>& artist)
{
auto res {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry"))};
res->bindWidget("name", LmsApplication::createArtistAnchor(artist));
@@ -35,7 +35,7 @@ namespace UserInterface::ArtistListHelpers
}
std::unique_ptr<Wt::WTemplate>
createEntrySmall(const Wt::Dbo::ptr<Database::Artist>& artist)
createEntrySmall(const Database::ObjectPtr<Database::Artist>& artist)
{
auto res {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry-small"))};
res->bindWidget("name", LmsApplication::createArtistAnchor(artist));
+2 -2
View File
@@ -32,7 +32,7 @@ namespace Database
namespace UserInterface::ArtistListHelpers
{
std::unique_ptr<Wt::WTemplate> createEntry(const Wt::Dbo::ptr<Database::Artist>& artist);
std::unique_ptr<Wt::WTemplate> createEntrySmall(const Wt::Dbo::ptr<Database::Artist>& artist);
std::unique_ptr<Wt::WTemplate> createEntry(const Database::ObjectPtr<Database::Artist>& artist);
std::unique_ptr<Wt::WTemplate> createEntrySmall(const Database::ObjectPtr<Database::Artist>& artist);
}
+9 -8
View File
@@ -26,6 +26,7 @@
#include <Wt/WText.h>
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Release.hpp"
#include "database/ScanSettings.hpp"
#include "database/Session.hpp"
@@ -68,7 +69,7 @@ Artist::Artist(Filters* filters)
}
static
std::optional<IdType>
std::optional<ArtistId>
extractArtistIdFromInternalPath()
{
if (wApp->internalPathMatches("/artist/mbid/"))
@@ -78,13 +79,13 @@ extractArtistIdFromInternalPath()
{
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
if (const Database::Artist::pointer artist {Database::Artist::getByMBID(LmsApp->getDbSession(), *mbid)})
return artist.id();
return artist->getId();
}
return std::nullopt;
}
return StringUtils::readAs<Database::IdType>(wApp->internalPathNextPart("/artist/"));
return StringUtils::readAs<Database::ArtistId::ValueType>(wApp->internalPathNextPart("/artist/"));
}
void
@@ -129,7 +130,7 @@ Artist::refreshView()
{
for (auto cluster : clusters)
{
auto clusterId = cluster.id();
auto clusterId = cluster->getId();
auto entry = clusterContainers->addWidget(LmsApp->createCluster(cluster));
entry->clicked().connect([=]
{
@@ -197,7 +198,7 @@ Artist::refreshView()
}
void
Artist::refreshReleases(const Wt::Dbo::ptr<Database::Artist>& artist)
Artist::refreshReleases(const Database::ObjectPtr<Database::Artist>& artist)
{
const auto releases {artist->getReleases(_filters->getClusterIds())};
if (releases.empty())
@@ -213,7 +214,7 @@ Artist::refreshReleases(const Wt::Dbo::ptr<Database::Artist>& artist)
}
void
Artist::refreshNonReleaseTracks(const Wt::Dbo::ptr<Database::Artist>& artist)
Artist::refreshNonReleaseTracks(const Database::ObjectPtr<Database::Artist>& artist)
{
if (!artist->hasNonReleaseTracks())
return;
@@ -229,7 +230,7 @@ Artist::refreshNonReleaseTracks(const Wt::Dbo::ptr<Database::Artist>& artist)
}
void
Artist::refreshSimilarArtists(const std::unordered_set<Database::IdType>& similarArtistsId)
Artist::refreshSimilarArtists(const std::vector<Database::ArtistId>& similarArtistsId)
{
if (similarArtistsId.empty())
return;
@@ -237,7 +238,7 @@ Artist::refreshSimilarArtists(const std::unordered_set<Database::IdType>& simila
setCondition("if-has-similar-artists", true);
Wt::WContainerWidget* similarArtistsContainer {bindNew<Wt::WContainerWidget>("similar-artists")};
for (Database::IdType artistId : similarArtistsId)
for (const Database::ArtistId artistId : similarArtistsId)
{
const Database::Artist::pointer similarArtist{Database::Artist::getById(LmsApp->getDbSession(), artistId)};
if (!similarArtist)
+7 -7
View File
@@ -45,15 +45,15 @@ namespace UserInterface
public:
Artist(Filters* filters);
PlayQueueActionSignal artistsAction;
PlayQueueActionSignal tracksAction;
PlayQueueActionArtistSignal artistsAction;
PlayQueueActionTrackSignal tracksAction;
private:
void refreshView();
void refreshReleases(const Wt::Dbo::ptr<Database::Artist>& artist);
void refreshNonReleaseTracks(const Wt::Dbo::ptr<Database::Artist>& artist);
void refreshSimilarArtists(const std::unordered_set<Database::IdType>& similarArtistsId);
void refreshLinks(const Wt::Dbo::ptr<Database::Artist>& artist);
void refreshReleases(const Database::ObjectPtr<Database::Artist>& artist);
void refreshNonReleaseTracks(const Database::ObjectPtr<Database::Artist>& artist);
void refreshSimilarArtists(const std::vector<Database::ArtistId>& similarArtistsId);
void refreshLinks(const Database::ObjectPtr<Database::Artist>& artist);
void addSomeNonReleaseTracks();
static constexpr std::size_t _tracksBatchSize {6};
@@ -61,7 +61,7 @@ namespace UserInterface
Filters* _filters {};
InfiniteScrollingContainer* _trackContainer {};
Database::IdType _artistId {};
Database::ArtistId _artistId {};
};
} // namespace UserInterface
+15 -15
View File
@@ -129,16 +129,16 @@ Explore::search(const Wt::WString& searchText)
}
static
std::vector<Database::IdType>
getArtistsTracks(Database::Session& session, const std::vector<Database::IdType>& artistsId, const std::set<Database::IdType>&)
std::vector<Database::TrackId>
getArtistsTracks(Database::Session& session, const std::vector<Database::ArtistId>& artistsId, const std::vector<Database::ClusterId>&)
{
std::vector<Database::IdType> res;
std::vector<Database::TrackId> res;
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
for (Database::IdType artistId : artistsId)
for (const Database::ArtistId artistId : artistsId)
{
Database::Artist::pointer artist {Database::Artist::getById(session, artistId)};
const Database::Artist::pointer artist {Database::Artist::getById(session, artistId)};
if (!artist)
continue;
@@ -146,49 +146,49 @@ getArtistsTracks(Database::Session& session, const std::vector<Database::IdType>
const std::vector<Database::Track::pointer> tracks {artist->getTracks()};
res.reserve(res.size() + tracks.size());
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track.id(); });
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track->getId(); });
}
return res;
}
static
std::vector<Database::IdType>
getReleasesTracks(Database::Session& session, const std::vector<Database::IdType>& releasesId, const std::set<Database::IdType>& clusters)
std::vector<Database::TrackId>
getReleasesTracks(Database::Session& session, const std::vector<Database::ReleaseId>& releasesId, const std::vector<Database::ClusterId>& clusters)
{
std::vector<Database::IdType> res;
std::vector<Database::TrackId> res;
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
for (Database::IdType releaseId : releasesId)
for (const Database::ReleaseId releaseId : releasesId)
{
Database::Release::pointer release {Database::Release::getById(session, releaseId)};
const Database::Release::pointer release {Database::Release::getById(session, releaseId)};
if (!release)
continue;
const std::vector<Database::Track::pointer> tracks {release->getTracks(clusters)};
res.reserve(res.size() + tracks.size());
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track.id(); });
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track->getId(); });
}
return res;
}
void
Explore::handleArtistsAction(PlayQueueAction action, const std::vector<Database::IdType>& artistsId)
Explore::handleArtistsAction(PlayQueueAction action, const std::vector<Database::ArtistId>& artistsId)
{
tracksAction.emit(action, getArtistsTracks(LmsApp->getDbSession(), artistsId, _filters->getClusterIds()));
}
void
Explore::handleReleasesAction(PlayQueueAction action, const std::vector<Database::IdType>& releasesId)
Explore::handleReleasesAction(PlayQueueAction action, const std::vector<Database::ReleaseId>& releasesId)
{
tracksAction.emit(action, getReleasesTracks(LmsApp->getDbSession(), releasesId, _filters->getClusterIds()));
}
void
Explore::handleTracksAction(PlayQueueAction action, const std::vector<Database::IdType>& tracksId)
Explore::handleTracksAction(PlayQueueAction action, const std::vector<Database::TrackId>& tracksId)
{
tracksAction.emit(action, tracksId);
}

Some files were not shown because too many files have changed in this diff Show More