Rework DB session + transactions. Now can handle multiple read only transactions in parallel

This commit is contained in:
emeric
2019-07-16 13:17:23 +02:00
parent 2a1fb0182b
commit 38ce2600d2
79 changed files with 1840 additions and 1369 deletions
+5
View File
@@ -20,6 +20,11 @@ AC_CHECK_HEADERS([Wt/WApplication.h pstreams/pstream.h curl/curl.h],
[], [],
[AC_MSG_ERROR([Header not found or unusable !])]) [AC_MSG_ERROR([Header not found or unusable !])])
AC_CHECK_LIB( [pthread],
[pthread_rwlock_unlock],
,
[AC_MSG_ERROR([libpthread not found!])])
AC_CHECK_LIB([wt], AC_CHECK_LIB([wt],
[main], [main],
, ,
+4 -2
View File
@@ -19,8 +19,8 @@ lms_SOURCES = \
$(srcdir)/database/Artist.hpp \ $(srcdir)/database/Artist.hpp \
$(srcdir)/database/Cluster.cpp \ $(srcdir)/database/Cluster.cpp \
$(srcdir)/database/Cluster.hpp \ $(srcdir)/database/Cluster.hpp \
$(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/Database.cpp \
$(srcdir)/database/DatabaseHandler.hpp \ $(srcdir)/database/Database.hpp \
$(srcdir)/database/TrackArtistLink.cpp \ $(srcdir)/database/TrackArtistLink.cpp \
$(srcdir)/database/TrackArtistLink.hpp \ $(srcdir)/database/TrackArtistLink.hpp \
$(srcdir)/database/TrackFeatures.cpp \ $(srcdir)/database/TrackFeatures.cpp \
@@ -32,6 +32,8 @@ lms_SOURCES = \
$(srcdir)/database/Release.hpp \ $(srcdir)/database/Release.hpp \
$(srcdir)/database/ScanSettings.cpp \ $(srcdir)/database/ScanSettings.cpp \
$(srcdir)/database/ScanSettings.hpp \ $(srcdir)/database/ScanSettings.hpp \
$(srcdir)/database/Session.cpp \
$(srcdir)/database/Session.hpp \
$(srcdir)/database/SimilaritySettings.cpp \ $(srcdir)/database/SimilaritySettings.cpp \
$(srcdir)/database/SimilaritySettings.hpp \ $(srcdir)/database/SimilaritySettings.hpp \
$(srcdir)/database/SqlQuery.cpp \ $(srcdir)/database/SqlQuery.cpp \
+149 -142
View File
@@ -22,6 +22,7 @@
#include <mutex> #include <mutex>
#include <numeric> #include <numeric>
#include <random> #include <random>
#include <thread>
#include <Wt/Auth/Identity.h> #include <Wt/Auth/Identity.h>
#include <Wt/WLocalDateTime.h> #include <Wt/WLocalDateTime.h>
@@ -33,6 +34,7 @@
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/TrackList.hpp" #include "database/TrackList.hpp"
#include "database/User.hpp"
#include "main/Service.hpp" #include "main/Service.hpp"
#include "similarity/SimilaritySearcher.hpp" #include "similarity/SimilaritySearcher.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
@@ -113,10 +115,37 @@ struct ClientInfo
struct RequestContext struct RequestContext
{ {
const Wt::Http::ParameterMap& parameters; const Wt::Http::ParameterMap& parameters;
Database::Handler& db; Database::Session& dbSession;
std::string userName; std::string userName;
}; };
// TODO handle multiple databases
static thread_local std::map<std::thread::id, std::unique_ptr<Database::Session>> dbSessions;
static
Database::Session&
getOrCreateDbSession(Database::Database& db)
{
static std::mutex mutex;
std::unique_lock<std::mutex> lock {mutex};
auto it {dbSessions.find(std::this_thread::get_id())};
if (it != dbSessions.end())
return *it->second;
auto res {dbSessions.emplace(std::this_thread::get_id(), db.createSession())};
assert(res.second);
return *(res.first->second);
}
static
void
cleanDbSessions()
{
dbSessions.clear();
}
// requests // requests
using RequestHandlerFunc = std::function<Response(RequestContext& context)>; using RequestHandlerFunc = std::function<Response(RequestContext& context)>;
static Response handlePingRequest(RequestContext& context); static Response handlePingRequest(RequestContext& context);
@@ -289,23 +318,15 @@ getClientInfo(const Wt::Http::ParameterMap& parameters)
return res; return res;
} }
static SubsonicResource::SubsonicResource(Database::Database& db)
bool : _db {db}
checkPassword(Database::Handler& db, const ClientInfo& clientInfo)
{ {
auto authUser {db.getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, clientInfo.user)};
if (!authUser.isValid())
{
LMS_LOG(API_SUBSONIC, ERROR) << "Cannot find user '" << clientInfo.user << "'";
return false;
} }
return db.getPasswordService().verifyPassword(authUser, clientInfo.password) == Wt::Auth::PasswordResult::PasswordValid; SubsonicResource::~SubsonicResource()
}
SubsonicResource::SubsonicResource(Wt::Dbo::SqlConnectionPool& connectionPool)
: _db {connectionPool}
{ {
LMS_LOG(API_SUBSONIC, DEBUG) << "Cleaning db sessions...";
cleanDbSessions();
} }
std::vector<std::string> std::vector<std::string>
@@ -371,24 +392,20 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
try try
{ {
static std::mutex mutex; Database::Session& dbSession {getOrCreateDbSession(_db)};
ClientInfo clientInfo {getClientInfo(parameters)}; const ClientInfo clientInfo {getClientInfo(parameters)};
std::unique_lock<std::mutex> lock{mutex}; // For now just handle request s one by one if (!dbSession.checkUserPassword(clientInfo.user, clientInfo.password))
if (!checkPassword(_db, clientInfo))
throw Error {Error::Code::WrongUsernameOrPassword}; throw Error {Error::Code::WrongUsernameOrPassword};
RequestContext requestContext {.parameters = parameters, .db = _db, .userName = clientInfo.user}; RequestContext requestContext {.parameters = parameters, .dbSession = dbSession, .userName = clientInfo.user};
auto itHandler {requestHandlers.find(request.path())}; auto itHandler {requestHandlers.find(request.path())};
if (itHandler != requestHandlers.end()) if (itHandler != requestHandlers.end())
{ {
Response resp {(itHandler->second)(requestContext)}; Response resp {(itHandler->second)(requestContext)};
lock.unlock();
resp.write(response.out(), format); resp.write(response.out(), format);
response.setMimeType(ResponseFormatToMimeType(format)); response.setMimeType(ResponseFormatToMimeType(format));
@@ -401,8 +418,6 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
{ {
MediaRetrievalResult res {itStreamHandler->second(requestContext, request.continuation())}; MediaRetrievalResult res {itStreamHandler->second(requestContext, request.continuation())};
lock.unlock();
if (!res.mimeType.empty()) if (!res.mimeType.empty())
response.setMimeType(res.mimeType); response.setMimeType(res.mimeType);
if (!res.data.empty()) if (!res.data.empty())
@@ -488,7 +503,7 @@ getTrackPath(const Database::Track::pointer& track)
static static
Response::Node Response::Node
trackToResponseNode(const Database::User::pointer& user, const Database::Track::pointer& track) trackToResponseNode(const Database::Track::pointer& track, Database::Session& dbSession, const Database::User::pointer& user)
{ {
Response::Node trackResponse; Response::Node trackResponse;
@@ -532,7 +547,7 @@ trackToResponseNode(const Database::User::pointer& user, const Database::Track::
trackResponse.setAttribute("starred", reportedStarredDate); trackResponse.setAttribute("starred", reportedStarredDate);
// Report the first GENRE for this track // Report the first GENRE for this track
Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(*track.session(), genreClusterName)}; Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(dbSession, genreClusterName)};
if (clusterType) if (clusterType)
{ {
auto clusters {track->getClusterGroups({clusterType}, 1)}; auto clusters {track->getClusterGroups({clusterType}, 1)};
@@ -545,7 +560,7 @@ trackToResponseNode(const Database::User::pointer& user, const Database::Track::
static static
Response::Node Response::Node
releaseToResponseNode(const Database::User::pointer& user, const Database::Release::pointer& release, bool id3) releaseToResponseNode(const Database::Release::pointer& release, Database::Session& dbSession, const Database::User::pointer& user, bool id3)
{ {
Response::Node albumNode; Response::Node albumNode;
@@ -598,7 +613,7 @@ releaseToResponseNode(const Database::User::pointer& user, const Database::Relea
if (id3) if (id3)
{ {
// Report the first GENRE for this track // Report the first GENRE for this track
Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(*release.session(), genreClusterName)}; Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(dbSession, genreClusterName)};
if (clusterType) if (clusterType)
{ {
auto clusters {release->getClusterGroups({clusterType}, 1)}; auto clusters {release->getClusterGroups({clusterType}, 1)};
@@ -668,16 +683,16 @@ handleCreatePlaylistRequest(RequestContext& context)
if (!name && !id) if (!name && !id)
throw Error {Error::Code::RequiredParameterMissing}; throw Error {Error::Code::RequiredParameterMissing};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
Database::TrackList::pointer tracklist; Database::TrackList::pointer tracklist;
if (id) if (id)
{ {
tracklist = Database::TrackList::getById(context.db.getSession(), id->value); tracklist = Database::TrackList::getById(context.dbSession, id->value);
if (!tracklist if (!tracklist
|| tracklist->getUser() != user || tracklist->getUser() != user
|| tracklist->getType() != Database::TrackList::Type::Playlist) || tracklist->getType() != Database::TrackList::Type::Playlist)
@@ -690,16 +705,16 @@ handleCreatePlaylistRequest(RequestContext& context)
} }
else else
{ {
tracklist = Database::TrackList::create(context.db.getSession(), *name, Database::TrackList::Type::Playlist, false, user); tracklist = Database::TrackList::create(context.dbSession, *name, Database::TrackList::Type::Playlist, false, user);
} }
for (const Id& trackId : trackIds) for (const Id& trackId : trackIds)
{ {
Database::Track::pointer track {Database::Track::getById(context.db.getSession(), trackId.value)}; Database::Track::pointer track {Database::Track::getById(context.dbSession, trackId.value)};
if (!track) if (!track)
continue; continue;
Database::TrackListEntry::create(context.db.getSession(), track, tracklist ); Database::TrackListEntry::create(context.dbSession, track, tracklist );
} }
return Response::createOkResponse(); return Response::createOkResponse();
@@ -713,13 +728,13 @@ handleDeletePlaylistRequest(RequestContext& context)
if (id.type != Id::Type::Playlist) if (id.type != Id::Type::Playlist)
throw Error {Error::CustomType::BadId}; throw Error {Error::CustomType::BadId};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
Database::TrackList::pointer tracklist {Database::TrackList::getById(context.db.getSession(), id.value)}; Database::TrackList::pointer tracklist {Database::TrackList::getById(context.dbSession, id.value)};
if (!tracklist if (!tracklist
|| tracklist->getUser() != user || tracklist->getUser() != user
|| tracklist->getType() != Database::TrackList::Type::Playlist) || tracklist->getType() != Database::TrackList::Type::Playlist)
@@ -752,29 +767,29 @@ handleGetRandomSongsRequest(RequestContext& context)
std::size_t size {getParameterAs<std::size_t>(context.parameters, "size").get_value_or(50)}; std::size_t size {getParameterAs<std::size_t>(context.parameters, "size").get_value_or(50)};
size = std::min(size, std::size_t {500}); size = std::min(size, std::size_t {500});
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
auto tracks {Database::Track::getAllRandom(context.db.getSession(), size)}; auto tracks {Database::Track::getAllRandom(context.dbSession, size)};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse()};
Response::Node& randomSongsNode {response.createNode("randomSongs")}; Response::Node& randomSongsNode {response.createNode("randomSongs")};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
randomSongsNode.addArrayChild("song", trackToResponseNode(user, track)); randomSongsNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
return response; return response;
} }
static static
std::vector<Database::Release::pointer> getRandomAlbums(Wt::Dbo::Session& session, std::size_t offset, std::size_t size) std::vector<Database::Release::pointer> getRandomAlbums(Database::Session& dbSession, std::size_t offset, std::size_t size)
{ {
std::vector<Database::Release::pointer> res; std::vector<Database::Release::pointer> res;
std::size_t nbReleases {Database::Release::getCount(session)}; std::size_t nbReleases {Database::Release::getCount(dbSession)};
if (offset > nbReleases) if (offset > nbReleases)
return res; return res;
@@ -793,7 +808,7 @@ std::vector<Database::Release::pointer> getRandomAlbums(Wt::Dbo::Session& sessio
std::for_each(std::next(std::begin(indexes), offset), std::next(std::begin(indexes), offset + size), std::for_each(std::next(std::begin(indexes), offset), std::next(std::begin(indexes), offset + size),
[&](std::size_t offset) [&](std::size_t offset)
{ {
auto release {Database::Release::getAll(session, offset, 1)}; auto release {Database::Release::getAll(dbSession, offset, 1)};
if (!release.empty()) if (!release.empty())
res.emplace_back(release.front()); res.emplace_back(release.front());
}); });
@@ -814,38 +829,38 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
std::vector<Database::Release::pointer> releases; std::vector<Database::Release::pointer> releases;
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
if (type == "random") if (type == "random")
{ {
releases = getRandomAlbums(context.db.getSession(), offset, size); releases = getRandomAlbums(context.dbSession, offset, size);
} }
else if (type == "newest") else if (type == "newest")
{ {
auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-6)}; auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-6)};
releases = Database::Release::getLastAdded(context.db.getSession(), after, offset, size); releases = Database::Release::getLastAdded(context.dbSession, after, offset, size);
} }
else if (type == "alphabeticalByName") else if (type == "alphabeticalByName")
{ {
releases = Database::Release::getAll(context.db.getSession(), offset, size); releases = Database::Release::getAll(context.dbSession, offset, size);
} }
else if (type == "byGenre") else if (type == "byGenre")
{ {
// Mandatory param // Mandatory param
std::string genre {getMandatoryParameterAs<std::string>(context.parameters, "genre")}; std::string genre {getMandatoryParameterAs<std::string>(context.parameters, "genre")};
Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(context.db.getSession(), genreClusterName)}; Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(context.dbSession, genreClusterName)};
if (clusterType) if (clusterType)
{ {
Database::Cluster::pointer cluster {clusterType->getCluster(genre)}; Database::Cluster::pointer cluster {clusterType->getCluster(genre)};
if (cluster) if (cluster)
{ {
bool more; bool more;
releases = Database::Release::getByFilter(context.db.getSession(), {cluster.id()}, {}, offset, size, more); releases = Database::Release::getByFilter(context.dbSession, {cluster.id()}, {}, offset, size, more);
} }
} }
} }
@@ -856,7 +871,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
Response::Node& albumListNode {response.createNode(id3 ? "albumList2" : "albumList")}; Response::Node& albumListNode {response.createNode(id3 ? "albumList2" : "albumList")};
for (const Database::Release::pointer& release : releases) for (const Database::Release::pointer& release : releases)
albumListNode.addArrayChild("album", releaseToResponseNode(user, release, id3)); albumListNode.addArrayChild("album", releaseToResponseNode(release, context.dbSession, user, id3));
return response; return response;
} }
@@ -882,22 +897,22 @@ handleGetAlbumRequest(RequestContext& context)
if (id.type != Id::Type::Release) if (id.type != Id::Type::Release)
throw Error {Error::CustomType::BadId}; throw Error {Error::CustomType::BadId};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::Release::pointer release {Database::Release::getById(context.db.getSession(), id.value)}; Database::Release::pointer release {Database::Release::getById(context.dbSession, id.value)};
if (!release) if (!release)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse()};
Response::Node releaseNode {releaseToResponseNode(user, release, true /* id3 */)}; Response::Node releaseNode {releaseToResponseNode(release, context.dbSession, user, true /* id3 */)};
auto tracks {release->getTracks()}; auto tracks {release->getTracks()};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
releaseNode.addArrayChild("song", trackToResponseNode(user, track)); releaseNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
response.addNode("album", std::move(releaseNode)); response.addNode("album", std::move(releaseNode));
@@ -913,10 +928,10 @@ handleGetArtistRequest(RequestContext& context)
if (id.type != Id::Type::Artist) if (id.type != Id::Type::Artist)
throw Error {Error::CustomType::BadId}; throw Error {Error::CustomType::BadId};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::Artist::pointer artist {Database::Artist::getById(context.db.getSession(), id.value)}; Database::Artist::pointer artist {Database::Artist::getById(context.dbSession, id.value)};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!artist || !user) if (!artist || !user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -926,7 +941,7 @@ handleGetArtistRequest(RequestContext& context)
auto releases {artist->getReleases()}; auto releases {artist->getReleases()};
for (const Database::Release::pointer& release : releases) for (const Database::Release::pointer& release : releases)
artistNode.addArrayChild("album", releaseToResponseNode(user, release, true /* id3 */)); artistNode.addArrayChild("album", releaseToResponseNode(release, context.dbSession, user, true /* id3 */));
response.addNode("artist", std::move(artistNode)); response.addNode("artist", std::move(artistNode));
@@ -945,10 +960,10 @@ Response handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
// Optional params // Optional params
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").get_value_or(10)}; std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").get_value_or(10)};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::Artist::pointer artist {Database::Artist::getById(context.db.getSession(), id.value)}; Database::Artist::pointer artist {Database::Artist::getById(context.dbSession, id.value)};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!artist || !user) if (!artist || !user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -959,10 +974,10 @@ Response handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
if (!artist->getMBID().empty()) if (!artist->getMBID().empty())
artistInfoNode.createChild("musicBrainzId").setValue(artist->getMBID()); artistInfoNode.createChild("musicBrainzId").setValue(artist->getMBID());
auto similarArtistsId {getService<Similarity::Searcher>()->getSimilarArtists(context.db.getSession(), artist.id(), count)}; auto similarArtistsId {getService<Similarity::Searcher>()->getSimilarArtists(context.dbSession, artist.id(), count)};
for ( const auto& similarArtistId : similarArtistsId ) for ( const auto& similarArtistId : similarArtistsId )
{ {
Database::Artist::pointer similarArtist {Database::Artist::getById(context.db.getSession(), similarArtistId)}; Database::Artist::pointer similarArtist {Database::Artist::getById(context.dbSession, similarArtistId)};
if (similarArtist) if (similarArtist)
artistInfoNode.addArrayChild("similarArtist", artistToResponseNode(user, similarArtist, id3)); artistInfoNode.addArrayChild("similarArtist", artistToResponseNode(user, similarArtist, id3));
@@ -989,13 +1004,13 @@ handleGetArtistsRequest(RequestContext& context)
Response::Node& indexNode {artistsNode.createArrayChild("index")}; Response::Node& indexNode {artistsNode.createArrayChild("index")};
indexNode.setAttribute("name", "?"); indexNode.setAttribute("name", "?");
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
auto artists {Database::Artist::getAll(context.db.getSession())}; auto artists {Database::Artist::getAll(context.dbSession)};
for (const Database::Artist::pointer& artist : artists) for (const Database::Artist::pointer& artist : artists)
indexNode.addArrayChild("artist", artistToResponseNode(user, artist, true /* id3 */)); indexNode.addArrayChild("artist", artistToResponseNode(user, artist, true /* id3 */));
@@ -1014,9 +1029,9 @@ handleGetMusicDirectoryRequest(RequestContext& context)
directoryNode.setAttribute("id", IdToString(id)); directoryNode.setAttribute("id", IdToString(id));
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1026,7 +1041,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
{ {
directoryNode.setAttribute("name", "Music"); directoryNode.setAttribute("name", "Music");
auto artists {Database::Artist::getAll(context.db.getSession())}; auto artists {Database::Artist::getAll(context.dbSession)};
for (const Database::Artist::pointer& artist : artists) for (const Database::Artist::pointer& artist : artists)
directoryNode.addArrayChild("child", artistToResponseNode(user, artist, false /* no id3 */)); directoryNode.addArrayChild("child", artistToResponseNode(user, artist, false /* no id3 */));
@@ -1035,7 +1050,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
case Id::Type::Artist: case Id::Type::Artist:
{ {
auto artist {Database::Artist::getById(context.db.getSession(), id.value)}; auto artist {Database::Artist::getById(context.dbSession, id.value)};
if (!artist) if (!artist)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1043,14 +1058,14 @@ handleGetMusicDirectoryRequest(RequestContext& context)
auto releases {artist->getReleases()}; auto releases {artist->getReleases()};
for (const Database::Release::pointer& release : releases) for (const Database::Release::pointer& release : releases)
directoryNode.addArrayChild("child", releaseToResponseNode(user, release, false /* no id3 */)); directoryNode.addArrayChild("child", releaseToResponseNode(release, context.dbSession, user, false /* no id3 */));
break; break;
} }
case Id::Type::Release: case Id::Type::Release:
{ {
auto release {Database::Release::getById(context.db.getSession(), id.value)}; auto release {Database::Release::getById(context.dbSession, id.value)};
if (!release) if (!release)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1058,7 +1073,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
auto tracks {release->getTracks()}; auto tracks {release->getTracks()};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
directoryNode.addArrayChild("child", trackToResponseNode(user, track)); directoryNode.addArrayChild("child", trackToResponseNode(track, context.dbSession, user));
break; break;
} }
@@ -1090,9 +1105,9 @@ handleGetGenresRequest(RequestContext& context)
Response::Node& genresNode {response.createNode("genres")}; Response::Node& genresNode {response.createNode("genres")};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
auto clusterType {Database::ClusterType::getByName(context.db.getSession(), genreClusterName)}; auto clusterType {Database::ClusterType::getByName(context.dbSession, genreClusterName)};
if (clusterType) if (clusterType)
{ {
auto clusters {clusterType->getClusters()}; auto clusters {clusterType->getClusters()};
@@ -1113,13 +1128,13 @@ handleGetIndexesRequest(RequestContext& context)
Response::Node& indexNode {artistsNode.createArrayChild("index")}; Response::Node& indexNode {artistsNode.createArrayChild("index")};
indexNode.setAttribute("name", "?"); indexNode.setAttribute("name", "?");
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
auto artists {Database::Artist::getAll(context.db.getSession())}; auto artists {Database::Artist::getAll(context.dbSession)};
for (const Database::Artist::pointer& artist : artists) for (const Database::Artist::pointer& artist : artists)
indexNode.addArrayChild("artist", artistToResponseNode(user, artist, false /* no id3 */)); indexNode.addArrayChild("artist", artistToResponseNode(user, artist, false /* no id3 */));
@@ -1137,10 +1152,10 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
// Optional params // Optional params
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").get_value_or(50)}; std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").get_value_or(50)};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::Artist::pointer artist {Database::Artist::getById(context.db.getSession(), id.value)}; Database::Artist::pointer artist {Database::Artist::getById(context.dbSession, id.value)};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user || !artist) if (!user || !artist)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1148,10 +1163,10 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
// "Returns a random collection of songs from the given artist and similar artists" // "Returns a random collection of songs from the given artist and similar artists"
auto tracks {artist->getRandomTracks(count / 2)}; auto tracks {artist->getRandomTracks(count / 2)};
auto similarArtistsId {getService<Similarity::Searcher>()->getSimilarArtists(context.db.getSession(), artist.id(), 5)}; auto similarArtistsId {getService<Similarity::Searcher>()->getSimilarArtists(context.dbSession, artist.id(), 5)};
for ( const auto& similarArtistId : similarArtistsId ) for ( const auto& similarArtistId : similarArtistsId )
{ {
Database::Artist::pointer similarArtist {Database::Artist::getById(context.db.getSession(), similarArtistId)}; Database::Artist::pointer similarArtist {Database::Artist::getById(context.dbSession, similarArtistId)};
if (!similarArtist) if (!similarArtist)
continue; continue;
@@ -1169,7 +1184,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse()};
Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")}; Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
similarSongsNode.addArrayChild("song", trackToResponseNode(user, track)); similarSongsNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
return response; return response;
} }
@@ -1191,9 +1206,9 @@ static
Response Response
handleGetStarredRequestCommon(RequestContext& context, bool id3) handleGetStarredRequestCommon(RequestContext& context, bool id3)
{ {
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1209,13 +1224,13 @@ handleGetStarredRequestCommon(RequestContext& context, bool id3)
{ {
auto releases {user->getStarredReleases()}; auto releases {user->getStarredReleases()};
for (const Database::Release::pointer& release : releases) for (const Database::Release::pointer& release : releases)
starredNode.addArrayChild("album", releaseToResponseNode(user, release, id3)); starredNode.addArrayChild("album", releaseToResponseNode(release, context.dbSession, user, id3));
} }
{ {
auto tracks {user->getStarredTracks()}; auto tracks {user->getStarredTracks()};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
starredNode.addArrayChild("song", trackToResponseNode(user, track)); starredNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
} }
return response; return response;
@@ -1235,7 +1250,7 @@ handleGetStarred2Request(RequestContext& context)
} }
Response::Node Response::Node
tracklistToResponseNode(const Database::TrackList::pointer& tracklist, Database::Handler& db) tracklistToResponseNode(const Database::TrackList::pointer& tracklist, Database::Session& dbSession)
{ {
Response::Node playlistNode; Response::Node playlistNode;
@@ -1245,15 +1260,7 @@ tracklistToResponseNode(const Database::TrackList::pointer& tracklist, Database:
playlistNode.setAttribute("duration", std::to_string(std::chrono::duration_cast<std::chrono::seconds>(tracklist->getDuration()).count())); playlistNode.setAttribute("duration", std::to_string(std::chrono::duration_cast<std::chrono::seconds>(tracklist->getDuration()).count()));
playlistNode.setAttribute("public", tracklist->isPublic() ? "true" : "false"); playlistNode.setAttribute("public", tracklist->isPublic() ? "true" : "false");
playlistNode.setAttribute("created", ""); playlistNode.setAttribute("created", "");
{ playlistNode.setAttribute("owner", dbSession.getUserLoginName(tracklist->getUser()));
std::string userId {std::to_string(tracklist->getUser().id())};
Wt::Auth::User authUser { db.getUserDatabase().findWithId(userId)};
if (!authUser.isValid())
throw Error {Error::CustomType::InternalError};
playlistNode.setAttribute("owner", authUser.identity(Wt::Auth::Identity::LoginName).toUTF8());
}
return playlistNode; return playlistNode;
} }
@@ -1266,19 +1273,19 @@ handleGetPlaylistRequest(RequestContext& context)
if (id.type != Id::Type::Playlist) if (id.type != Id::Type::Playlist)
throw Error {Error::CustomType::BadId}; throw Error {Error::CustomType::BadId};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
Database::TrackList::pointer tracklist {Database::TrackList::getById(context.db.getSession(), id.value)}; Database::TrackList::pointer tracklist {Database::TrackList::getById(context.dbSession, id.value)};
if (!user || !tracklist) if (!user || !tracklist)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse()};
Response::Node playlistNode {tracklistToResponseNode(tracklist, context.db)}; Response::Node playlistNode {tracklistToResponseNode(tracklist, context.dbSession)};
auto entries {tracklist->getEntries()}; auto entries {tracklist->getEntries()};
for (const Database::TrackListEntry::pointer& entry : entries) for (const Database::TrackListEntry::pointer& entry : entries)
playlistNode.addArrayChild("entry", trackToResponseNode(user, entry->getTrack())); playlistNode.addArrayChild("entry", trackToResponseNode(entry->getTrack(), context.dbSession, user));
response.addNode("playlist", playlistNode ); response.addNode("playlist", playlistNode );
@@ -1288,18 +1295,18 @@ handleGetPlaylistRequest(RequestContext& context)
Response Response
handleGetPlaylistsRequest(RequestContext& context) handleGetPlaylistsRequest(RequestContext& context)
{ {
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse()};
Response::Node& playlistsNode {response.createNode("playlists")}; Response::Node& playlistsNode {response.createNode("playlists")};
auto tracklists {Database::TrackList::getAll(context.db.getSession(), user, Database::TrackList::Type::Playlist)}; auto tracklists {Database::TrackList::getAll(context.dbSession, user, Database::TrackList::Type::Playlist)};
for (const Database::TrackList::pointer& tracklist : tracklists) for (const Database::TrackList::pointer& tracklist : tracklists)
playlistsNode.addArrayChild("playlist", tracklistToResponseNode(tracklist, context.db)); playlistsNode.addArrayChild("playlist", tracklistToResponseNode(tracklist, context.dbSession));
return response; return response;
} }
@@ -1316,9 +1323,9 @@ handleGetSongsByGenreRequest(RequestContext& context)
std::size_t offset {getParameterAs<std::size_t>(context.parameters, "offset").get_value_or(0)}; std::size_t offset {getParameterAs<std::size_t>(context.parameters, "offset").get_value_or(0)};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
auto clusterType {Database::ClusterType::getByName(context.db.getSession(), genreClusterName)}; auto clusterType {Database::ClusterType::getByName(context.dbSession, genreClusterName)};
if (!clusterType) if (!clusterType)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1326,7 +1333,7 @@ handleGetSongsByGenreRequest(RequestContext& context)
if (!cluster) if (!cluster)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1334,9 +1341,9 @@ handleGetSongsByGenreRequest(RequestContext& context)
Response::Node& songsByGenreNode {response.createNode("songsByGenre")}; Response::Node& songsByGenreNode {response.createNode("songsByGenre")};
bool more; bool more;
auto tracks {Database::Track::getByFilter(context.db.getSession(), {cluster.id()}, {}, offset, size, more)}; auto tracks {Database::Track::getByFilter(context.dbSession, {cluster.id()}, {}, offset, size, more)};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
songsByGenreNode.addArrayChild("song", trackToResponseNode(user, track)); songsByGenreNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
return response; return response;
} }
@@ -1358,9 +1365,9 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
std::size_t songCount {getParameterAs<std::size_t>(context.parameters, "songCount").get_value_or(20)}; std::size_t songCount {getParameterAs<std::size_t>(context.parameters, "songCount").get_value_or(20)};
std::size_t songOffset {getParameterAs<std::size_t>(context.parameters, "songOffset").get_value_or(0)}; std::size_t songOffset {getParameterAs<std::size_t>(context.parameters, "songOffset").get_value_or(0)};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1369,21 +1376,21 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
bool more; bool more;
{ {
auto artists {Database::Artist::getByFilter(context.db.getSession(), {}, keywords, artistOffset, artistCount, more)}; auto artists {Database::Artist::getByFilter(context.dbSession, {}, keywords, artistOffset, artistCount, more)};
for (const Database::Artist::pointer& artist : artists) for (const Database::Artist::pointer& artist : artists)
searchResult2Node.addArrayChild("artist", artistToResponseNode(user, artist, id3)); searchResult2Node.addArrayChild("artist", artistToResponseNode(user, artist, id3));
} }
{ {
auto releases {Database::Release::getByFilter(context.db.getSession(), {}, keywords, albumOffset, albumCount, more)}; auto releases {Database::Release::getByFilter(context.dbSession, {}, keywords, albumOffset, albumCount, more)};
for (const Database::Release::pointer& release : releases) for (const Database::Release::pointer& release : releases)
searchResult2Node.addArrayChild("album", releaseToResponseNode(user, release, id3)); searchResult2Node.addArrayChild("album", releaseToResponseNode(release, context.dbSession, user, id3));
} }
{ {
auto tracks {Database::Track::getByFilter(context.db.getSession(), {}, keywords, songOffset, songCount, more)}; auto tracks {Database::Track::getByFilter(context.dbSession, {}, keywords, songOffset, songCount, more)};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
searchResult2Node.addArrayChild("song", trackToResponseNode(user, track)); searchResult2Node.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
} }
return response; return response;
@@ -1439,15 +1446,15 @@ handleStarRequest(RequestContext& context)
{ {
StarParameters params {getStarParameters(context.parameters)}; StarParameters params {getStarParameters(context.parameters)};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
for (const Id& id : params.artistIds) for (const Id& id : params.artistIds)
{ {
Database::Artist::pointer artist {Database::Artist::getById(context.db.getSession(), id.value)}; Database::Artist::pointer artist {Database::Artist::getById(context.dbSession, id.value)};
if (!artist) if (!artist)
continue; continue;
@@ -1456,7 +1463,7 @@ handleStarRequest(RequestContext& context)
for (const Id& id : params.releaseIds) for (const Id& id : params.releaseIds)
{ {
Database::Release::pointer release {Database::Release::getById(context.db.getSession(), id.value)}; Database::Release::pointer release {Database::Release::getById(context.dbSession, id.value)};
if (!release) if (!release)
continue; continue;
@@ -1465,7 +1472,7 @@ handleStarRequest(RequestContext& context)
for (const Id& id : params.trackIds) for (const Id& id : params.trackIds)
{ {
Database::Track::pointer track {Database::Track::getById(context.db.getSession(), id.value)}; Database::Track::pointer track {Database::Track::getById(context.dbSession, id.value)};
if (!track) if (!track)
continue; continue;
@@ -1492,15 +1499,15 @@ handleUnstarRequest(RequestContext& context)
{ {
StarParameters params {getStarParameters(context.parameters)}; StarParameters params {getStarParameters(context.parameters)};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
for (const Id& id : params.artistIds) for (const Id& id : params.artistIds)
{ {
Database::Artist::pointer artist {Database::Artist::getById(context.db.getSession(), id.value)}; Database::Artist::pointer artist {Database::Artist::getById(context.dbSession, id.value)};
if (!artist) if (!artist)
continue; continue;
@@ -1509,7 +1516,7 @@ handleUnstarRequest(RequestContext& context)
for (const Id& id : params.releaseIds) for (const Id& id : params.releaseIds)
{ {
Database::Release::pointer release {Database::Release::getById(context.db.getSession(), id.value)}; Database::Release::pointer release {Database::Release::getById(context.dbSession, id.value)};
if (!release) if (!release)
continue; continue;
@@ -1518,7 +1525,7 @@ handleUnstarRequest(RequestContext& context)
for (const Id& id : params.trackIds) for (const Id& id : params.trackIds)
{ {
Database::Track::pointer track {Database::Track::getById(context.db.getSession(), id.value)}; Database::Track::pointer track {Database::Track::getById(context.dbSession, id.value)};
if (!track) if (!track)
continue; continue;
@@ -1547,13 +1554,13 @@ handleUpdatePlaylistRequest(RequestContext& context)
std::vector<std::size_t> trackPositionsToRemove {getMultiParametersAs<std::size_t>(context.parameters, "songIndexToRemove")}; std::vector<std::size_t> trackPositionsToRemove {getMultiParametersAs<std::size_t>(context.parameters, "songIndexToRemove")};
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
Database::TrackList::pointer tracklist {Database::TrackList::getById(context.db.getSession(), id.value)}; Database::TrackList::pointer tracklist {Database::TrackList::getById(context.dbSession, id.value)};
if (!tracklist if (!tracklist
|| tracklist->getUser() != user || tracklist->getUser() != user
|| tracklist->getType() != Database::TrackList::Type::Playlist) || tracklist->getType() != Database::TrackList::Type::Playlist)
@@ -1582,11 +1589,11 @@ handleUpdatePlaylistRequest(RequestContext& context)
// Add tracks // Add tracks
for (const Id& trackIdToAdd : trackIdsToAdd) for (const Id& trackIdToAdd : trackIdsToAdd)
{ {
Database::Track::pointer track {Database::Track::getById(context.db.getSession(), trackIdToAdd.value)}; Database::Track::pointer track {Database::Track::getById(context.dbSession, trackIdToAdd.value)};
if (!track) if (!track)
continue; continue;
Database::TrackListEntry::create(context.db.getSession(), track, tracklist ); Database::TrackListEntry::create(context.dbSession, track, tracklist );
} }
return Response::createOkResponse(); return Response::createOkResponse();
@@ -1604,9 +1611,9 @@ createTranscoder(RequestContext& context)
boost::filesystem::path trackPath; boost::filesystem::path trackPath;
{ {
Wt::Dbo::Transaction transaction {context.db.getSession()}; auto transaction {context.dbSession.createSharedTransaction()};
Database::User::pointer user {context.db.getUser(context.userName)}; Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user) if (!user)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1616,7 +1623,7 @@ createTranscoder(RequestContext& context)
*maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioTranscodeBitrate() / 1000); *maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioTranscodeBitrate() / 1000);
auto track {Database::Track::getById(context.db.getSession(), id.value)}; auto track {Database::Track::getById(context.dbSession, id.value)};
if (!track) if (!track)
throw Error {Error::Code::RequestedDataNotFound}; throw Error {Error::Code::RequestedDataNotFound};
@@ -1685,10 +1692,10 @@ handleGetCoverArt(RequestContext& context, Wt::Http::ResponseContinuation*)
switch (id.type) switch (id.type)
{ {
case Id::Type::Track: case Id::Type::Track:
res.data = getService<CoverArt::Grabber>()->getFromTrack(context.db.getSession(), id.value, Image::Format::JPEG, size); res.data = getService<CoverArt::Grabber>()->getFromTrack(context.dbSession, id.value, Image::Format::JPEG, size);
break; break;
case Id::Type::Release: case Id::Type::Release:
res.data = getService<CoverArt::Grabber>()->getFromRelease(context.db.getSession(), id.value, Image::Format::JPEG, size); res.data = getService<CoverArt::Grabber>()->getFromRelease(context.dbSession, id.value, Image::Format::JPEG, size);
break; break;
default: default:
throw Error {Error::CustomType::BadId}; throw Error {Error::CustomType::BadId};
+4 -4
View File
@@ -23,7 +23,7 @@
#include <Wt/WResource.h> #include <Wt/WResource.h>
#include <Wt/Http/Response.h> #include <Wt/Http/Response.h>
#include "database/DatabaseHandler.hpp" #include "database/Database.hpp"
namespace API::Subsonic namespace API::Subsonic
{ {
@@ -31,15 +31,15 @@ namespace API::Subsonic
class SubsonicResource final : public Wt::WResource class SubsonicResource final : public Wt::WResource
{ {
public: public:
SubsonicResource(Wt::Dbo::SqlConnectionPool& connectionPool); SubsonicResource(Database::Database& db);
~SubsonicResource();
static std::vector<std::string> getPaths(); static std::vector<std::string> getPaths();
private: private:
void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override; void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override;
Database::Handler _db; Database::Database& _db;
}; };
} // namespace } // namespace
+26 -27
View File
@@ -24,6 +24,7 @@
#include "av/AvInfo.hpp" #include "av/AvInfo.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
@@ -169,30 +170,31 @@ Grabber::getFromTrack(const boost::filesystem::path& p) const
} }
Image::Image Image::Image
Grabber::getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::size_t size) Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, std::size_t size)
{ {
using namespace Database; using namespace Database;
boost::optional<Image::Image> cover; boost::optional<Image::Image> cover;
{ bool hasCover {};
Wt::Dbo::Transaction transaction(session); boost::filesystem::path trackPath;
Track::pointer track = Track::getById(session, trackId); {
auto transaction {dbSession.createSharedTransaction()};
Track::pointer track = Track::getById(dbSession, trackId);
if (track) if (track)
{ {
bool hasCover = track->hasCover(); hasCover = track->hasCover();
boost::filesystem::path trackPath = track->getPath(); trackPath = track->getPath();
}
transaction.commit(); }
if (hasCover) if (hasCover)
cover = getFromTrack(trackPath); cover = getFromTrack(trackPath);
if (!cover) if (!cover)
cover = getFromDirectory(trackPath.parent_path()); cover = getFromDirectory(trackPath.parent_path());
}
}
if (!cover) if (!cover)
cover = getDefaultCover(size); cover = getDefaultCover(size);
@@ -204,28 +206,25 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::
Image::Image Image::Image
Grabber::getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t size) Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId, std::size_t size)
{ {
using namespace Database;
boost::optional<Image::Image> cover; boost::optional<Image::Image> cover;
boost::optional<Database::IdType> trackId;
{ {
Wt::Dbo::Transaction transaction(session); auto transaction {session.createSharedTransaction()};
auto release = Release::getById(session, releaseId); auto release {Database::Release::getById(session, releaseId)};
if (release) if (release)
{ {
auto tracks = release->getTracks(); auto tracks {release->getTracks()};
if (!tracks.empty()) if (!tracks.empty())
{ trackId = tracks.front().id();
auto trackId = tracks.front().id(); }
transaction.commit(); }
return getFromTrack(session, trackId, size); if (trackId)
} return getFromTrack(session, *trackId, size);
}
}
if (!cover) if (!cover)
cover = getDefaultCover(size); cover = getDefaultCover(size);
@@ -236,17 +235,17 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, s
} }
std::vector<uint8_t> std::vector<uint8_t>
Grabber::getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, Image::Format format, std::size_t size) Grabber::getFromTrack(Database::Session& session, Database::IdType trackId, Image::Format format, std::size_t size)
{ {
Image::Image cover = getFromTrack(session, trackId, size); const Image::Image cover {getFromTrack(session, trackId, size)};
return cover.save(Image::Format::JPEG); return cover.save(Image::Format::JPEG);
} }
std::vector<uint8_t> std::vector<uint8_t>
Grabber::getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, Image::Format format, std::size_t size) Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId, Image::Format format, std::size_t size)
{ {
Image::Image cover = getFromRelease(session, releaseId, size); const Image::Image cover {getFromRelease(session, releaseId, size)};
return cover.save(Image::Format::JPEG); return cover.save(Image::Format::JPEG);
} }
+10 -4
View File
@@ -23,10 +23,16 @@
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include <boost/optional.hpp>
#include "database/Types.hpp" #include "database/Types.hpp"
#include "image/Image.hpp" #include "image/Image.hpp"
namespace Database {
class Session;
}
namespace CoverArt { namespace CoverArt {
class Grabber class Grabber
@@ -40,13 +46,13 @@ class Grabber
void setDefaultCover(boost::filesystem::path defaultCoverPath); void setDefaultCover(boost::filesystem::path defaultCoverPath);
std::vector<uint8_t> getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, Image::Format format, std::size_t size); std::vector<uint8_t> getFromTrack(Database::Session& dbSession, Database::IdType trackId, Image::Format format, std::size_t size);
std::vector<uint8_t> getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, Image::Format format, std::size_t size); std::vector<uint8_t> getFromRelease(Database::Session& dbSession, Database::IdType releaseId, Image::Format format, std::size_t size);
private: private:
Image::Image getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::size_t size); Image::Image getFromTrack(Database::Session& dbSession, Database::IdType trackId, std::size_t size);
Image::Image getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t size); Image::Image getFromRelease(Database::Session& dbSession, Database::IdType releaseId, std::size_t size);
boost::optional<Image::Image> getFromTrack(const boost::filesystem::path& path) const; boost::optional<Image::Image> getFromTrack(const boost::filesystem::path& path) const;
std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath) const; std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath) const;
+35 -18
View File
@@ -25,6 +25,7 @@
#include "Cluster.hpp" #include "Cluster.hpp"
#include "Release.hpp" #include "Release.hpp"
#include "SqlQuery.hpp" #include "SqlQuery.hpp"
#include "Session.hpp"
#include "Track.hpp" #include "Track.hpp"
#include "User.hpp" #include "User.hpp"
@@ -40,34 +41,44 @@ _MBID {MBID}
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
Artist::getByName(Wt::Dbo::Session& session, const std::string& name) Artist::getByName(Session& session, const std::string& name)
{ {
Wt::Dbo::collection<Artist::pointer> res = session.find<Artist>().where("name = ?").bind( std::string{name, 0, _maxNameLength} ); session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> res = session.getDboSession().find<Artist>().where("name = ?").bind( std::string{name, 0, _maxNameLength} );
return std::vector<Artist::pointer>(res.begin(), res.end()); return std::vector<Artist::pointer>(res.begin(), res.end());
} }
Artist::pointer Artist::pointer
Artist::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) Artist::getByMBID(Session& session, const std::string& mbid)
{ {
return session.find<Artist>().where("mbid = ?").bind(mbid); session.checkSharedLocked();
return session.getDboSession().find<Artist>().where("mbid = ?").bind(mbid);
} }
Artist::pointer Artist::pointer
Artist::getById(Wt::Dbo::Session& session, IdType id) Artist::getById(Session& session, IdType id)
{ {
return session.find<Artist>().where("id = ?").bind(id); session.checkSharedLocked();
return session.getDboSession().find<Artist>().where("id = ?").bind(id);
} }
Artist::pointer Artist::pointer
Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID) Artist::create(Session& session, const std::string& name, const std::string& MBID)
{ {
return session.add(std::make_unique<Artist>(name, MBID)); session.checkUniqueLocked();
Artist::pointer res {session.getDboSession().add(std::make_unique<Artist>(name, MBID))};
session.getDboSession().flush();
return res;
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
Artist::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset, boost::optional<std::size_t> size) Artist::getAll(Session& session, boost::optional<std::size_t> offset, boost::optional<std::size_t> size)
{ {
Wt::Dbo::collection<pointer> res = session.find<Artist>() session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Artist>()
.offset(offset ? static_cast<int>(*offset) : -1) .offset(offset ? static_cast<int>(*offset) : -1)
.limit(size ? static_cast<int>(*size) : -1) .limit(size ? static_cast<int>(*size) : -1)
.orderBy("sort_name COLLATE NOCASE"); .orderBy("sort_name COLLATE NOCASE");
@@ -76,19 +87,22 @@ Artist::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset, b
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
Artist::getAllOrphans(Wt::Dbo::Session& session) Artist::getAllOrphans(Session& session)
{ {
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res {session.query<Wt::Dbo::ptr<Artist>>("SELECT DISTINCT a FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id WHERE t.id = t_a_l.track_id)")}; session.checkSharedLocked();
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res {session.getDboSession().query<Wt::Dbo::ptr<Artist>>("SELECT DISTINCT a FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id WHERE t.id = t_a_l.track_id)")};
return std::vector<pointer>(res.begin(), res.end()); return std::vector<pointer>(res.begin(), res.end());
} }
static static
Wt::Dbo::Query<Artist::pointer> Wt::Dbo::Query<Artist::pointer>
getQuery(Wt::Dbo::Session& session, getQuery(Session& session,
const std::set<IdType>& clusterIds, const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords) const std::vector<std::string>& keywords)
{ {
session.checkSharedLocked();
WhereClause where; WhereClause where;
std::ostringstream oss; std::ostringstream oss;
@@ -115,7 +129,7 @@ getQuery(Wt::Dbo::Session& session,
oss << " ORDER BY a.sort_name COLLATE NOCASE"; oss << " ORDER BY a.sort_name COLLATE NOCASE";
Wt::Dbo::Query<Artist::pointer> query = session.query<Artist::pointer>( oss.str() ); Wt::Dbo::Query<Artist::pointer> query = session.getDboSession().query<Artist::pointer>( oss.str() );
for (const std::string& bindArg : where.getBindArgs()) for (const std::string& bindArg : where.getBindArgs())
{ {
@@ -126,20 +140,22 @@ getQuery(Wt::Dbo::Session& session,
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
Artist::getByFilter(Wt::Dbo::Session& session, const std::set<IdType>& clusters) Artist::getByFilter(Session& session, const std::set<IdType>& clusters)
{ {
session.checkSharedLocked();
bool more; bool more;
return getByFilter(session, clusters, {}, {}, {}, more); return getByFilter(session, clusters, {}, {}, {}, more);
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
Artist::getByFilter(Wt::Dbo::Session& session, Artist::getByFilter(Session& session,
const std::set<IdType>& clusters, const std::set<IdType>& clusters,
const std::vector<std::string>& keywords, const std::vector<std::string>& keywords,
boost::optional<std::size_t> offset, boost::optional<std::size_t> offset,
boost::optional<std::size_t> size, boost::optional<std::size_t> size,
bool& moreResults) bool& moreResults)
{ {
session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> collection = getQuery(session, clusters, keywords) Wt::Dbo::collection<Artist::pointer> collection = getQuery(session, clusters, keywords)
.limit(size ? static_cast<int>(*size) + 1 : -1) .limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1); .offset(offset ? static_cast<int>(*offset) : -1);
@@ -158,9 +174,10 @@ Artist::getByFilter(Wt::Dbo::Session& session,
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
Artist::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> limit) Artist::getLastAdded(Session& session, Wt::WDateTime after, boost::optional<std::size_t> limit)
{ {
Wt::Dbo::collection<Artist::pointer> res = session.query<Artist::pointer>("SELECT 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") session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> res = session.getDboSession().query<Artist::pointer>("SELECT 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")
.where("t.file_added > ?").bind(after) .where("t.file_added > ?").bind(after)
.groupBy("a.id") .groupBy("a.id")
.orderBy("t.file_added DESC") .orderBy("t.file_added DESC")
+10 -10
View File
@@ -36,6 +36,7 @@ namespace Database
class Cluster; class Cluster;
class ClusterType; class ClusterType;
class Release; class Release;
class Session;
class Track; class Track;
class User; class User;
@@ -49,21 +50,21 @@ class Artist : public Wt::Dbo::Dbo<Artist>
Artist(const std::string& name, const std::string& MBID = ""); Artist(const std::string& name, const std::string& MBID = "");
// Accessors // Accessors
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); static pointer getByMBID(Session& session, const std::string& MBID);
static pointer getById(Wt::Dbo::Session& session, IdType id); static pointer getById(Session& session, IdType id);
static std::vector<pointer> getByName(Wt::Dbo::Session& session, const std::string& name); static std::vector<pointer> getByName(Session& session, const std::string& name);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters); // at least one track that belongs to these clusters const std::set<IdType>& clusters); // at least one track that belongs to these clusters
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // at least one track that belongs to these clusters const std::set<IdType>& clusters, // at least one track that belongs to these clusters
const std::vector<std::string>& keywords, // name must match all of these keywords const std::vector<std::string>& keywords, // name must match all of these keywords
boost::optional<std::size_t> offset, boost::optional<std::size_t> offset,
boost::optional<std::size_t> size, boost::optional<std::size_t> size,
bool& moreExpected); bool& moreExpected);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {}); static std::vector<pointer> getAll(Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session); // No track related static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> size = {}); static std::vector<pointer> getLastAdded(Session& session, Wt::WDateTime after, boost::optional<std::size_t> size = {});
// Accessors // Accessors
const std::string& getName(void) const { return _name; } const std::string& getName(void) const { return _name; }
@@ -84,8 +85,7 @@ class Artist : public Wt::Dbo::Dbo<Artist>
void setSortName(const std::string& sortName); void setSortName(const std::string& sortName);
// Create // Create
static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); static pointer create(Session& session, const std::string& name, const std::string& MBID = "");
template<class Action> template<class Action>
void persist(Action& a) void persist(Action& a)
+46 -18
View File
@@ -22,6 +22,7 @@
#include "Artist.hpp" #include "Artist.hpp"
#include "Release.hpp" #include "Release.hpp"
#include "ScanSettings.hpp" #include "ScanSettings.hpp"
#include "Session.hpp"
#include "SqlQuery.hpp" #include "SqlQuery.hpp"
#include "Track.hpp" #include "Track.hpp"
@@ -38,31 +39,42 @@ Cluster::Cluster(Wt::Dbo::ptr<ClusterType> type, std::string name)
} }
Cluster::pointer Cluster::pointer
Cluster::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<ClusterType> type, std::string name) Cluster::create(Session& session, Wt::Dbo::ptr<ClusterType> type, std::string name)
{ {
return session.add(std::make_unique<Cluster>(type, name)); session.checkUniqueLocked();
Cluster::pointer res {session.getDboSession().add(std::make_unique<Cluster>(type, name))};
session.getDboSession().flush();
return res;
} }
std::vector<Cluster::pointer> std::vector<Cluster::pointer>
Cluster::getAll(Wt::Dbo::Session& session) Cluster::getAll(Session& session)
{ {
Wt::Dbo::collection<Cluster::pointer> res = session.find<Cluster>(); session.checkSharedLocked();
Wt::Dbo::collection<Cluster::pointer> res {session.getDboSession().find<Cluster>()};
return std::vector<Cluster::pointer>(res.begin(), res.end()); return std::vector<Cluster::pointer>(res.begin(), res.end());
} }
std::vector<Cluster::pointer> std::vector<Cluster::pointer>
Cluster::getAllOrphans(Wt::Dbo::Session& session) Cluster::getAllOrphans(Session& session)
{ {
Wt::Dbo::collection<Cluster::pointer> res {session.query<Cluster::pointer>("SELECT DISTINCT c FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_cluster t_c ON t.id = t_c.track_id)")}; 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 t INNER JOIN track_cluster t_c ON t.id = t_c.track_id)")};
return std::vector<Cluster::pointer>(res.begin(), res.end()); return std::vector<Cluster::pointer>(res.begin(), res.end());
} }
Cluster::pointer Cluster::pointer
Cluster::getById(Wt::Dbo::Session& session, IdType id) Cluster::getById(Session& session, IdType id)
{ {
return session.find<Cluster>().where("id = ?").bind(id); session.checkSharedLocked();
return session.getDboSession().find<Cluster>().where("id = ?").bind(id);
} }
void void
@@ -74,6 +86,9 @@ Cluster::addTrack(Wt::Dbo::ptr<Track> track)
std::vector<Wt::Dbo::ptr<Track>> std::vector<Wt::Dbo::ptr<Track>>
Cluster::getTracks(int offset, int limit) const Cluster::getTracks(int offset, int 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") 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()) .where("c.id = ?").bind(self()->id())
.offset(offset) .offset(offset)
@@ -113,38 +128,51 @@ ClusterType::ClusterType(std::string name)
} }
std::vector<ClusterType::pointer> std::vector<ClusterType::pointer>
ClusterType::getAllOrphans(Wt::Dbo::Session& session) ClusterType::getAllOrphans(Session& session)
{ {
Wt::Dbo::collection<pointer> res = session.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"); session.checkSharedLocked();
Wt::Dbo::collection<pointer> 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");
return std::vector<pointer>(res.begin(), res.end()); return std::vector<pointer>(res.begin(), res.end());
} }
ClusterType::pointer ClusterType::pointer
ClusterType::getByName(Wt::Dbo::Session& session, std::string name) ClusterType::getByName(Session& session, std::string name)
{ {
return session.find<ClusterType>().where("name = ?").bind(name); session.checkSharedLocked();
return session.getDboSession().find<ClusterType>().where("name = ?").bind(name);
} }
ClusterType::pointer ClusterType::pointer
ClusterType::getById(Wt::Dbo::Session& session, IdType id) ClusterType::getById(Session& session, IdType id)
{ {
return session.find<ClusterType>().where("id= ?").bind(id); session.checkSharedLocked();
return session.getDboSession().find<ClusterType>().where("id= ?").bind(id);
} }
std::vector<ClusterType::pointer> std::vector<ClusterType::pointer>
ClusterType::getAll(Wt::Dbo::Session& session) ClusterType::getAll(Session& session)
{ {
Wt::Dbo::collection<pointer> res = session.find<ClusterType>(); session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<ClusterType>();
return std::vector<pointer>(res.begin(), res.end()); return std::vector<pointer>(res.begin(), res.end());
} }
ClusterType::pointer ClusterType::pointer
ClusterType::create(Wt::Dbo::Session& session, std::string name) ClusterType::create(Session& session, std::string name)
{ {
return session.add(std::make_unique<ClusterType>(name)); session.checkUniqueLocked();
ClusterType::pointer res {session.getDboSession().add(std::make_unique<ClusterType>(name))};
session.getDboSession().flush();
return res;
} }
Cluster::pointer Cluster::pointer
+11 -10
View File
@@ -33,6 +33,7 @@ namespace Database {
class Track; class Track;
class ClusterType; class ClusterType;
class ScanSettings; class ScanSettings;
class Session;
class Cluster : public Wt::Dbo::Dbo<Cluster> class Cluster : public Wt::Dbo::Dbo<Cluster>
{ {
@@ -43,12 +44,12 @@ class Cluster : public Wt::Dbo::Dbo<Cluster>
Cluster(Wt::Dbo::ptr<ClusterType> type, std::string name); Cluster(Wt::Dbo::ptr<ClusterType> type, std::string name);
// Find utility // Find utility
static std::vector<pointer> getAll(Wt::Dbo::Session& session); static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session); static std::vector<pointer> getAllOrphans(Session& session);
static pointer getById(Wt::Dbo::Session& session, IdType id); static pointer getById(Session& session, IdType id);
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<ClusterType> type, std::string name); static pointer create(Session& session, Wt::Dbo::ptr<ClusterType> type, std::string name);
// Accessors // Accessors
const std::string& getName() const { return _name; } const std::string& getName() const { return _name; }
@@ -89,13 +90,13 @@ class ClusterType : public Wt::Dbo::Dbo<ClusterType>
ClusterType() {} ClusterType() {}
ClusterType(std::string name); ClusterType(std::string name);
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session); static std::vector<pointer> getAllOrphans(Session& session);
static pointer getByName(Wt::Dbo::Session& session, std::string name); static pointer getByName(Session& session, std::string name);
static pointer getById(Wt::Dbo::Session& session, IdType id); static pointer getById(Session& session, IdType id);
static std::vector<pointer> getAll(Wt::Dbo::Session& session); static std::vector<pointer> getAll(Session& session);
static pointer create(Wt::Dbo::Session& session, std::string name); static pointer create(Session& session, std::string name);
static void remove(Wt::Dbo::Session& session, std::string name); static void remove(Session& session, std::string name);
// Accessors // Accessors
const std::string& getName(void) const { return _name; } const std::string& getName(void) const { return _name; }
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2019 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/>.
*/
#include "Database.hpp"
#include <Wt/Dbo/FixedSqlConnectionPool.h>
#include <Wt/Dbo/backend/Sqlite3.h>
#include "utils/Logger.hpp"
#include "User.hpp"
namespace Database {
// Session living class handling the database and the login
Database::Database(const boost::filesystem::path& dbPath)
{
LMS_LOG(DB, INFO) << "Creating connection pool on file " << dbPath.string();
std::unique_ptr<Wt::Dbo::backend::Sqlite3> connection {std::make_unique<Wt::Dbo::backend::Sqlite3>(dbPath.string())};
connection->executeSql("pragma journal_mode=WAL");
// connection->setProperty("show-queries", "true");
auto connectionPool = std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), 10);
connectionPool->setTimeout(std::chrono::seconds(10));
_connectionPool = std::move(connectionPool);
{
auto session {createSession()};
session->prepareTables();
}
}
std::unique_ptr<Session>
Database::createSession()
{
return std::unique_ptr<Session>{new Session {_sharedMutex, *_connectionPool.get()}};
}
} // namespace Database
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2019 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 <shared_mutex>
#include <boost/filesystem.hpp>
#include <Wt/Dbo/SqlConnectionPool.h>
#include "Session.hpp"
namespace Database {
// Session living class handling the database and the login
class Database
{
public:
Database(const boost::filesystem::path& dbPath);
std::unique_ptr<Session> createSession();
private:
std::shared_timed_mutex _sharedMutex;
std::unique_ptr<Wt::Dbo::SqlConnectionPool> _connectionPool;
};
} // namespace Database
-76
View File
@@ -1,76 +0,0 @@
/*
* Copyright (C) 2013 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 <boost/filesystem.hpp>
#include <memory>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include <Wt/Auth/Dbo/UserDatabase.h>
#include <Wt/Auth/Login.h>
#include <Wt/Auth/PasswordService.h>
#include "User.hpp"
namespace Database {
using UserDatabase = Wt::Auth::Dbo::UserDatabase<AuthInfo>;
// Session living class handling the database and the login
class Handler
{
public:
Handler(Wt::Dbo::SqlConnectionPool& connectionPool);
~Handler();
Wt::Dbo::Session& getSession() { return _session; }
void optimize();
Wt::Dbo::ptr<User> getCurrentUser(); // get the current user, may return empty
Wt::Dbo::ptr<User> getUser(const std::string& loginName);
Wt::Dbo::ptr<User> getUser(const Wt::Auth::User& authUser);
Wt::Dbo::ptr<User> createUser(const Wt::Auth::User& authUser);
Wt::Auth::AbstractUserDatabase& getUserDatabase();
Wt::Auth::Login& getLogin() { return _login; } // TODO move
// Long living shared associated services
static void configureAuth();
static const Wt::Auth::AuthService& getAuthService();
static const Wt::Auth::PasswordService& getPasswordService();
static std::unique_ptr<Wt::Dbo::SqlConnectionPool> createConnectionPool(boost::filesystem::path db);
private:
Wt::Dbo::Session _session;
UserDatabase* _users;
Wt::Auth::Login _login;
};
} // namespace Database
+46 -24
View File
@@ -23,6 +23,7 @@
#include "Artist.hpp" #include "Artist.hpp"
#include "Cluster.hpp" #include "Cluster.hpp"
#include "Session.hpp"
#include "SqlQuery.hpp" #include "SqlQuery.hpp"
#include "Track.hpp" #include "Track.hpp"
#include "User.hpp" #include "User.hpp"
@@ -38,41 +39,56 @@ _MBID(MBID)
} }
std::vector<Release::pointer> std::vector<Release::pointer>
Release::getByName(Wt::Dbo::Session& session, const std::string& name) Release::getByName(Session& session, const std::string& name)
{ {
Wt::Dbo::collection<Release::pointer> res = session.find<Release>().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); session.checkUniqueLocked();
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().find<Release>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
return std::vector<Release::pointer>(res.begin(), res.end()); return std::vector<Release::pointer>(res.begin(), res.end());
} }
Release::pointer Release::pointer
Release::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) Release::getByMBID(Session& session, const std::string& mbid)
{ {
return session.find<Release>().where("mbid = ?").bind(mbid); session.checkSharedLocked();
return session.getDboSession().find<Release>().where("mbid = ?").bind(mbid);
} }
Release::pointer Release::pointer
Release::getById(Wt::Dbo::Session& session, IdType id) Release::getById(Session& session, IdType id)
{ {
return session.find<Release>().where("id = ?").bind(id); session.checkSharedLocked();
return session.getDboSession().find<Release>().where("id = ?").bind(id);
} }
Release::pointer Release::pointer
Release::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID) Release::create(Session& session, const std::string& name, const std::string& MBID)
{ {
return session.add(std::make_unique<Release>(name, MBID)); session.checkSharedLocked();
Release::pointer res {session.getDboSession().add(std::make_unique<Release>(name, MBID))};
session.getDboSession().flush();
return res;
} }
std::size_t std::size_t
Release::getCount(Wt::Dbo::Session& session) Release::getCount(Session& session)
{ {
Wt::Dbo::collection<pointer> releases {session.find<Release>()}; session.checkSharedLocked();
Wt::Dbo::collection<pointer> releases {session.getDboSession().find<Release>()};
return releases.size(); return releases.size();
} }
std::vector<Release::pointer> std::vector<Release::pointer>
Release::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset, boost::optional<std::size_t> size) Release::getAll(Session& session, boost::optional<std::size_t> offset, boost::optional<std::size_t> size)
{ {
Wt::Dbo::collection<pointer> res = session.find<Release>() session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Release>()
.offset(offset ? static_cast<int>(*offset) : -1) .offset(offset ? static_cast<int>(*offset) : -1)
.limit(size ? static_cast<int>(*size) : -1) .limit(size ? static_cast<int>(*size) : -1)
.orderBy("name COLLATE NOCASE"); .orderBy("name COLLATE NOCASE");
@@ -81,9 +97,11 @@ Release::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset,
} }
std::vector<Release::pointer> std::vector<Release::pointer>
Release::getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> size) Release::getAllRandom(Session& session, boost::optional<std::size_t> size)
{ {
Wt::Dbo::collection<pointer> res = session.find<Release>() session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Release>()
.limit(size ? static_cast<int>(*size) : -1) .limit(size ? static_cast<int>(*size) : -1)
.orderBy("RANDOM()"); .orderBy("RANDOM()");
@@ -91,17 +109,21 @@ Release::getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> si
} }
std::vector<Release::pointer> std::vector<Release::pointer>
Release::getAllOrphans(Wt::Dbo::Session& session) Release::getAllOrphans(Session& session)
{ {
Wt::Dbo::collection<Release::pointer> res = session.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"); 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");
return std::vector<pointer>(res.begin(), res.end()); return std::vector<pointer>(res.begin(), res.end());
} }
std::vector<Release::pointer> std::vector<Release::pointer>
Release::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> offset, boost::optional<std::size_t> limit) Release::getLastAdded(Session& session, Wt::WDateTime after, boost::optional<std::size_t> offset, boost::optional<std::size_t> limit)
{ {
Wt::Dbo::collection<Release::pointer> res = session.query<Release::pointer>("SELECT r from release r INNER JOIN track t ON r.id = t.release_id") session.checkSharedLocked();
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().query<Release::pointer>("SELECT r from release r INNER JOIN track t ON r.id = t.release_id")
.where("t.file_added > ?").bind(after) .where("t.file_added > ?").bind(after)
.groupBy("r.id") .groupBy("r.id")
.orderBy("t.file_added DESC") .orderBy("t.file_added DESC")
@@ -113,9 +135,9 @@ Release::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::opt
static static
Wt::Dbo::Query<Release::pointer> Wt::Dbo::Query<Release::pointer>
getQuery(Wt::Dbo::Session& session, getQuery(Session& session,
const std::set<IdType>& clusterIds, const std::set<IdType>& clusterIds,
const std::vector<std::string> keywords) const std::vector<std::string>& keywords)
{ {
WhereClause where; WhereClause where;
@@ -144,7 +166,7 @@ getQuery(Wt::Dbo::Session& session,
oss << " ORDER BY r.name COLLATE NOCASE"; oss << " ORDER BY r.name COLLATE NOCASE";
Wt::Dbo::Query<Release::pointer> query = session.query<Release::pointer>( oss.str() ); Wt::Dbo::Query<Release::pointer> query = session.getDboSession().query<Release::pointer>( oss.str() );
for (const std::string& bindArg : where.getBindArgs()) for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg); query.bind(bindArg);
@@ -153,16 +175,16 @@ getQuery(Wt::Dbo::Session& session,
} }
std::vector<Release::pointer> std::vector<Release::pointer>
Release::getByFilter(Wt::Dbo::Session& session, const std::set<IdType>& clusterIds) Release::getByFilter(Session& session, const std::set<IdType>& clusterIds)
{ {
bool moreResults; bool moreResults;
return getByFilter(session, clusterIds, {}, {}, {}, moreResults); return getByFilter(session, clusterIds, {}, {}, {}, moreResults);
} }
std::vector<Release::pointer> std::vector<Release::pointer>
Release::getByFilter(Wt::Dbo::Session& session, Release::getByFilter(Session& session,
const std::set<IdType>& clusterIds, const std::set<IdType>& clusterIds,
const std::vector<std::string> keywords, const std::vector<std::string>& keywords,
boost::optional<std::size_t> offset, boost::optional<std::size_t> offset,
boost::optional<std::size_t> size, boost::optional<std::size_t> size,
bool& moreResults) bool& moreResults)
+12 -12
View File
@@ -46,19 +46,19 @@ class Release : public Wt::Dbo::Dbo<Release>
Release(const std::string& name, const std::string& MBID = ""); Release(const std::string& name, const std::string& MBID = "");
// Accessors // Accessors
static std::size_t getCount(Wt::Dbo::Session& session); static std::size_t getCount(Session& session);
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); static pointer getByMBID(Session& session, const std::string& MBID);
static std::vector<pointer> getByName(Wt::Dbo::Session& session, const std::string& name); static std::vector<pointer> getByName(Session& session, const std::string& name);
static pointer getById(Wt::Dbo::Session& session, IdType id); static pointer getById(Session& session, IdType id);
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session); // no track related static std::vector<pointer> getAllOrphans(Session& session); // no track related
static std::vector<pointer> getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {}); static std::vector<pointer> getAll(Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
static std::vector<pointer> getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> size = {}); static std::vector<pointer> getAllRandom(Session& session, boost::optional<std::size_t> size = {});
static std::vector<pointer> getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {}); static std::vector<pointer> getLastAdded(Session& session, Wt::WDateTime after, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, const std::set<IdType>& clusters); static std::vector<pointer> getByFilter(Session& session, const std::set<IdType>& clusters);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // at least one track that belongs to these clusters const std::set<IdType>& clusters, // at least one track that belongs to these clusters
const std::vector<std::string> keywords, // name must match all of these keywords const std::vector<std::string>& keywords, // name must match all of these keywords
boost::optional<std::size_t> offset, boost::optional<std::size_t> offset,
boost::optional<std::size_t> size, boost::optional<std::size_t> size,
bool& moreExpected); bool& moreExpected);
@@ -72,7 +72,7 @@ class Release : public Wt::Dbo::Dbo<Release>
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<Wt::Dbo::ptr<Cluster>>> getClusterGroups(std::vector<Wt::Dbo::ptr<ClusterType>> clusterTypes, std::size_t size) const;
// Create // Create
static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); static pointer create(Session& session, const std::string& name, const std::string& MBID = "");
// Utility functions // Utility functions
boost::optional<int> getReleaseYear(bool originalDate = false) const; // 0 if unknown or various boost::optional<int> getReleaseYear(bool originalDate = false) const; // 0 if unknown or various
+42 -21
View File
@@ -25,10 +25,11 @@
#include "utils/Utils.hpp" #include "utils/Utils.hpp"
#include "Cluster.hpp" #include "Cluster.hpp"
#include "Session.hpp"
namespace { namespace {
std::set<std::string> defaultClusterTypeNames = const std::set<std::string> defaultClusterTypeNames =
{ {
"GENRE", "GENRE",
"ALBUMGROUPING", "ALBUMGROUPING",
@@ -40,31 +41,38 @@ std::set<std::string> defaultClusterTypeNames =
namespace Database { namespace Database {
void
ScanSettings::init(Session& session)
{
session.checkUniqueLocked();
ScanSettings::pointer pointer settings {get(session)};
ScanSettings::get(Wt::Dbo::Session& session) if (settings)
{ return;
pointer settings = session.find<ScanSettings>();
if (!settings) settings = session.getDboSession().add(std::make_unique<ScanSettings>());
{ settings.modify()->setClusterTypes(session, defaultClusterTypeNames );
settings = session.add(std::make_unique<ScanSettings>());
settings.modify()->setClusterTypes(defaultClusterTypeNames);
} }
return settings; ScanSettings::pointer
ScanSettings::get(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<ScanSettings>();
} }
std::set<boost::filesystem::path> std::set<boost::filesystem::path>
ScanSettings::getAudioFileExtensions() const ScanSettings::getAudioFileExtensions() const
{ {
auto extensions = splitString(_audioFileExtensions, " "); auto extensions = splitString(_audioFileExtensions, " ");
return std::set<boost::filesystem::path>(extensions.begin(), extensions.end()); return std::set<boost::filesystem::path>(std::cbegin(extensions), std::cend(extensions));
} }
std::vector<ClusterType::pointer> std::vector<ClusterType::pointer>
ScanSettings::getClusterTypes() const ScanSettings::getClusterTypes() const
{ {
return std::vector<ClusterType::pointer>(_clusterTypes.begin(), _clusterTypes.end()); return std::vector<ClusterType::pointer>(std::cbegin(_clusterTypes), std::cend(_clusterTypes));
} }
void void
@@ -73,20 +81,34 @@ ScanSettings::setMediaDirectory(boost::filesystem::path p)
_mediaDirectory = stringTrimEnd(p.string(), "/\\"); _mediaDirectory = stringTrimEnd(p.string(), "/\\");
} }
void template <typename It>
ScanSettings::setClusterTypes(const std::set<std::string>& clusterTypeNames) std::set<std::string> getNames(It begin, It end)
{ {
bool needRescan = false; std::set<std::string> names;
assert(session()); std::transform(begin, end, std::inserter(names, std::begin(names)),
[](const ClusterType::pointer& clusterType)
{
return clusterType->getName();
});
return names;
}
void
ScanSettings::setClusterTypes(Session& session, const std::set<std::string>& clusterTypeNames)
{
session.checkUniqueLocked();
bool needRescan {};
// Create any missing cluster type // Create any missing cluster type
for (const auto& clusterTypeName : clusterTypeNames) for (const std::string& clusterTypeName : clusterTypeNames)
{ {
auto clusterType = ClusterType::getByName(*session(), clusterTypeName); auto clusterType {ClusterType::getByName(session, clusterTypeName)};
if (!clusterType) if (!clusterType)
{ {
LMS_LOG(DB, INFO) << "Creating cluster type " << clusterTypeName; LMS_LOG(DB, INFO) << "Creating cluster type " << clusterTypeName;
clusterType = ClusterType::create(*session(), clusterTypeName); clusterType = ClusterType::create(session, clusterTypeName);
_clusterTypes.insert(clusterType); _clusterTypes.insert(clusterType);
needRescan = true; needRescan = true;
@@ -94,7 +116,7 @@ ScanSettings::setClusterTypes(const std::set<std::string>& clusterTypeNames)
} }
// Delete no longer existing cluster types // Delete no longer existing cluster types
for (auto clusterType : _clusterTypes) for (ClusterType::pointer& clusterType : _clusterTypes)
{ {
if (std::none_of(clusterTypeNames.begin(), clusterTypeNames.end(), if (std::none_of(clusterTypeNames.begin(), clusterTypeNames.end(),
[clusterType](const std::string& name) { return name == clusterType->getName(); })) [clusterType](const std::string& name) { return name == clusterType->getName(); }))
@@ -108,6 +130,5 @@ ScanSettings::setClusterTypes(const std::set<std::string>& clusterTypeNames)
_scanVersion += 1; _scanVersion += 1;
} }
} // namespace Database } // namespace Database
+5 -2
View File
@@ -27,6 +27,7 @@
namespace Database { namespace Database {
class ClusterType; class ClusterType;
class Session;
class ScanSettings : public Wt::Dbo::Dbo<ScanSettings> class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
{ {
@@ -40,7 +41,9 @@ class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
Monthly Monthly
}; };
static pointer get(Wt::Dbo::Session& session); static void init(Session& session);
static pointer get(Session& session);
// Getters // Getters
std::size_t getScanVersion() const { return _scanVersion; } std::size_t getScanVersion() const { return _scanVersion; }
@@ -54,7 +57,7 @@ class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
void setMediaDirectory(boost::filesystem::path p); void setMediaDirectory(boost::filesystem::path p);
void setUpdateStartTime(Wt::WTime t) { _startTime = t; } void setUpdateStartTime(Wt::WTime t) { _startTime = t; }
void setUpdatePeriod(UpdatePeriod p) { _updatePeriod = p; } void setUpdatePeriod(UpdatePeriod p) { _updatePeriod = p; }
void setClusterTypes(const std::set<std::string>& clusterTypeNames); void setClusterTypes(Session& session, const std::set<std::string>& clusterTypeNames);
void setAudioFileExtensions(std::set<boost::filesystem::path> fileExtensions); void setAudioFileExtensions(std::set<boost::filesystem::path> fileExtensions);
template<class Action> template<class Action>
@@ -17,10 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "DatabaseHandler.hpp" #include "Session.hpp"
#include <Wt/Dbo/FixedSqlConnectionPool.h>
#include <Wt/Dbo/backend/Sqlite3.h>
#include <Wt/Auth/Dbo/AuthInfo.h> #include <Wt/Auth/Dbo/AuthInfo.h>
#include <Wt/Auth/Dbo/UserDatabase.h> #include <Wt/Auth/Dbo/UserDatabase.h>
@@ -43,6 +40,7 @@
#include "TrackArtistLink.hpp" #include "TrackArtistLink.hpp"
#include "TrackList.hpp" #include "TrackList.hpp"
#include "TrackFeatures.hpp" #include "TrackFeatures.hpp"
#include "User.hpp"
namespace Database { namespace Database {
@@ -60,15 +58,24 @@ class VersionInfo
public: public:
using pointer = Wt::Dbo::ptr<VersionInfo>; using pointer = Wt::Dbo::ptr<VersionInfo>;
static VersionInfo::pointer get(Wt::Dbo::Session& session) static VersionInfo::pointer getOrCreate(Session& session)
{ {
pointer versionInfo {session.find<VersionInfo>()}; session.checkUniqueLocked();
pointer versionInfo {session.getDboSession().find<VersionInfo>()};
if (!versionInfo) if (!versionInfo)
versionInfo = session.add(std::make_unique<VersionInfo>()); return session.getDboSession().add(std::make_unique<VersionInfo>());
return versionInfo; return versionInfo;
} }
static VersionInfo::pointer get(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<VersionInfo>();
}
Version getVersion() const { return _version; } Version getVersion() const { return _version; }
void setVersion(Version version) { _version = static_cast<int>(version); } void setVersion(Version version) { _version = static_cast<int>(version); }
@@ -82,18 +89,18 @@ class VersionInfo
int _version {LMS_DATABASE_VERSION}; int _version {LMS_DATABASE_VERSION};
}; };
static
void void
doDatabaseMigrationIfNeeded(Wt::Dbo::Session& session) Session::doDatabaseMigrationIfNeeded()
{ {
Wt::Dbo::Transaction transaction {session}; auto uniqueTransaction {createUniqueTransaction()};
static const std::string outdatedMsg {"Outdated database, please rebuild it (delete the .db file and restart)"}; static const std::string outdatedMsg {"Outdated database, please rebuild it (delete the .db file and restart)"};
Version version; Version version;
try try
{ {
version = VersionInfo::get(session)->getVersion(); version = VersionInfo::getOrCreate(*this)->getVersion();
LMS_LOG(DB, INFO) << "Database version = " << version;
if (version == LMS_DATABASE_VERSION) if (version == LMS_DATABASE_VERSION)
return; return;
} }
@@ -109,30 +116,30 @@ doDatabaseMigrationIfNeeded(Wt::Dbo::Session& session)
LMS_LOG(DB, INFO) << "Migrating database from version 3..."; LMS_LOG(DB, INFO) << "Migrating database from version 3...";
session.execute(R"(CREATE TABLE IF NOT EXISTS "user_artist_starred" ( _session.execute(R"(CREATE TABLE IF NOT EXISTS "user_artist_starred" (
"user_id" bigint, "user_id" bigint,
"artist_id" bigint, "artist_id" bigint,
primary key ("user_id", "artist_id"), primary key ("user_id", "artist_id"),
constraint "fk_user_artist_starred_key1" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred, constraint "fk_user_artist_starred_key1" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred,
constraint "fk_user_artist_starred_key2" foreign key ("artist_id") references "artist" ("id") deferrable initially deferred);)"); constraint "fk_user_artist_starred_key2" foreign key ("artist_id") references "artist" ("id") deferrable initially deferred);)");
session.execute(R"(CREATE INDEX "user_artist_starred_user" on "user_artist_starred" ("user_id");)"); _session.execute(R"(CREATE INDEX "user_artist_starred_user" on "user_artist_starred" ("user_id");)");
session.execute(R"(CREATE INDEX "user_artist_starred_artist" on "user_artist_starred" ("artist_id");)"); _session.execute(R"(CREATE INDEX "user_artist_starred_artist" on "user_artist_starred" ("artist_id");)");
session.execute(R"(CREATE TABLE IF NOT EXISTS "user_release_starred" ( _session.execute(R"(CREATE TABLE IF NOT EXISTS "user_release_starred" (
"user_id" bigint, "user_id" bigint,
"release_id" bigint, "release_id" bigint,
primary key ("user_id", "release_id"), primary key ("user_id", "release_id"),
constraint "fk_user_release_starred_key1" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred, constraint "fk_user_release_starred_key1" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred,
constraint "fk_user_release_starred_key2" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred);)"); constraint "fk_user_release_starred_key2" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred);)");
session.execute(R"(CREATE INDEX "user_release_starred_user" on "user_release_starred" ("user_id");)"); _session.execute(R"(CREATE INDEX "user_release_starred_user" on "user_release_starred" ("user_id");)");
session.execute(R"(CREATE INDEX "user_release_starred_release" on "user_release_starred" ("release_id");)"); _session.execute(R"(CREATE INDEX "user_release_starred_release" on "user_release_starred" ("release_id");)");
session.execute(R"(CREATE TABLE IF NOT EXISTS "user_track_starred" ( _session.execute(R"(CREATE TABLE IF NOT EXISTS "user_track_starred" (
"user_id" bigint, "user_id" bigint,
"track_id" bigint, "track_id" bigint,
primary key ("user_id", "track_id"), primary key ("user_id", "track_id"),
constraint "fk_user_track_starred_key1" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred, constraint "fk_user_track_starred_key1" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred,
constraint "fk_user_track_starred_key2" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred);)"); constraint "fk_user_track_starred_key2" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred);)");
session.execute(R"(CREATE INDEX "user_track_starred_user" on "user_track_starred" ("user_id");)"); _session.execute(R"(CREATE INDEX "user_track_starred_user" on "user_track_starred" ("user_id");)");
session.execute(R"(CREATE INDEX "user_track_starred_track" on "user_track_starred" ("track_id");)"); _session.execute(R"(CREATE INDEX "user_track_starred_track" on "user_track_starred" ("track_id");)");
break; break;
default: default:
@@ -140,12 +147,12 @@ doDatabaseMigrationIfNeeded(Wt::Dbo::Session& session)
throw LmsException {outdatedMsg}; throw LmsException {outdatedMsg};
} }
VersionInfo::get(session).modify()->setVersion(LMS_DATABASE_VERSION); VersionInfo::get(*this).modify()->setVersion(LMS_DATABASE_VERSION);
} }
void void
Handler::configureAuth(void) Session::configureAuth(void)
{ {
authService.setEmailVerificationEnabled(false); authService.setEmailVerificationEnabled(false);
authService.setAuthTokensEnabled(true, "lmsauth"); authService.setAuthTokensEnabled(true, "lmsauth");
@@ -176,19 +183,20 @@ Handler::configureAuth(void)
} }
const Wt::Auth::AuthService& const Wt::Auth::AuthService&
Handler::getAuthService() Session::getAuthService()
{ {
return authService; return authService;
} }
const Wt::Auth::PasswordService& const Wt::Auth::PasswordService&
Handler::getPasswordService() Session::getPasswordService()
{ {
return passwordService; return passwordService;
} }
Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) Session::Session(std::shared_timed_mutex& mutex, Wt::Dbo::SqlConnectionPool& connectionPool)
: _mutex {mutex}
{ {
_session.setConnectionPool(connectionPool); _session.setConnectionPool(connectionPool);
@@ -212,9 +220,76 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
_session.mapClass<AuthInfo::AuthTokenType>("auth_token"); _session.mapClass<AuthInfo::AuthTokenType>("auth_token");
_session.mapClass<User>("user"); _session.mapClass<User>("user");
try { _users = std::make_unique<UserDatabase>(_session);
Wt::Dbo::Transaction transaction {_session}; }
// TODO make this per database
static thread_local bool hasSharedLock {false};
static thread_local bool hasUniqueLock {false};
UniqueTransaction::UniqueTransaction(std::shared_timed_mutex& mutex, Wt::Dbo::Session& session)
: _lock {mutex},
_transaction {session}
{
assert(!hasSharedLock);
assert(!hasUniqueLock);
hasUniqueLock = true;
LMS_LOG(DB, DEBUG) << "UniqueTransaction ACQUIRED";
}
UniqueTransaction::~UniqueTransaction()
{
assert(hasUniqueLock);
hasUniqueLock = false;
LMS_LOG(DB, DEBUG) << "UniqueTransaction RELEASED";
}
SharedTransaction::SharedTransaction(std::shared_timed_mutex& mutex, Wt::Dbo::Session& session)
: _lock {mutex},
_transaction {session}
{
assert(!hasSharedLock);
assert(!hasUniqueLock);
hasSharedLock = true;
LMS_LOG(DB, DEBUG) << "SharedTransaction ACQUIRED";
}
SharedTransaction::~SharedTransaction()
{
assert(hasSharedLock);
hasSharedLock = false;
LMS_LOG(DB, DEBUG) << "SharedTransaction RELEASED";
}
void
Session::checkUniqueLocked()
{
assert(hasUniqueLock);
}
void
Session::checkSharedLocked()
{
assert(hasUniqueLock || hasSharedLock);
}
std::unique_ptr<UniqueTransaction>
Session::createUniqueTransaction()
{
return std::unique_ptr<UniqueTransaction>(new UniqueTransaction{_mutex, _session});
}
std::unique_ptr<SharedTransaction>
Session::createSharedTransaction()
{
return std::unique_ptr<SharedTransaction>(new SharedTransaction{_mutex, _session});
}
void
Session::prepareTables()
{
// Creation case
try {
_session.createTables(); _session.createTables();
LMS_LOG(DB, INFO) << "Tables created"; LMS_LOG(DB, INFO) << "Tables created";
@@ -224,12 +299,11 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what(); LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what();
} }
doDatabaseMigrationIfNeeded(_session); doDatabaseMigrationIfNeeded();
{
Wt::Dbo::Transaction transaction {_session};
// Indexes // Indexes
{
auto uniqueTransaction {createUniqueTransaction()};
_session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)"); _session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)");
_session.execute("CREATE INDEX IF NOT EXISTS artist_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE)"); _session.execute("CREATE INDEX IF NOT EXISTS artist_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE)");
_session.execute("CREATE INDEX IF NOT EXISTS artist_mbid_idx ON artist(mbid)"); _session.execute("CREATE INDEX IF NOT EXISTS artist_mbid_idx ON artist(mbid)");
@@ -255,29 +329,101 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
_session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_type_idx ON track_artist_link(type)"); _session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_type_idx ON track_artist_link(type)");
} }
_users = new UserDatabase(_session); // Initial settings tables
}
Handler::~Handler()
{ {
delete _users; auto uniqueTransaction {createUniqueTransaction()};
ScanSettings::init(*this);
SimilaritySettings::init(*this);
}
} }
void void
Handler::optimize() Session::optimize()
{ {
Wt::Dbo::Transaction transaction {_session}; auto uniqueTransaction {createUniqueTransaction()};
_session.execute("ANALYZE"); _session.execute("ANALYZE");
} }
std::string
Session::getUserLoginName(Wt::Dbo::ptr<User> user)
{
const Wt::Auth::User authUser {_users->findWithId(std::to_string(user.id()))};
if (!authUser.isValid())
throw LmsException {"Invalid user state"};
return authUser.identity(Wt::Auth::Identity::LoginName).toUTF8();
}
bool
Session::checkUserPassword(const std::string& loginName, const std::string& password)
{
auto transaction {createUniqueTransaction()};
auto authUser {_users->findWithIdentity(Wt::Auth::Identity::LoginName, loginName)};
if (!authUser.isValid())
return false; // TODO const time?
return passwordService.verifyPassword(authUser, password) == Wt::Auth::PasswordResult::PasswordValid;
}
void
Session::updateUserPassword(Wt::Dbo::ptr<User> user, const std::string& password)
{
const Wt::Auth::User authUser {_users->findWithId(std::to_string(user.id()))};
if (!authUser.isValid())
throw LmsException {"Bad user state"};
passwordService.updatePassword(authUser, password);
}
Wt::WDateTime
Session::getUserLastLoginAttempt(Wt::Dbo::ptr<User> user)
{
const Wt::Auth::User authUser {_users->findWithId(std::to_string(user.id()))};
if (!authUser.isValid())
throw LmsException {"Bad user state"};
return authUser.lastLoginAttempt();
}
void
Session::removeUser(Database::User::pointer user)
{
checkUniqueLocked();
auto authUser = _users->findWithId(std::to_string(user.id()));
_users->deleteUser(authUser);
user.remove();
}
Wt::Auth::AbstractUserDatabase& Wt::Auth::AbstractUserDatabase&
Handler::getUserDatabase() Session::getUserDatabase()
{ {
return *_users; return *_users;
} }
User::pointer User::pointer
Handler::getCurrentUser() Session::createUser(const std::string& loginName, const std::string& password)
{
Wt::Auth::User authUser {_users->registerNew()};
if (!authUser.isValid())
{
LMS_LOG(DB, ERROR) << "Invalid authUser";
return {};
}
User::pointer user {User::create(*this)};
Wt::Dbo::ptr<AuthInfo> authInfo = _users->find(authUser);
authInfo.modify()->setUser(user);
authUser.setIdentity(Wt::Auth::Identity::LoginName, loginName);
passwordService.updatePassword(authUser, password);
return user;
}
User::pointer
Session::getLoggedUser()
{ {
if (_login.loggedIn()) if (_login.loggedIn())
return getUser(_login.user()); return getUser(_login.user());
@@ -286,10 +432,10 @@ Handler::getCurrentUser()
} }
User::pointer User::pointer
Handler::getUser(const Wt::Auth::User& authUser) Session::getUser(const Wt::Auth::User& authUser)
{ {
if (!authUser.isValid()) { if (!authUser.isValid()) {
LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser"; LMS_LOG(DB, ERROR) << "Session::getUser: invalid authUser";
return User::pointer(); return User::pointer();
} }
@@ -299,7 +445,7 @@ Handler::getUser(const Wt::Auth::User& authUser)
} }
User::pointer User::pointer
Handler::getUser(const std::string& loginName) Session::getUser(const std::string& loginName)
{ {
auto authUser {getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, loginName)}; auto authUser {getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, loginName)};
if (!authUser.isValid()) if (!authUser.isValid())
@@ -308,36 +454,4 @@ Handler::getUser(const std::string& loginName)
return getUser(authUser); return getUser(authUser);
} }
User::pointer
Handler::createUser(const Wt::Auth::User& authUser)
{
if (!authUser.isValid())
{
LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser";
return User::pointer();
}
User::pointer user = _session.add(std::make_unique<User>());
Wt::Dbo::ptr<AuthInfo> authInfo = _users->find(authUser);
authInfo.modify()->setUser(user);
return user;
}
std::unique_ptr<Wt::Dbo::SqlConnectionPool>
Handler::createConnectionPool(boost::filesystem::path p)
{
LMS_LOG(DB, INFO) << "Creating connection pool on file " << p.string();
auto connection = std::make_unique<Wt::Dbo::backend::Sqlite3>(p.string());
connection->executeSql("pragma journal_mode=WAL");
// connection->setProperty("show-queries", "true");
auto pool = std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), 1);
pool->setTimeout(std::chrono::seconds(10));
return pool;
}
} // namespace Database } // namespace Database
+123
View File
@@ -0,0 +1,123 @@
/*
* Copyright (C) 2013 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 <shared_mutex>
#include <mutex>
#include <boost/filesystem.hpp>
#include <memory>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include <Wt/Auth/Dbo/AuthInfo.h>
#include <Wt/Auth/Dbo/UserDatabase.h>
#include <Wt/Auth/Login.h>
#include <Wt/Auth/PasswordService.h>
#include "User.hpp"
namespace Database {
using AuthInfo = Wt::Auth::Dbo::AuthInfo<User>;
using UserDatabase = Wt::Auth::Dbo::UserDatabase<AuthInfo>;
class UniqueTransaction
{
public:
~UniqueTransaction();
private:
friend class Session;
UniqueTransaction(std::shared_timed_mutex& mutex, Wt::Dbo::Session& session);
std::unique_lock<std::shared_timed_mutex> _lock;
Wt::Dbo::Transaction _transaction;
};
class SharedTransaction
{
public:
~SharedTransaction();
private:
friend class Session;
SharedTransaction(std::shared_timed_mutex& mutex, Wt::Dbo::Session& session);
std::shared_lock<std::shared_timed_mutex> _lock;
Wt::Dbo::Transaction _transaction;
};
class Session
{
public:
Session(const Session&) = delete;
Session(Session&&) = delete;
Session& operator=(const Session&) = delete;
Session& operator=(Session&&) = delete;
std::unique_ptr<UniqueTransaction> createUniqueTransaction();
std::unique_ptr<SharedTransaction> createSharedTransaction();
void checkUniqueLocked();
void checkSharedLocked();
void optimize();
// User management
Wt::Dbo::ptr<User> getLoggedUser(); // get the current user, may return empty
Wt::Dbo::ptr<User> getUser(const std::string& loginName);
std::string getUserLoginName(Wt::Dbo::ptr<User> user);
Wt::Dbo::ptr<User> createUser(const std::string& loginName, const std::string& password);
void removeUser(Wt::Dbo::ptr<User> user);
bool checkUserPassword(const std::string& loginName, const std::string& password);
void updateUserPassword(Wt::Dbo::ptr<User> user, const std::string& password);
Wt::WDateTime getUserLastLoginAttempt(Wt::Dbo::ptr<User> user);
Wt::Auth::AbstractUserDatabase& getUserDatabase();
Wt::Auth::Login& getLogin() { return _login; } // TODO move
// Long living shared associated services
static void configureAuth();
static const Wt::Auth::AuthService& getAuthService();
static const Wt::Auth::PasswordService& getPasswordService();
Wt::Dbo::Session& getDboSession() { return _session; }
private:
friend class Database;
Session(std::shared_timed_mutex& mutex, Wt::Dbo::SqlConnectionPool& connectionPool);
void doDatabaseMigrationIfNeeded();
void prepareTables(); // need to run only once at startup
Wt::Dbo::ptr<User> getUser(const Wt::Auth::User& authUser);
std::shared_timed_mutex& _mutex;
Wt::Dbo::Session _session;
std::unique_ptr<UserDatabase> _users;
Wt::Auth::Login _login;
};
} // namespace Database
+24 -10
View File
@@ -22,6 +22,7 @@
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/Utils.hpp" #include "utils/Utils.hpp"
#include "Session.hpp"
#include "TrackFeatures.hpp" #include "TrackFeatures.hpp"
namespace Database { namespace Database {
@@ -33,7 +34,7 @@ struct TrackFeatureInfo
double weight; double weight;
}; };
static std::vector<TrackFeatureInfo> defaultFeatures = static const std::vector<TrackFeatureInfo> defaultFeatures =
{ {
{ "lowlevel.spectral_contrast_coeffs.median", 6, 1. }, { "lowlevel.spectral_contrast_coeffs.median", 6, 1. },
{ "lowlevel.erbbands.median", 40, 1. }, { "lowlevel.erbbands.median", 40, 1. },
@@ -53,24 +54,37 @@ _settings(settings)
} }
SimilaritySettingsFeature::pointer SimilaritySettingsFeature::pointer
SimilaritySettingsFeature::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<SimilaritySettings> settings, const std::string& name, std::size_t nbDimensions, double weight) SimilaritySettingsFeature::create(Session& session, Wt::Dbo::ptr<SimilaritySettings> settings, const std::string& name, std::size_t nbDimensions, double weight)
{ {
return session.add(std::make_unique<SimilaritySettingsFeature>(settings, name, nbDimensions, weight)); session.checkUniqueLocked();
SimilaritySettingsFeature::pointer res {session.getDboSession().add(std::make_unique<SimilaritySettingsFeature>(settings, name, nbDimensions, weight))};
session.getDboSession().flush();
return res;
} }
SimilaritySettings::pointer void
SimilaritySettings::get(Wt::Dbo::Session& session) SimilaritySettings::init(Session& session)
{ {
pointer settings = session.find<SimilaritySettings>(); session.checkUniqueLocked();
if (!settings)
{
settings = session.add(std::make_unique<SimilaritySettings>());
pointer settings {session.getDboSession().find<SimilaritySettings>()};
if (settings)
return;
settings = session.getDboSession().add(std::make_unique<SimilaritySettings>());
for (const auto& feature : defaultFeatures) for (const auto& feature : defaultFeatures)
SimilaritySettingsFeature::create(session, settings, feature.name, feature.nbDimensions, feature.weight); SimilaritySettingsFeature::create(session, settings, feature.name, feature.nbDimensions, feature.weight);
} }
return settings;
SimilaritySettings::pointer
SimilaritySettings::get(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<SimilaritySettings>();
} }
std::vector<Wt::Dbo::ptr<SimilaritySettingsFeature>> std::vector<Wt::Dbo::ptr<SimilaritySettingsFeature>>
+4 -2
View File
@@ -23,6 +23,7 @@
namespace Database { namespace Database {
class Session;
class SimilaritySettings; class SimilaritySettings;
class SimilaritySettingsFeature : public Wt::Dbo::Dbo<SimilaritySettingsFeature> class SimilaritySettingsFeature : public Wt::Dbo::Dbo<SimilaritySettingsFeature>
@@ -33,7 +34,7 @@ class SimilaritySettingsFeature : public Wt::Dbo::Dbo<SimilaritySettingsFeature
SimilaritySettingsFeature() = default; SimilaritySettingsFeature() = default;
SimilaritySettingsFeature(Wt::Dbo::ptr<SimilaritySettings> settings, const std::string& name, std::size_t nbDimensions, double weight); SimilaritySettingsFeature(Wt::Dbo::ptr<SimilaritySettings> settings, const std::string& name, std::size_t nbDimensions, double weight);
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<SimilaritySettings> settings, const std::string& name, std::size_t nbDimensions, double weight = 1); static pointer create(Session& session, Wt::Dbo::ptr<SimilaritySettings> settings, const std::string& name, std::size_t nbDimensions, double weight = 1);
const std::string& getName() const { return _name; } ; const std::string& getName() const { return _name; } ;
std::size_t getNbDimensions() const { return static_cast<std::size_t>(_nbDimensions); } std::size_t getNbDimensions() const { return static_cast<std::size_t>(_nbDimensions); }
@@ -70,7 +71,8 @@ class SimilaritySettings : public Wt::Dbo::Dbo<SimilaritySettings>
using pointer = Wt::Dbo::ptr<SimilaritySettings>; using pointer = Wt::Dbo::ptr<SimilaritySettings>;
// Utils // Utils
static pointer get(Wt::Dbo::Session& session); static void init(Session& session);
static pointer get(Session& session);
// Accessors Read // Accessors Read
std::size_t getVersion() const { return _settingsVersion; } std::size_t getVersion() const { return _settingsVersion; }
+70 -38
View File
@@ -27,6 +27,7 @@
#include "Cluster.hpp" #include "Cluster.hpp"
#include "Release.hpp" #include "Release.hpp"
#include "TrackFeatures.hpp" #include "TrackFeatures.hpp"
#include "Session.hpp"
#include "SqlQuery.hpp" #include "SqlQuery.hpp"
namespace Database { namespace Database {
@@ -38,18 +39,22 @@ _filePath( p.string() )
} }
std::vector<Track::pointer> std::vector<Track::pointer>
Track::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> limit) Track::getAll(Session& session, boost::optional<std::size_t> limit)
{ {
Wt::Dbo::collection<Track::pointer> res {session.find<Track>() session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res {session.getDboSession().find<Track>()
.limit(limit ? static_cast<int>(*limit) : -1)}; .limit(limit ? static_cast<int>(*limit) : -1)};
return std::vector<Track::pointer>(std::cbegin(res), std::cend(res)); return std::vector<Track::pointer>(std::cbegin(res), std::cend(res));
} }
std::vector<Track::pointer> std::vector<Track::pointer>
Track::getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> limit) Track::getAllRandom(Session& session, boost::optional<std::size_t> limit)
{ {
Wt::Dbo::collection<Track::pointer> res {session.find<Track>() session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res {session.getDboSession().find<Track>()
.limit(limit ? static_cast<int>(*limit) : -1) .limit(limit ? static_cast<int>(*limit) : -1)
.orderBy("RANDOM()")}; .orderBy("RANDOM()")};
@@ -57,67 +62,88 @@ Track::getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> limi
} }
std::vector<IdType> std::vector<IdType>
Track::getAllIds(Wt::Dbo::Session& session) Track::getAllIds(Session& session)
{ {
Wt::Dbo::Transaction transaction(session); session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.query<IdType>("SELECT id from track");
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM track");
return std::vector<IdType>(res.begin(), res.end()); return std::vector<IdType>(res.begin(), res.end());
} }
Track::pointer Track::pointer
Track::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p) Track::getByPath(Session& session, const boost::filesystem::path& p)
{ {
return session.find<Track>().where("file_path = ?").bind(p.string()); session.checkSharedLocked();
return session.getDboSession().find<Track>().where("file_path = ?").bind(p.string());
} }
Track::pointer Track::pointer
Track::getById(Wt::Dbo::Session& session, IdType id) Track::getById(Session& session, IdType id)
{ {
return session.find<Track>().where("id = ?").bind(id); session.checkSharedLocked();
return session.getDboSession().find<Track>()
.where("id = ?").bind(id);
} }
Track::pointer Track::pointer
Track::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) Track::getByMBID(Session& session, const std::string& mbid)
{ {
return session.find<Track>().where("mbid = ?").bind(mbid); session.checkSharedLocked();
return session.getDboSession().find<Track>()
.where("mbid = ?").bind(mbid);
} }
Track::pointer Track::pointer
Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p) Track::create(Session& session, const boost::filesystem::path& p)
{ {
return session.add(std::make_unique<Track>(p)); session.checkUniqueLocked();
Track::pointer res {session.getDboSession().add(std::make_unique<Track>(p))};
session.getDboSession().flush();
return res;
} }
std::vector<boost::filesystem::path> std::vector<boost::filesystem::path>
Track::getAllPaths(Wt::Dbo::Session& session) Track::getAllPaths(Session& session)
{ {
Wt::Dbo::Transaction transaction(session); session.checkSharedLocked();
Wt::Dbo::collection<std::string> res = session.query<std::string>("SELECT file_path from track");
Wt::Dbo::collection<std::string> res = session.getDboSession().query<std::string>("SELECT file_path FROM track");
return std::vector<boost::filesystem::path>(res.begin(), res.end()); return std::vector<boost::filesystem::path>(res.begin(), res.end());
} }
std::vector<Track::pointer> std::vector<Track::pointer>
Track::getMBIDDuplicates(Wt::Dbo::Session& session) Track::getMBIDDuplicates(Session& session)
{ {
Wt::Dbo::collection<pointer> res = session.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"); 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");
return std::vector<pointer>(res.begin(), res.end()); return std::vector<pointer>(res.begin(), res.end());
} }
std::vector<Track::pointer> std::vector<Track::pointer>
Track::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int limit) Track::getLastAdded(Session& session, const Wt::WDateTime& after, boost::optional<std::size_t> limit)
{ {
Wt::Dbo::collection<Track::pointer> res = session.find<Track>() session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res = session.getDboSession().find<Track>()
.where("file_added > ?").bind(after) .where("file_added > ?").bind(after)
.orderBy("file_added DESC") .orderBy("file_added DESC")
.limit(limit); .limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<pointer>(res.begin(), res.end()); return std::vector<pointer>(res.begin(), res.end());
} }
std::vector<Track::pointer> std::vector<Track::pointer>
Track::getAllWithMBIDAndMissingFeatures(Wt::Dbo::Session& session) Track::getAllWithMBIDAndMissingFeatures(Session& session)
{ {
Wt::Dbo::collection<pointer> res = session.query<pointer> session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<pointer>
("SELECT t FROM track t") ("SELECT t FROM track t")
.where("LENGTH(t.mbid) > 0") .where("LENGTH(t.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)");
@@ -125,14 +151,14 @@ Track::getAllWithMBIDAndMissingFeatures(Wt::Dbo::Session& session)
} }
std::vector<IdType> std::vector<IdType>
Track::getAllIdsWithFeatures(Wt::Dbo::Session& session, boost::optional<std::size_t> limit) Track::getAllIdsWithFeatures(Session& session, boost::optional<std::size_t> limit)
{ {
int size {limit ? static_cast<int>(*limit) : -1}; session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.query<IdType> Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
("SELECT t.id FROM track t") ("SELECT t.id FROM track t")
.where("EXISTS (SELECT * from track_features t_f WHERE t_f.track_id = t.id)") .where("EXISTS (SELECT * from track_features t_f WHERE t_f.track_id = t.id)")
.limit(size); .limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end()); return std::vector<IdType>(res.begin(), res.end());
} }
@@ -153,10 +179,12 @@ Track::hasTrackFeatures() const
static static
Wt::Dbo::Query< Track::pointer > Wt::Dbo::Query< Track::pointer >
getQuery(Wt::Dbo::Session& session, getQuery(Session& session,
const std::set<IdType>& clusterIds, const std::set<IdType>& clusterIds,
const std::vector<std::string> keywords) const std::vector<std::string>& keywords)
{ {
session.checkSharedLocked();
WhereClause where; WhereClause where;
std::ostringstream oss; std::ostringstream oss;
@@ -184,7 +212,7 @@ getQuery(Wt::Dbo::Session& session,
oss << " ORDER BY t.name COLLATE NOCASE"; oss << " ORDER BY t.name COLLATE NOCASE";
Wt::Dbo::Query<Track::pointer> query = session.query<Track::pointer>( oss.str() ); Wt::Dbo::Query<Track::pointer> query = session.getDboSession().query<Track::pointer>( oss.str() );
for (const std::string& bindArg : where.getBindArgs()) for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg); query.bind(bindArg);
@@ -193,13 +221,15 @@ getQuery(Wt::Dbo::Session& session,
} }
std::vector<Track::pointer> std::vector<Track::pointer>
Track::getByFilter(Wt::Dbo::Session& session, Track::getByFilter(Session& session,
const std::set<IdType>& clusterIds, const std::set<IdType>& clusterIds,
const std::vector<std::string> keywords, const std::vector<std::string>& keywords,
boost::optional<std::size_t> offset, boost::optional<std::size_t> offset,
boost::optional<std::size_t> size, boost::optional<std::size_t> size,
bool& moreResults) bool& moreResults)
{ {
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords) Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords)
.limit(size ? static_cast<int>(*size) + 1 : -1) .limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1); .offset(offset ? static_cast<int>(*offset) : -1);
@@ -218,16 +248,18 @@ Track::getByFilter(Wt::Dbo::Session& session,
} }
std::vector<Track::pointer> std::vector<Track::pointer>
Track::getByFilter(Wt::Dbo::Session& session, Track::getByFilter(Session& session,
const std::set<IdType>& clusters) const std::set<IdType>& clusters)
{ {
session.checkSharedLocked();
bool moreResults; bool moreResults;
return getByFilter(session, return getByFilter(session,
clusters, clusters,
std::vector<std::string> {}, {},
boost::optional<std::size_t> {}, {},
boost::optional<std::size_t> {}, {},
moreResults); moreResults);
} }
+15 -15
View File
@@ -53,29 +53,29 @@ class Track : public Wt::Dbo::Dbo<Track>
Track(const boost::filesystem::path& p); Track(const boost::filesystem::path& p);
// Find utility functions // Find utility functions
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p); static pointer getByPath(Session& session, const boost::filesystem::path& p);
static pointer getById(Wt::Dbo::Session& session, IdType id); static pointer getById(Session& session, IdType id);
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); static pointer getByMBID(Session& session, const std::string& MBID);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters); // tracks that belong to these clusters const std::set<IdType>& clusters); // tracks that belong to these clusters
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // tracks that belong to these clusters const std::set<IdType>& clusters, // tracks that belong to these clusters
const std::vector<std::string> keywords, // name must match all of these keywords const std::vector<std::string>& keywords, // name must match all of these keywords
boost::optional<std::size_t> offset, boost::optional<std::size_t> offset,
boost::optional<std::size_t> size, boost::optional<std::size_t> size,
bool& moreExpected); bool& moreExpected);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {}); static std::vector<pointer> getAll(Session& session, boost::optional<std::size_t> limit = {});
static std::vector<pointer> getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {}); static std::vector<pointer> getAllRandom(Session& session, boost::optional<std::size_t> limit = {});
static std::vector<IdType> getAllIds(Wt::Dbo::Session& session); // nested transaction static std::vector<IdType> getAllIds(Session& session); // nested transaction
static std::vector<boost::filesystem::path> getAllPaths(Wt::Dbo::Session& session); // nested transaction static std::vector<boost::filesystem::path> getAllPaths(Session& session); // nested transaction
static std::vector<pointer> getMBIDDuplicates(Wt::Dbo::Session& session); static std::vector<pointer> getMBIDDuplicates(Session& session);
static std::vector<pointer> getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int size = 1); static std::vector<pointer> getLastAdded(Session& session, const Wt::WDateTime& after, boost::optional<std::size_t> size = 1);
static std::vector<pointer> getAllWithMBIDAndMissingFeatures(Wt::Dbo::Session& session); // nested transaction static std::vector<pointer> getAllWithMBIDAndMissingFeatures(Session& session);
static std::vector<IdType> getAllIdsWithFeatures(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {}); // nested transaction static std::vector<IdType> getAllIdsWithFeatures(Session& session, boost::optional<std::size_t> limit = {});
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p); static pointer create(Session& session, const boost::filesystem::path& p);
// Accessors // Accessors
void setScanVersion(std::size_t version) { _scanVersion = version; } void setScanVersion(std::size_t version) { _scanVersion = version; }
+8 -2
View File
@@ -20,6 +20,7 @@
#include "TrackArtistLink.hpp" #include "TrackArtistLink.hpp"
#include "Artist.hpp" #include "Artist.hpp"
#include "Session.hpp"
#include "Track.hpp" #include "Track.hpp"
namespace Database { namespace Database {
@@ -32,9 +33,14 @@ _artist {artist}
} }
TrackArtistLink::pointer TrackArtistLink::pointer
TrackArtistLink::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist,Type type) TrackArtistLink::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist,Type type)
{ {
return session.add(std::make_unique<TrackArtistLink>(track, artist, type)); session.checkUniqueLocked();
TrackArtistLink::pointer res {session.getDboSession().add(std::make_unique<TrackArtistLink>(track, artist, type))};
session.getDboSession().flush();
return res;
} }
} }
+2 -1
View File
@@ -26,6 +26,7 @@
namespace Database { namespace Database {
class Artist; class Artist;
class Session;
class Track; class Track;
class TrackArtistLink class TrackArtistLink
@@ -51,7 +52,7 @@ class TrackArtistLink
TrackArtistLink() = default; TrackArtistLink() = default;
TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, Type type); TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, Type type);
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist,Type type); static pointer create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist,Type type);
Wt::Dbo::ptr<Track> getTrack() const { return _track; } Wt::Dbo::ptr<Track> getTrack() const { return _track; }
Wt::Dbo::ptr<Artist> getArtist() const { return _artist; } Wt::Dbo::ptr<Artist> getArtist() const { return _artist; }
+4 -2
View File
@@ -23,6 +23,7 @@
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "Session.hpp"
#include "Track.hpp" #include "Track.hpp"
namespace Database { namespace Database {
@@ -34,9 +35,10 @@ _track(track)
} }
TrackFeatures::pointer TrackFeatures::pointer
TrackFeatures::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures) TrackFeatures::create(Session& session, Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures)
{ {
return session.add(std::make_unique<TrackFeatures>(track, jsonEncodedFeatures)); session.checkUniqueLocked();
return session.getDboSession().add(std::make_unique<TrackFeatures>(track, jsonEncodedFeatures));
} }
std::vector<double> std::vector<double>
+2 -1
View File
@@ -27,6 +27,7 @@
namespace Database { namespace Database {
class Session;
class Track; class Track;
class TrackFeatures : public Wt::Dbo::Dbo<TrackFeatures> class TrackFeatures : public Wt::Dbo::Dbo<TrackFeatures>
@@ -39,7 +40,7 @@ class TrackFeatures : public Wt::Dbo::Dbo<TrackFeatures>
TrackFeatures(Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures); TrackFeatures(Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures);
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures); static pointer create(Session& session, Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures);
std::vector<double> getFeatures(const std::string& featureNode) const; std::vector<double> getFeatures(const std::string& featureNode) const;
bool getFeatures(std::map<std::string /*featureNode*/, std::vector<double> /*values*/>& featureNodes) const; bool getFeatures(std::map<std::string /*featureNode*/, std::vector<double> /*values*/>& featureNodes) const;
+30 -46
View File
@@ -26,6 +26,7 @@
#include "Artist.hpp" #include "Artist.hpp"
#include "Cluster.hpp" #include "Cluster.hpp"
#include "Release.hpp" #include "Release.hpp"
#include "Session.hpp"
#include "User.hpp" #include "User.hpp"
#include "Track.hpp" #include "Track.hpp"
@@ -41,38 +42,35 @@ TrackList::TrackList(const std::string& name, Type type, bool isPublic, Wt::Dbo:
} }
TrackList::pointer TrackList::pointer
TrackList::create(Wt::Dbo::Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user) TrackList::create(Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user)
{ {
session.checkUniqueLocked();
assert(user); assert(user);
auto res = session.add( std::make_unique<TrackList>(name, type, isPublic, user) ); auto res = session.getDboSession().add( std::make_unique<TrackList>(name, type, isPublic, user) );
session.flush(); session.getDboSession().flush();
return res; return res;
} }
TrackListEntry::pointer
TrackList::add(IdType trackId)
{
assert(session());
assert(self());
return TrackListEntry::create(*session(), Database::Track::getById(*session(), trackId), self());
}
TrackList::pointer TrackList::pointer
TrackList::get(Wt::Dbo::Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user) TrackList::get(Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user)
{ {
return session.find<TrackList>() session.checkSharedLocked();
assert(user);
return session.getDboSession().find<TrackList>()
.where("name = ?").bind(name) .where("name = ?").bind(name)
.where("type = ?").bind(type) .where("type = ?").bind(type)
.where("user_id = ?").bind(user.id()); .where("user_id = ?").bind(user.id());
} }
std::vector<TrackList::pointer> std::vector<TrackList::pointer>
TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user) TrackList::getAll(Session& session, Wt::Dbo::ptr<User> user)
{ {
Wt::Dbo::collection<TrackList::pointer> res = session.find<TrackList>() session.checkSharedLocked();
Wt::Dbo::collection<TrackList::pointer> res = session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user.id()) .where("user_id = ?").bind(user.id())
.orderBy("name COLLATE NOCASE"); .orderBy("name COLLATE NOCASE");
@@ -80,9 +78,11 @@ TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user)
} }
std::vector<TrackList::pointer> std::vector<TrackList::pointer>
TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user, Type type) TrackList::getAll(Session& session, Wt::Dbo::ptr<User> user, Type type)
{ {
Wt::Dbo::collection<TrackList::pointer> res = session.find<TrackList>() session.checkSharedLocked();
Wt::Dbo::collection<TrackList::pointer> res = session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user.id()) .where("user_id = ?").bind(user.id())
.where("type = ?").bind(type) .where("type = ?").bind(type)
.orderBy("name COLLATE NOCASE"); .orderBy("name COLLATE NOCASE");
@@ -91,9 +91,11 @@ TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user, Type type)
} }
TrackList::pointer TrackList::pointer
TrackList::getById(Wt::Dbo::Session& session, IdType id) TrackList::getById(Session& session, IdType id)
{ {
return session.find<TrackList>().where("id = ?").bind(id); session.checkSharedLocked();
return session.getDboSession().find<TrackList>().where("id = ?").bind(id);
} }
@@ -200,23 +202,6 @@ TrackList::getDuration() const
return query.resultValue(); return query.resultValue();
} }
void
TrackList::shuffle()
{
assert(session());
auto entries = getEntries();
auto now = std::chrono::system_clock::now();
std::mt19937 randGenerator(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
std::shuffle(entries.begin(), entries.end(), randGenerator);
clear();
for (auto entry : entries)
TrackListEntry::create(*session(), entry->getTrack(), self());
}
std::vector<Artist::pointer> std::vector<Artist::pointer>
TrackList::getTopArtists(std::size_t limit) const TrackList::getTopArtists(std::size_t limit) const
{ {
@@ -269,26 +254,25 @@ TrackListEntry::TrackListEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList
} }
TrackListEntry::TrackListEntry()
{
}
TrackListEntry::pointer TrackListEntry::pointer
TrackListEntry::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist) TrackListEntry::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist)
{ {
session.checkUniqueLocked();
assert(track); assert(track);
assert(tracklist); assert(tracklist);
auto res = session.add( std::make_unique<TrackListEntry>( track, tracklist) ); auto res = session.getDboSession().add( std::make_unique<TrackListEntry>( track, tracklist) );
session.flush(); session.getDboSession().flush();
return res; return res;
} }
TrackListEntry::pointer TrackListEntry::pointer
TrackListEntry::getById(Wt::Dbo::Session& session, IdType id) TrackListEntry::getById(Session& session, IdType id)
{ {
return session.find<TrackListEntry>().where("id = ?").bind(id); session.checkSharedLocked();
return session.getDboSession().find<TrackListEntry>().where("id = ?").bind(id);
} }
} // namespace Database } // namespace Database
+11 -12
View File
@@ -30,11 +30,12 @@
namespace Database { namespace Database {
class Artist; class Artist;
class Cluster;
class Release; class Release;
class User; class Session;
class Track; class Track;
class TrackListEntry; class TrackListEntry;
class Cluster; class User;
class TrackList : public Wt::Dbo::Dbo<TrackList> class TrackList : public Wt::Dbo::Dbo<TrackList>
{ {
@@ -56,13 +57,13 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(std::size_t limit = 1) const; std::vector<Wt::Dbo::ptr<Track>> getTopTracks(std::size_t limit = 1) const;
// Search utility // Search utility
static pointer get(Wt::Dbo::Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user); static pointer get(Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user);
static pointer getById(Wt::Dbo::Session& session, IdType tracklistId); static pointer getById(Session& session, IdType tracklistId);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user); static std::vector<pointer> getAll(Session& session, Wt::Dbo::ptr<User> user);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user, Type type); static std::vector<pointer> getAll(Session& session, Wt::Dbo::ptr<User> user, Type type);
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user); static pointer create(Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
// Accessors // Accessors
std::string getName() const { return _name; } std::string getName() const { return _name; }
@@ -73,9 +74,7 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
// Modifiers // Modifiers
void setName(const std::string& name) { _name = name; } void setName(const std::string& name) { _name = name; }
void setIsPublic(bool isPublic) { _isPublic = isPublic; } void setIsPublic(bool isPublic) { _isPublic = isPublic; }
Wt::Dbo::ptr<TrackListEntry> add(IdType trackId);
void clear() { _entries.clear(); } void clear() { _entries.clear(); }
void shuffle();
// Get tracks, ordered by position // Get tracks, ordered by position
std::size_t getCount() const; std::size_t getCount() const;
@@ -120,13 +119,13 @@ class TrackListEntry : public Wt::Dbo::Dbo<TrackListEntry>
using pointer = Wt::Dbo::ptr<TrackListEntry>; using pointer = Wt::Dbo::ptr<TrackListEntry>;
TrackListEntry(); TrackListEntry() = default;
TrackListEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist); TrackListEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist);
static pointer getById(Wt::Dbo::Session& session, IdType id); static pointer getById(Session& session, IdType id);
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist); static pointer create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist);
// Accessors // Accessors
Wt::Dbo::ptr<Track> getTrack() const { return _track; } Wt::Dbo::ptr<Track> getTrack() const { return _track; }
+1 -1
View File
@@ -19,7 +19,7 @@
#pragma once #pragma once
#include <Wt/Dbo/Dbo.h> #include <Wt/Dbo/ptr.h>
namespace Database { namespace Database {
using IdType = Wt::Dbo::dbo_default_traits::IdType; using IdType = Wt::Dbo::dbo_default_traits::IdType;
+31 -28
View File
@@ -21,11 +21,15 @@
#include "Artist.hpp" #include "Artist.hpp"
#include "Release.hpp" #include "Release.hpp"
#include "Session.hpp"
#include "Track.hpp" #include "Track.hpp"
#include "TrackList.hpp" #include "TrackList.hpp"
namespace Database { namespace Database {
static const std::string playedListName {"__played_tracks__"};
static const std::string queuedListName {"__queued_tracks__"};
const std::set<Bitrate> const std::set<Bitrate>
User::audioTranscodeAllowedBitrates = User::audioTranscodeAllowedBitrates =
{ {
@@ -43,29 +47,42 @@ User::User()
} }
std::vector<User::pointer> std::vector<User::pointer>
User::getAll(Wt::Dbo::Session& session) User::getAll(Session& session)
{ {
Wt::Dbo::collection<pointer> res = session.find<User>(); session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<User>();
return std::vector<pointer>(res.begin(), res.end()); return std::vector<pointer>(res.begin(), res.end());
} }
User::pointer User::pointer
User::getDemo(Wt::Dbo::Session& session) User::getDemo(Session& session)
{ {
pointer res = session.find<User>().where("type = ?").bind(Type::DEMO); session.checkSharedLocked();
pointer res = session.getDboSession().find<User>().where("type = ?").bind(Type::DEMO);
return res; return res;
} }
User::pointer User::pointer
User::create(Wt::Dbo::Session& session) User::create(Session& session)
{ {
return session.add(std::make_unique<User>()); session.checkUniqueLocked();
User::pointer user {session.getDboSession().add(std::make_unique<User>())};
TrackList::create(session, playedListName, TrackList::Type::Internal, false, user);
TrackList::create(session, queuedListName, TrackList::Type::Internal, false, user);
session.getDboSession().flush();
return user;
} }
User::pointer User::pointer
User::getById(Wt::Dbo::Session& session, IdType id) User::getById(Session& session, IdType id)
{ {
return session.find<User>().where("id = ?").bind( id ); return session.getDboSession().find<User>().where("id = ?").bind( id );
} }
void void
@@ -95,35 +112,21 @@ User::getMaxAudioTranscodeBitrate(void) const
} }
Wt::Dbo::ptr<TrackList> Wt::Dbo::ptr<TrackList>
User::getPlayedTrackList() const User::getPlayedTrackList(Session& session) const
{ {
static const std::string listName = "__played_tracks__";
assert(self()); assert(self());
assert(IdIsValid(self()->id())); session.checkSharedLocked();
assert(session());
auto res = TrackList::get(*session(), listName, TrackList::Type::Internal, self()); return TrackList::get(session, playedListName, TrackList::Type::Internal, self());
if (!res)
res = TrackList::create(*session(), listName, TrackList::Type::Internal, false, self());
return res;
} }
Wt::Dbo::ptr<TrackList> Wt::Dbo::ptr<TrackList>
User::getQueuedTrackList() const User::getQueuedTrackList(Session& session) const
{ {
static const std::string listName = "__queued_tracks__";
assert(self()); assert(self());
assert(IdIsValid(self()->id())); session.checkSharedLocked();
assert(session());
auto res = TrackList::get(*session(), listName, TrackList::Type::Internal, self()); return TrackList::get(session, queuedListName, TrackList::Type::Internal, self());
if (!res)
res = TrackList::create(*session(), listName, TrackList::Type::Internal, false, self());
return res;
} }
void void
+8 -9
View File
@@ -22,17 +22,15 @@
#include <vector> #include <vector>
#include <Wt/Dbo/Dbo.h> #include <Wt/Dbo/Dbo.h>
#include <Wt/Auth/Dbo/AuthInfo.h>
#include "Types.hpp" #include "Types.hpp"
namespace Database { namespace Database {
class User;
using AuthInfo = Wt::Auth::Dbo::AuthInfo<User>;
class Artist; class Artist;
class Release; class Release;
class Session;
class TrackList; class TrackList;
class Track; class Track;
@@ -69,12 +67,13 @@ class User : public Wt::Dbo::Dbo<User>
User(); User();
// utility // utility
static pointer create(Wt::Dbo::Session& session); static pointer create(Session& session);
// accessors // accessors
static pointer getById(Wt::Dbo::Session& session, IdType id); static pointer getById(Session& session, IdType id);
static std::vector<pointer> getAll(Wt::Dbo::Session& session); static pointer getByLoginName(const std::string& loginName);
static pointer getDemo(Wt::Dbo::Session& session); static std::vector<pointer> getAll(Session& session);
static pointer getDemo(Session& session);
// write // write
void setType(Type type) { _type = type; } void setType(Type type) { _type = type; }
@@ -97,8 +96,8 @@ class User : public Wt::Dbo::Dbo<User>
bool isRepeatAllSet() const { return _repeatAll; } bool isRepeatAllSet() const { return _repeatAll; }
bool isRadioSet() const { return _radio; } bool isRadioSet() const { return _radio; }
Wt::Dbo::ptr<TrackList> getQueuedTrackList() const; Wt::Dbo::ptr<TrackList> getPlayedTrackList(Session& session) const;
Wt::Dbo::ptr<TrackList> getPlayedTrackList() const; Wt::Dbo::ptr<TrackList> getQueuedTrackList(Session& session) const;
void starArtist(Wt::Dbo::ptr<Artist> artist); void starArtist(Wt::Dbo::ptr<Artist> artist);
void unstarArtist(Wt::Dbo::ptr<Artist> artist); void unstarArtist(Wt::Dbo::ptr<Artist> artist);
+6 -6
View File
@@ -121,17 +121,17 @@ int main(int argc, char* argv[])
Image::init(argv[0]); Image::init(argv[0]);
Av::AvInit(); Av::AvInit();
Av::Transcoder::init(); Av::Transcoder::init();
Database::Handler::configureAuth(); Database::Session::configureAuth();
// Initializing a connection pool to the database that will be shared along services // Initializing a connection pool to the database that will be shared along services
auto connectionPool = Database::Handler::createConnectionPool(Config::instance().getPath("working-dir") / "lms.db"); Database::Database database {Config::instance().getPath("working-dir") / "lms.db"};
UserInterface::LmsApplicationGroupContainer appGroups; UserInterface::LmsApplicationGroupContainer appGroups;
// Service initialization order is important // Service initialization order is important
Scanner::MediaScanner& mediaScanner {ServiceProvider<Scanner::MediaScanner>::create(*connectionPool)}; Scanner::MediaScanner& mediaScanner {ServiceProvider<Scanner::MediaScanner>::create(database.createSession())};
Similarity::FeaturesScannerAddon similarityFeaturesScannerAddon(*connectionPool); Similarity::FeaturesScannerAddon similarityFeaturesScannerAddon {database.createSession()};
mediaScanner.setAddon(similarityFeaturesScannerAddon); mediaScanner.setAddon(similarityFeaturesScannerAddon);
@@ -140,7 +140,7 @@ int main(int argc, char* argv[])
ServiceProvider<Similarity::Searcher>::create(similarityFeaturesScannerAddon); ServiceProvider<Similarity::Searcher>::create(similarityFeaturesScannerAddon);
API::Subsonic::SubsonicResource subsonicResource {*connectionPool}; API::Subsonic::SubsonicResource subsonicResource {database};
// bind API resources // bind API resources
if (Config::instance().getBool("api-subsonic", true)) if (Config::instance().getBool("api-subsonic", true))
@@ -152,7 +152,7 @@ int main(int argc, char* argv[])
// bind UI entry point // bind UI entry point
server.addEntryPoint(Wt::EntryPointType::Application, server.addEntryPoint(Wt::EntryPointType::Application,
std::bind(UserInterface::LmsApplication::create, std::bind(UserInterface::LmsApplication::create,
std::placeholders::_1, std::ref(*connectionPool), std::ref(appGroups))); std::placeholders::_1, std::ref(database), std::ref(appGroups)));
// Start // Start
LMS_LOG(MAIN, INFO) << "Starting media scanner..."; LMS_LOG(MAIN, INFO) << "Starting media scanner...";
+42 -50
View File
@@ -85,7 +85,7 @@ isPathInParentPath(const boost::filesystem::path& path, const boost::filesystem:
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
getOrCreateArtists(Wt::Dbo::Session& session, const std::vector<MetaData::Artist>& artistsInfo) getOrCreateArtists(Session& session, const std::vector<MetaData::Artist>& artistsInfo)
{ {
std::vector<Artist::pointer> artists; std::vector<Artist::pointer> artists;
@@ -129,7 +129,7 @@ getOrCreateArtists(Wt::Dbo::Session& session, const std::vector<MetaData::Artist
} }
Release::pointer Release::pointer
getOrCreateRelease(Wt::Dbo::Session& session, const MetaData::Album& album) getOrCreateRelease(Session& session, const MetaData::Album& album)
{ {
Release::pointer release; Release::pointer release;
@@ -166,7 +166,7 @@ getOrCreateRelease(Wt::Dbo::Session& session, const MetaData::Album& album)
} }
std::vector<Cluster::pointer> std::vector<Cluster::pointer>
getOrCreateClusters(Wt::Dbo::Session& session, const MetaData::Clusters& clustersNames) getOrCreateClusters(Session& session, const MetaData::Clusters& clustersNames)
{ {
std::vector< Cluster::pointer > clusters; std::vector< Cluster::pointer > clusters;
@@ -193,8 +193,8 @@ getOrCreateClusters(Wt::Dbo::Session& session, const MetaData::Clusters& cluster
namespace Scanner { namespace Scanner {
MediaScanner::MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool) MediaScanner::MediaScanner(std::unique_ptr<Database::Session> dbSession)
: _db {connectionPool} : _dbSession {std::move(dbSession)}
{ {
_ioService.setThreadCount(1); _ioService.setThreadCount(1);
@@ -443,16 +443,17 @@ MediaScanner::scan(boost::system::error_code err)
} }
LMS_LOG(DBUPDATER, INFO) << "Optimizing db..."; LMS_LOG(DBUPDATER, INFO) << "Optimizing db...";
_db.optimize(); _dbSession->optimize();
LMS_LOG(DBUPDATER, INFO) << "Optimize db done!"; LMS_LOG(DBUPDATER, INFO) << "Optimize db done!";
} }
void void
MediaScanner::refreshScanSettings() MediaScanner::refreshScanSettings()
{ {
Wt::Dbo::Transaction transaction(_db.getSession()); {
auto transaction {_dbSession->createSharedTransaction()};
auto scanSettings = ScanSettings::get(_db.getSession()); ScanSettings::pointer scanSettings {ScanSettings::get(*_dbSession)};
LMS_LOG(DBUPDATER, INFO) << "Using scan settings version " << scanSettings->getScanVersion(); LMS_LOG(DBUPDATER, INFO) << "Using scan settings version " << scanSettings->getScanVersion();
@@ -466,13 +467,12 @@ MediaScanner::refreshScanSettings()
auto clusterTypes = scanSettings->getClusterTypes(); auto clusterTypes = scanSettings->getClusterTypes();
std::set<std::string> clusterTypeNames; std::set<std::string> clusterTypeNames;
std::transform(clusterTypes.begin(), clusterTypes.end(), std::transform(std::cbegin(clusterTypes), std::cend(clusterTypes),
std::inserter(clusterTypeNames, clusterTypeNames.begin()), std::inserter(clusterTypeNames, clusterTypeNames.begin()),
[](ClusterType::pointer clusterType) -> std::string { return clusterType->getName(); }); [](ClusterType::pointer clusterType) { return clusterType->getName(); });
_metadataParser.setClusterTypeNames(clusterTypeNames); _metadataParser.setClusterTypeNames(clusterTypeNames);
}
transaction.commit();
for (auto& addon : _addons) for (auto& addon : _addons)
addon->refreshSettings(); addon->refreshSettings();
@@ -506,9 +506,9 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
if (!forceScan) if (!forceScan)
{ {
// Skip file if last write is the same // Skip file if last write is the same
Wt::Dbo::Transaction transaction(_db.getSession()); auto transaction {_dbSession->createSharedTransaction()};
Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file); Track::pointer track {Track::getByPath(*_dbSession, file)};
if (track && track->getLastWriteTime() == lastWriteTime && track->getScanVersion() == _scanVersion) if (track && track->getLastWriteTime() == lastWriteTime && track->getScanVersion() == _scanVersion)
{ {
@@ -526,9 +526,9 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
stats.scans++; stats.scans++;
Wt::Dbo::Transaction transaction {_db.getSession()}; auto uniqueTransaction {_dbSession->createUniqueTransaction()};
Wt::Dbo::ptr<Track> track {Track::getByPath(_db.getSession(), file) }; Track::pointer track {Track::getByPath(*_dbSession, file) };
// We estimate this is an audio file if: // We estimate this is an audio file if:
// - we found a least one audio stream // - we found a least one audio stream
@@ -572,29 +572,27 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
} }
// ***** Clusters // ***** Clusters
std::vector<Cluster::pointer> clusters {getOrCreateClusters(_db.getSession(), trackInfo->clusters)}; std::vector<Cluster::pointer> clusters {getOrCreateClusters(*_dbSession, trackInfo->clusters)};
// ***** Artists // ***** Artists
std::vector<Artist::pointer> artists {getOrCreateArtists(_db.getSession(), trackInfo->artists)}; std::vector<Artist::pointer> artists {getOrCreateArtists(*_dbSession, trackInfo->artists)};
// ***** Release artists // ***** Release artists
std::vector<Artist::pointer> releaseArtists {getOrCreateArtists(_db.getSession(), trackInfo->albumArtists)}; std::vector<Artist::pointer> releaseArtists {getOrCreateArtists(*_dbSession, trackInfo->albumArtists)};
// ***** Release // ***** Release
Release::pointer release; Release::pointer release;
if (trackInfo->album) if (trackInfo->album)
release = getOrCreateRelease(_db.getSession(), *trackInfo->album); release = getOrCreateRelease(*_dbSession, *trackInfo->album);
// If file already exist, update data // If file already exist, update data
// Otherwise, create it // Otherwise, create it
bool trackAdded {false};
if (!track) if (!track)
{ {
// Create a new song // Create a new song
track = Track::create(_db.getSession(), file); track = Track::create(*_dbSession, file);
LMS_LOG(DBUPDATER, INFO) << "Adding '" << file.string() << "'"; LMS_LOG(DBUPDATER, INFO) << "Adding '" << file.string() << "'";
stats.additions++; stats.additions++;
trackAdded = true;
} }
else else
{ {
@@ -615,10 +613,10 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
track.modify()->clearArtistLinks(); track.modify()->clearArtistLinks();
for (const auto& artist : artists) for (const auto& artist : artists)
track.modify()->addArtistLink(Database::TrackArtistLink::create(_db.getSession(), track, artist, Database::TrackArtistLink::Type::Artist)); track.modify()->addArtistLink(Database::TrackArtistLink::create(*_dbSession, track, artist, Database::TrackArtistLink::Type::Artist));
for (const auto& releaseArtist : releaseArtists) for (const auto& releaseArtist : releaseArtists)
track.modify()->addArtistLink(Database::TrackArtistLink::create(_db.getSession(), track, releaseArtist, Database::TrackArtistLink::Type::ReleaseArtist)); track.modify()->addArtistLink(Database::TrackArtistLink::create(*_dbSession, track, releaseArtist, Database::TrackArtistLink::Type::ReleaseArtist));
track.modify()->setScanVersion(_scanVersion); track.modify()->setScanVersion(_scanVersion);
track.modify()->setRelease(release); track.modify()->setRelease(release);
@@ -640,16 +638,6 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
track.modify()->setHasCover(trackInfo->hasCover); track.modify()->setHasCover(trackInfo->hasCover);
track.modify()->setCopyright(trackInfo->copyright); track.modify()->setCopyright(trackInfo->copyright);
track.modify()->setCopyrightURL(trackInfo->copyrightURL); track.modify()->setCopyrightURL(trackInfo->copyrightURL);
transaction.commit();
for (auto& addon : _addons)
{
if (trackAdded)
addon->trackAdded(track.id());
else
addon->trackUpdated(track.id());
}
} }
void void
@@ -728,7 +716,11 @@ checkFile(const boost::filesystem::path& p, const boost::filesystem::path& media
void void
MediaScanner::removeMissingTracks(Stats& stats) MediaScanner::removeMissingTracks(Stats& stats)
{ {
std::vector<boost::filesystem::path> trackPaths = Track::getAllPaths(_db.getSession());; std::vector<boost::filesystem::path> trackPaths;
{
auto transaction {_dbSession->createSharedTransaction()};
trackPaths = Track::getAllPaths(*_dbSession);;
}
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks...";
for (const auto& trackPath : trackPaths) for (const auto& trackPath : trackPaths)
@@ -738,9 +730,9 @@ MediaScanner::removeMissingTracks(Stats& stats)
if (!checkFile(trackPath, _mediaDirectory, _fileExtensions)) if (!checkFile(trackPath, _mediaDirectory, _fileExtensions))
{ {
Wt::Dbo::Transaction transaction(_db.getSession()); auto transaction {_dbSession->createUniqueTransaction()};
Track::pointer track = Track::getByPath(_db.getSession(), trackPath); Track::pointer track {Track::getByPath(*_dbSession, trackPath)};
if (track) if (track)
{ {
track.remove(); track.remove();
@@ -755,11 +747,11 @@ MediaScanner::removeOrphanEntries()
{ {
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters...";
{ {
Wt::Dbo::Transaction transaction(_db.getSession()); auto transaction {_dbSession->createUniqueTransaction()};
// Now process orphan Cluster (no track) // Now process orphan Cluster (no track)
auto clusters = Cluster::getAllOrphans(_db.getSession()); auto clusters {Cluster::getAllOrphans(*_dbSession)};
for (auto cluster : clusters) for (auto& cluster : clusters)
{ {
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'";
cluster.remove(); cluster.remove();
@@ -768,10 +760,10 @@ MediaScanner::removeOrphanEntries()
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan artists..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan artists...";
{ {
Wt::Dbo::Transaction transaction(_db.getSession()); auto transaction {_dbSession->createUniqueTransaction()};
auto artists = Artist::getAllOrphans(_db.getSession()); auto artists {Artist::getAllOrphans(*_dbSession)};
for (auto artist : artists) for (auto& artist : artists)
{ {
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
artist.remove(); artist.remove();
@@ -780,10 +772,10 @@ MediaScanner::removeOrphanEntries()
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases...";
{ {
Wt::Dbo::Transaction transaction(_db.getSession()); auto transaction {_dbSession->createUniqueTransaction()};
auto releases = Release::getAllOrphans(_db.getSession()); auto releases {Release::getAllOrphans(*_dbSession)};
for (auto release : releases) for (auto& release : releases)
{ {
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'";
release.remove(); release.remove();
@@ -798,10 +790,10 @@ MediaScanner::checkDuplicatedAudioFiles(Stats& stats)
{ {
LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files"; LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files";
Wt::Dbo::Transaction transaction(_db.getSession()); auto transaction {_dbSession->createSharedTransaction()};
std::vector<Track::pointer> tracks = Database::Track::getMBIDDuplicates(_db.getSession()); const std::vector<Track::pointer> tracks = Database::Track::getMBIDDuplicates(*_dbSession);
for (Track::pointer track : tracks) for (const Track::pointer& track : tracks)
{ {
LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath().string() << " - " << track->getName(); LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath().string() << " - " << track->getName();
stats.duplicateMBID++; stats.duplicateMBID++;
+4 -3
View File
@@ -29,7 +29,7 @@
#include <boost/asio/system_timer.hpp> #include <boost/asio/system_timer.hpp>
#include "database/ScanSettings.hpp" #include "database/ScanSettings.hpp"
#include "database/DatabaseHandler.hpp" #include "database/Session.hpp"
#include "metadata/TagLibParser.hpp" #include "metadata/TagLibParser.hpp"
#include "MediaScannerAddon.hpp" #include "MediaScannerAddon.hpp"
@@ -39,7 +39,7 @@ namespace Scanner {
class MediaScanner class MediaScanner
{ {
public: public:
MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool); MediaScanner(std::unique_ptr<Database::Session> dbSession);
void setAddon(MediaScannerAddon& addon); void setAddon(MediaScannerAddon& addon);
@@ -120,6 +120,7 @@ class MediaScanner
void removeOrphanEntries(); void removeOrphanEntries();
void checkDuplicatedAudioFiles(Stats& stats); void checkDuplicatedAudioFiles(Stats& stats);
void scanAudioFile(const boost::filesystem::path& file, bool forceScan, Stats& stats); void scanAudioFile(const boost::filesystem::path& file, bool forceScan, Stats& stats);
Database::IdType doScanAudioFile(const boost::filesystem::path& file, Stats& stats);
void notifyInProgressIfNeeded(Stats& stats); void notifyInProgressIfNeeded(Stats& stats);
void notifyInProgress(Stats& stats); void notifyInProgress(Stats& stats);
@@ -130,7 +131,7 @@ class MediaScanner
Wt::Signal<Stats> _sigScanInProgress; Wt::Signal<Stats> _sigScanInProgress;
std::chrono::system_clock::time_point _lastScanInProgressEmit {}; std::chrono::system_clock::time_point _lastScanInProgressEmit {};
Wt::Signal<Wt::WDateTime> _sigScheduled; Wt::Signal<Wt::WDateTime> _sigScheduled;
Database::Handler _db; std::unique_ptr<Database::Session> _dbSession;
MetaData::TagLibParser _metadataParser; MetaData::TagLibParser _metadataParser;
std::vector<MediaScannerAddon*> _addons; std::vector<MediaScannerAddon*> _addons;
+2 -1
View File
@@ -29,11 +29,12 @@ class MediaScannerAddon
virtual void refreshSettings() = 0; virtual void refreshSettings() = 0;
virtual void requestStop() = 0; virtual void requestStop() = 0;
virtual void preScanComplete() = 0;
virtual void trackAdded(Database::IdType trackId) = 0; virtual void trackAdded(Database::IdType trackId) = 0;
virtual void trackToRemove(Database::IdType trackId) = 0; virtual void trackToRemove(Database::IdType trackId) = 0;
virtual void trackUpdated(Database::IdType trackId) = 0; virtual void trackUpdated(Database::IdType trackId) = 0;
virtual void preScanComplete() = 0;
}; };
} // ns Scanner } // ns Scanner
+12 -13
View File
@@ -31,17 +31,16 @@ Searcher::Searcher(FeaturesScannerAddon& somAddon)
{} {}
static static
Database::SimilaritySettings::EngineType getEngineType(Wt::Dbo::Session& session) Database::SimilaritySettings::EngineType getEngineType(Database::Session& dbSession)
{ {
Wt::Dbo::Transaction transaction{session}; auto transaction {dbSession.createSharedTransaction()};
return Database::SimilaritySettings::get(session)->getEngineType(); return Database::SimilaritySettings::get(dbSession)->getEngineType();
} }
std::vector<Database::IdType> std::vector<Database::IdType>
Searcher::getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::IdType>& trackIds, std::size_t maxCount) Searcher::getSimilarTracks(Database::Session& dbSession, const std::set<Database::IdType>& trackIds, std::size_t maxCount)
{ {
auto engineType {getEngineType(dbSession)};
auto engineType {getEngineType(session)};
auto somSearcher {_somAddon.getSearcher()}; auto somSearcher {_somAddon.getSearcher()};
if (engineType == Database::SimilaritySettings::EngineType::Features if (engineType == Database::SimilaritySettings::EngineType::Features
@@ -51,13 +50,13 @@ Searcher::getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::I
return somSearcher->getSimilarTracks(trackIds, maxCount); return somSearcher->getSimilarTracks(trackIds, maxCount);
} }
else else
return ClusterSearcher::getSimilarTracks(session, trackIds, maxCount); return ClusterSearcher::getSimilarTracks(dbSession, trackIds, maxCount);
} }
std::vector<Database::IdType> std::vector<Database::IdType>
Searcher::getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t maxCount) Searcher::getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount)
{ {
auto engineType {getEngineType(session)}; auto engineType {getEngineType(dbSession)};
auto somSearcher {_somAddon.getSearcher()}; auto somSearcher {_somAddon.getSearcher()};
if (engineType == Database::SimilaritySettings::EngineType::Features if (engineType == Database::SimilaritySettings::EngineType::Features
@@ -67,13 +66,13 @@ Searcher::getSimilarReleases(Wt::Dbo::Session& session, Database::IdType release
return somSearcher->getSimilarReleases(releaseId, maxCount); return somSearcher->getSimilarReleases(releaseId, maxCount);
} }
else else
return ClusterSearcher::getSimilarReleases(session, releaseId, maxCount); return ClusterSearcher::getSimilarReleases(dbSession, releaseId, maxCount);
} }
std::vector<Database::IdType> std::vector<Database::IdType>
Searcher::getSimilarArtists(Wt::Dbo::Session& session, Database::IdType artistId, std::size_t maxCount) Searcher::getSimilarArtists(Database::Session& dbSession, Database::IdType artistId, std::size_t maxCount)
{ {
auto engineType {getEngineType(session)}; auto engineType {getEngineType(dbSession)};
auto somSearcher {_somAddon.getSearcher()}; auto somSearcher {_somAddon.getSearcher()};
if (engineType == Database::SimilaritySettings::EngineType::Features if (engineType == Database::SimilaritySettings::EngineType::Features
@@ -83,7 +82,7 @@ Searcher::getSimilarArtists(Wt::Dbo::Session& session, Database::IdType artistId
return somSearcher->getSimilarArtists(artistId, maxCount); return somSearcher->getSimilarArtists(artistId, maxCount);
} }
else else
return ClusterSearcher::getSimilarArtists(session, artistId, maxCount); return ClusterSearcher::getSimilarArtists(dbSession, artistId, maxCount);
} }
} // ns Similarity } // ns Similarity
+8 -3
View File
@@ -24,6 +24,11 @@
#include "database/Types.hpp" #include "database/Types.hpp"
namespace Database
{
class Session;
}
namespace Similarity { namespace Similarity {
class FeaturesScannerAddon; class FeaturesScannerAddon;
@@ -34,9 +39,9 @@ class Searcher
Searcher(FeaturesScannerAddon& somAddon); Searcher(FeaturesScannerAddon& somAddon);
// Closest results first // Closest results first
std::vector<Database::IdType> getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::IdType>& tracksId, std::size_t maxCount); std::vector<Database::IdType> getSimilarTracks(Database::Session& session, const std::set<Database::IdType>& tracksId, std::size_t maxCount);
std::vector<Database::IdType> getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t maxCount); std::vector<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount);
std::vector<Database::IdType> getSimilarArtists(Wt::Dbo::Session& session, Database::IdType artistId, std::size_t maxCount); std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount);
private: private:
@@ -25,23 +25,21 @@
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "utils/Utils.hpp" #include "utils/Utils.hpp"
namespace Similarity { namespace Similarity {
namespace ClusterSearcher { namespace ClusterSearcher {
static
std::vector<Database::IdType> std::vector<Database::IdType>
getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::IdType>& trackIds, std::size_t maxCount) getSimilarTracksLocked(Database::Session& dbSession, const std::set<Database::IdType>& trackIds, std::size_t maxCount)
{ {
std::vector<Database::IdType> res;
Wt::Dbo::Transaction transaction(session);
std::vector<Database::IdType> clusterIds; std::vector<Database::IdType> clusterIds;
for (auto trackId : trackIds) for (auto trackId : trackIds)
{ {
auto track = Database::Track::getById(session, trackId); auto track {Database::Track::getById(dbSession, trackId)};
if (!track) if (!track)
continue; continue;
@@ -56,9 +54,10 @@ getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::IdType>& tr
std::vector<Database::IdType> sortedClusterIds; std::vector<Database::IdType> sortedClusterIds;
uniqueAndSortedByOccurence(clusterIds.begin(), clusterIds.end(), std::back_inserter(sortedClusterIds)); uniqueAndSortedByOccurence(clusterIds.begin(), clusterIds.end(), std::back_inserter(sortedClusterIds));
std::vector<Database::IdType> res;
for (auto clusterId : clusterIds) for (auto clusterId : clusterIds)
{ {
auto cluster = Database::Cluster::getById(session, clusterId); auto cluster {Database::Cluster::getById(dbSession, clusterId)};
if (!cluster) if (!cluster)
continue; continue;
@@ -88,13 +87,21 @@ getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::IdType>& tr
} }
std::vector<Database::IdType> std::vector<Database::IdType>
getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t maxCount) getSimilarTracks(Database::Session& dbSession, const std::set<Database::IdType>& trackIds, std::size_t maxCount)
{
auto transaction {dbSession.createSharedTransaction()};
return getSimilarTracksLocked(dbSession, trackIds, maxCount);
}
std::vector<Database::IdType>
getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount)
{ {
std::vector<Database::IdType> res; std::vector<Database::IdType> res;
Wt::Dbo::Transaction transaction(session); auto transaction {dbSession.createSharedTransaction()};
auto release = Database::Release::getById(session, releaseId); auto release {Database::Release::getById(dbSession, releaseId)};
if (!release) if (!release)
return res; return res;
@@ -104,11 +111,10 @@ getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::s
for (const auto& releaseTrack : releaseTracks) for (const auto& releaseTrack : releaseTracks)
releaseTrackIds.insert(releaseTrack.id()); releaseTrackIds.insert(releaseTrack.id());
auto trackIds = getSimilarTracks(session, releaseTrackIds, maxCount * 5); auto trackIds {getSimilarTracksLocked(dbSession, releaseTrackIds, maxCount * 5)};
for (auto trackId : trackIds) for (auto trackId : trackIds)
{ {
auto track = Database::Track::getById(session, trackId); auto track {Database::Track::getById(dbSession, trackId)};
if (!track) if (!track)
continue; continue;
@@ -129,31 +135,30 @@ getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::s
} }
std::vector<Database::IdType> std::vector<Database::IdType>
getSimilarArtists(Wt::Dbo::Session& session, Database::IdType artistId, std::size_t maxCount) getSimilarArtists(Database::Session& dbSession, Database::IdType artistId, std::size_t maxCount)
{ {
std::vector<Database::IdType> res; std::vector<Database::IdType> res;
Wt::Dbo::Transaction transaction(session); auto transaction {dbSession.createSharedTransaction()};
auto artist = Database::Artist::getById(session, artistId); auto artist {Database::Artist::getById(dbSession, artistId)};
if (!artist) if (!artist)
return res; return res;
auto artistTracks = artist->getTracks(); auto artistTracks {artist->getTracks()};
std::set<Database::IdType> artistTrackIds; std::set<Database::IdType> artistTrackIds;
for (const auto& artistTrack : artistTracks) for (const auto& artistTrack : artistTracks)
artistTrackIds.insert(artistTrack.id()); artistTrackIds.insert(artistTrack.id());
auto trackIds = getSimilarTracks(session, artistTrackIds, maxCount * 5); auto trackIds {getSimilarTracksLocked(dbSession, artistTrackIds, maxCount * 5)};
for (auto trackId : trackIds) for (auto trackId : trackIds)
{ {
auto track = Database::Track::getById(session, trackId); auto track {Database::Track::getById(dbSession, trackId)};
if (!track) if (!track)
continue; continue;
for (auto trackArtist : track->getArtists()) for (const auto& trackArtist : track->getArtists())
{ {
if (!trackArtist || trackArtist.id() == artistId) if (!trackArtist || trackArtist.id() == artistId)
continue; continue;
@@ -23,13 +23,17 @@
#include "database/Types.hpp" #include "database/Types.hpp"
namespace Database {
class Session;
}
namespace Similarity { namespace Similarity {
namespace ClusterSearcher namespace ClusterSearcher
{ {
std::vector<Database::IdType> getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::IdType>& tracksId, std::size_t maxCount); std::vector<Database::IdType> getSimilarTracks(Database::Session& session, const std::set<Database::IdType>& tracksId, std::size_t maxCount);
std::vector<Database::IdType> getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t maxCount); std::vector<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount);
std::vector<Database::IdType> getSimilarArtists(Wt::Dbo::Session& session, Database::IdType artistId, std::size_t maxCount); std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount);
}; };
} // namespace Similarity } // namespace Similarity
@@ -38,13 +38,13 @@ struct TrackInfo
}; };
std::vector<TrackInfo> std::vector<TrackInfo>
getTracksWithMBIDAndMissingFeatures(Wt::Dbo::Session& session) getTracksWithMBIDAndMissingFeatures(Database::Session& dbSession)
{ {
std::vector<TrackInfo> res; std::vector<TrackInfo> res;
Wt::Dbo::Transaction transaction {session}; auto transaction {dbSession.createSharedTransaction()};
auto tracks {Database::Track::getAllWithMBIDAndMissingFeatures(session)}; auto tracks {Database::Track::getAllWithMBIDAndMissingFeatures(dbSession)};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
res.push_back({track.id(), track->getMBID()}); res.push_back({track.id(), track->getMBID()});
@@ -53,13 +53,13 @@ getTracksWithMBIDAndMissingFeatures(Wt::Dbo::Session& session)
} // namespace } // namespace
FeaturesScannerAddon::FeaturesScannerAddon(Wt::Dbo::SqlConnectionPool& connectionPool) FeaturesScannerAddon::FeaturesScannerAddon(std::unique_ptr<Database::Session> dbSession)
: _db(connectionPool) : _dbSession {std::move(dbSession)}
{ {
boost::optional<Similarity::FeaturesCache> cache {Similarity::FeaturesCache::read()}; boost::optional<Similarity::FeaturesCache> cache {Similarity::FeaturesCache::read()};
if (cache) if (cache)
{ {
auto searcher {std::make_shared<Similarity::FeaturesSearcher>(_db.getSession(), *cache, [&]() { return _stopRequested; })}; auto searcher {std::make_shared<Similarity::FeaturesSearcher>(*_dbSession.get(), *cache, [&]() { return _stopRequested; })};
if (searcher->isValid()) if (searcher->isValid())
std::atomic_store(&_searcher, searcher); std::atomic_store(&_searcher, searcher);
} }
@@ -80,9 +80,9 @@ FeaturesScannerAddon::requestStop()
void void
FeaturesScannerAddon::trackUpdated(Database::IdType trackId) FeaturesScannerAddon::trackUpdated(Database::IdType trackId)
{ {
Wt::Dbo::Transaction transaction {_db.getSession()}; auto uniqueTransaction {_dbSession->createUniqueTransaction()};
auto track {Database::Track::getById(_db.getSession(), trackId)}; auto track {Database::Track::getById(*_dbSession, trackId)};
if (!track) if (!track)
return; return;
@@ -93,9 +93,9 @@ void
FeaturesScannerAddon::preScanComplete() FeaturesScannerAddon::preScanComplete()
{ {
{ {
Wt::Dbo::Transaction transaction {_db.getSession()}; auto transaction {_dbSession->createSharedTransaction()};
if (Database::SimilaritySettings::get(_db.getSession())->getEngineType() != Database::SimilaritySettings::EngineType::Features) if (Database::SimilaritySettings::get(*_dbSession)->getEngineType() != Database::SimilaritySettings::EngineType::Features)
{ {
LMS_LOG(DBUPDATER, INFO) << "Do not fetch features since the engine type does not make use of them"; LMS_LOG(DBUPDATER, INFO) << "Do not fetch features since the engine type does not make use of them";
return; return;
@@ -103,9 +103,12 @@ FeaturesScannerAddon::preScanComplete()
} }
LMS_LOG(DBUPDATER, DEBUG) << "Getting tracks with missing Features..."; LMS_LOG(DBUPDATER, DEBUG) << "Getting tracks with missing Features...";
std::vector<TrackInfo> tracksInfo {getTracksWithMBIDAndMissingFeatures(_db.getSession())}; const std::vector<TrackInfo> tracksInfo {getTracksWithMBIDAndMissingFeatures(*_dbSession)};
LMS_LOG(DBUPDATER, DEBUG) << "Getting tracks with missing Features DONE (found " << tracksInfo.size() << ")"; LMS_LOG(DBUPDATER, DEBUG) << "Getting tracks with missing Features DONE (found " << tracksInfo.size() << ")";
if (!tracksInfo.empty())
Similarity::FeaturesCache::invalidate();
for (const TrackInfo& trackInfo : tracksInfo) for (const TrackInfo& trackInfo : tracksInfo)
{ {
if (_stopRequested) if (_stopRequested)
@@ -114,7 +117,6 @@ FeaturesScannerAddon::preScanComplete()
fetchFeatures(trackInfo.id, trackInfo.mbid); fetchFeatures(trackInfo.id, trackInfo.mbid);
} }
Similarity::FeaturesCache::invalidate();
updateSearcher(); updateSearcher();
} }
@@ -125,8 +127,8 @@ FeaturesScannerAddon::updateSearcher()
std::vector<Database::IdType> trackIds; std::vector<Database::IdType> trackIds;
{ {
Wt::Dbo::Transaction transaction {_db.getSession()}; auto transaction {_dbSession->createSharedTransaction()};
trackIds = Database::Track::getAllIdsWithFeatures(_db.getSession()); trackIds = Database::Track::getAllIdsWithFeatures(*_dbSession);
} }
if (trackIds.empty()) if (trackIds.empty())
@@ -136,7 +138,7 @@ FeaturesScannerAddon::updateSearcher()
return; return;
} }
auto searcher {std::make_shared<Similarity::FeaturesSearcher>(_db.getSession(), [&]() { return _stopRequested; })}; auto searcher {std::make_shared<Similarity::FeaturesSearcher>(*_dbSession, [&]() { return _stopRequested; })};
if (searcher->isValid()) if (searcher->isValid())
{ {
std::atomic_store(&_searcher, searcher); std::atomic_store(&_searcher, searcher);
@@ -165,15 +167,15 @@ FeaturesScannerAddon::fetchFeatures(Database::IdType trackId, const std::string&
return false; return false;
} }
Wt::Dbo::Transaction transaction{_db.getSession()}; auto uniqueTransaction {_dbSession->createUniqueTransaction()};
Wt::Dbo::ptr<Database::Track> track {Database::Track::getById(_db.getSession(), trackId)}; Wt::Dbo::ptr<Database::Track> track {Database::Track::getById(*_dbSession, trackId)};
if (!track) if (!track)
return false; return false;
LMS_LOG(DBUPDATER, DEBUG) << "Successfully extracted AcousticBrainz lowlevel features for track '" << track->getPath().string() << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Successfully extracted AcousticBrainz lowlevel features for track '" << track->getPath().string() << "'";
Database::TrackFeatures::create(_db.getSession(), track, data); Database::TrackFeatures::create(*_dbSession, track, data);
return true; return true;
} }
@@ -19,9 +19,7 @@
#pragma once #pragma once
#include <Wt/Dbo/SqlConnectionPool.h> #include "database/Session.hpp"
#include "database/DatabaseHandler.hpp"
#include "scanner/MediaScannerAddon.hpp" #include "scanner/MediaScannerAddon.hpp"
#include "SimilarityFeaturesSearcher.hpp" #include "SimilarityFeaturesSearcher.hpp"
@@ -32,7 +30,7 @@ class FeaturesScannerAddon final : public Scanner::MediaScannerAddon
{ {
public: public:
FeaturesScannerAddon(Wt::Dbo::SqlConnectionPool& connectionPool); FeaturesScannerAddon(std::unique_ptr<Database::Session> dbSession);
std::shared_ptr<FeaturesSearcher> getSearcher(); std::shared_ptr<FeaturesSearcher> getSearcher();
@@ -40,18 +38,19 @@ class FeaturesScannerAddon final : public Scanner::MediaScannerAddon
void refreshSettings() override {} void refreshSettings() override {}
void requestStop() override; void requestStop() override;
void preScanComplete() override;
void trackAdded(Database::IdType trackId) override {} void trackAdded(Database::IdType trackId) override {}
void trackToRemove(Database::IdType trackId) override {} void trackToRemove(Database::IdType trackId) override {}
void trackUpdated(Database::IdType trackId) override; void trackUpdated(Database::IdType trackId) override;
void preScanComplete() override;
bool fetchFeatures(Database::IdType trackId, const std::string& MBID); bool fetchFeatures(Database::IdType trackId, const std::string& MBID);
void updateSearcher(); void updateSearcher();
Database::Handler _db; std::unique_ptr<Database::Session> _dbSession;
std::shared_ptr<FeaturesSearcher> _searcher; std::shared_ptr<FeaturesSearcher> _searcher;
bool _stopRequested{false}; bool _stopRequested {};
}; };
FeaturesScannerAddon* setFeaturesScannerAddon(FeaturesScannerAddon addon); FeaturesScannerAddon* setFeaturesScannerAddon(FeaturesScannerAddon addon);
@@ -24,6 +24,7 @@
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/SimilaritySettings.hpp" #include "database/SimilaritySettings.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/TrackFeatures.hpp" #include "database/TrackFeatures.hpp"
#include "som/DataNormalizer.hpp" #include "som/DataNormalizer.hpp"
@@ -43,9 +44,9 @@ using FeatureInfoMap = std::map<std::string, FeatureInfo>;
static static
FeatureInfoMap FeatureInfoMap
getFeatureInfoMap(Wt::Dbo::Session& session) getFeatureInfoMap(Database::Session& session)
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto settings {Database::SimilaritySettings::get(session)}; auto settings {Database::SimilaritySettings::get(session)};
@@ -68,7 +69,7 @@ getFeatureInfoMapNbDimensions(const FeatureInfoMap& featureInfoMap)
static static
boost::optional<SOM::InputVector> boost::optional<SOM::InputVector>
getInputVectorFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, const FeatureInfoMap& featuresInfo, std::size_t nbDimensions) getInputVectorFromTrack(Database::Session& session, Database::IdType trackId, const FeatureInfoMap& featuresInfo, std::size_t nbDimensions)
{ {
boost::optional<SOM::InputVector> res {SOM::InputVector {nbDimensions}}; boost::optional<SOM::InputVector> res {SOM::InputVector {nbDimensions}};
@@ -76,7 +77,7 @@ getInputVectorFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, con
for (auto itFeatureInfo : featuresInfo) for (auto itFeatureInfo : featuresInfo)
features[itFeatureInfo.first] = {}; features[itFeatureInfo.first] = {};
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
Database::Track::pointer track {Database::Track::getById(session, trackId)}; Database::Track::pointer track {Database::Track::getById(session, trackId)};
if (!track) if (!track)
@@ -119,22 +120,27 @@ getInputVectorWeights(const FeatureInfoMap& featuresInfo, std::size_t nbDimensio
return weights; return weights;
} }
FeaturesSearcher::FeaturesSearcher(Wt::Dbo::Session& session, std::function<bool()> stopRequested) FeaturesSearcher::FeaturesSearcher(Database::Session& session, std::function<bool()> stopRequested)
{ {
LMS_LOG(SIMILARITY, INFO) << "Constructing features searcher..."; LMS_LOG(SIMILARITY, INFO) << "Constructing features searcher...";
Wt::Dbo::Transaction transaction {session}; std::size_t nbDimensions;
FeatureInfoMap featuresInfo;
std::vector<Database::IdType> trackIds;
FeatureInfoMap featuresInfo {getFeatureInfoMap(session)}; {
std::size_t nbDimensions {getFeatureInfoMapNbDimensions(featuresInfo)}; auto transaction {session.createSharedTransaction()};
featuresInfo = getFeatureInfoMap(session);
nbDimensions = getFeatureInfoMapNbDimensions(featuresInfo);
LMS_LOG(SIMILARITY, DEBUG) << "Features dimension = " << nbDimensions; LMS_LOG(SIMILARITY, DEBUG) << "Features dimension = " << nbDimensions;
LMS_LOG(SIMILARITY, DEBUG) << "Getting Tracks with features..."; LMS_LOG(SIMILARITY, DEBUG) << "Getting Tracks with features...";
std::vector<Database::IdType> trackIds {Database::Track::getAllIdsWithFeatures(session)}; trackIds = Database::Track::getAllIdsWithFeatures(session);
LMS_LOG(SIMILARITY, DEBUG) << "Getting Tracks with features DONE"; LMS_LOG(SIMILARITY, DEBUG) << "Getting Tracks with features DONE";
transaction.commit(); }
std::vector<SOM::InputVector> samples; std::vector<SOM::InputVector> samples;
std::vector<Database::IdType> samplesTrackIds; std::vector<Database::IdType> samplesTrackIds;
@@ -209,7 +215,7 @@ FeaturesSearcher::FeaturesSearcher(Wt::Dbo::Session& session, std::function<bool
LMS_LOG(SIMILARITY, INFO) << "Successfully constructed features searcher"; LMS_LOG(SIMILARITY, INFO) << "Successfully constructed features searcher";
} }
FeaturesSearcher::FeaturesSearcher(Wt::Dbo::Session& session, FeaturesCache cache, std::function<bool()> stopRequested) FeaturesSearcher::FeaturesSearcher(Database::Session& session, FeaturesCache cache, std::function<bool()> stopRequested)
{ {
LMS_LOG(SIMILARITY, INFO) << "Constructing features searcher from cache..."; LMS_LOG(SIMILARITY, INFO) << "Constructing features searcher from cache...";
@@ -261,7 +267,7 @@ FeaturesSearcher::getSimilarArtists(Database::IdType artistId, std::size_t maxCo
} }
void void
FeaturesSearcher::dump(Wt::Dbo::Session& session, std::ostream& os) const FeaturesSearcher::dump(Database::Session& session, std::ostream& os) const
{ {
if (!isValid()) if (!isValid())
{ {
@@ -273,7 +279,7 @@ FeaturesSearcher::dump(Wt::Dbo::Session& session, std::ostream& os) const
os << "Network size: " << _network->getWidth() << " * " << _network->getHeight() << std::endl; os << "Network size: " << _network->getWidth() << " * " << _network->getHeight() << std::endl;
os << "Ref vectors median distance = " << _networkRefVectorsDistanceMedian << std::endl; os << "Ref vectors median distance = " << _networkRefVectorsDistanceMedian << std::endl;
Wt::Dbo::Transaction transaction(session); auto transaction {session.createSharedTransaction()};
for (SOM::Coordinate y {}; y < _network->getHeight(); ++y) for (SOM::Coordinate y {}; y < _network->getHeight(); ++y)
{ {
@@ -319,7 +325,7 @@ FeaturesSearcher::toCache() const
} }
void void
FeaturesSearcher::init(Wt::Dbo::Session& session, FeaturesSearcher::init(Database::Session& session,
SOM::Network network, SOM::Network network,
std::map<Database::IdType, std::map<Database::IdType,
std::set<SOM::Position>> tracksPosition, std::set<SOM::Position>> tracksPosition,
@@ -342,12 +348,12 @@ FeaturesSearcher::init(Wt::Dbo::Session& session,
if (stopRequested()) if (stopRequested())
return; return;
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
Database::IdType trackId {itTrackCoord.first}; Database::IdType trackId {itTrackCoord.first};
const std::set<SOM::Position>& positionSet {itTrackCoord.second}; const std::set<SOM::Position>& positionSet {itTrackCoord.second};
Database::Track::pointer track {Database::Track::getById(session, trackId)}; const Database::Track::pointer track {Database::Track::getById(session, trackId)};
if (!track) if (!track)
continue; continue;
@@ -22,24 +22,27 @@
#include <map> #include <map>
#include <set> #include <set>
#include "database/DatabaseHandler.hpp"
#include "database/Types.hpp" #include "database/Types.hpp"
#include "som/DataNormalizer.hpp" #include "som/DataNormalizer.hpp"
#include "som/Network.hpp" #include "som/Network.hpp"
#include "SimilarityFeaturesCache.hpp" #include "SimilarityFeaturesCache.hpp"
namespace Similarity { namespace Database
{
class Session;
}
namespace Similarity {
class FeaturesSearcher class FeaturesSearcher
{ {
public: public:
// Use cache // Use cache
FeaturesSearcher(Wt::Dbo::Session& session, FeaturesCache cache, std::function<bool()> stopRequested); FeaturesSearcher(Database::Session& session, FeaturesCache cache, std::function<bool()> stopRequested);
// Use training (may be very slow) // Use training (may be very slow)
FeaturesSearcher(Wt::Dbo::Session& session, std::function<bool()> stopRequested); FeaturesSearcher(Database::Session& session, std::function<bool()> stopRequested);
bool isValid() const; bool isValid() const;
@@ -51,7 +54,7 @@ class FeaturesSearcher
std::vector<Database::IdType> getSimilarReleases(Database::IdType releaseId, std::size_t maxCount) const; std::vector<Database::IdType> getSimilarReleases(Database::IdType releaseId, std::size_t maxCount) const;
std::vector<Database::IdType> getSimilarArtists(Database::IdType artistId, std::size_t maxCount) const; std::vector<Database::IdType> getSimilarArtists(Database::IdType artistId, std::size_t maxCount) const;
void dump(Wt::Dbo::Session& session, std::ostream& os) const; void dump(Database::Session& session, std::ostream& os) const;
FeaturesCache toCache() const; FeaturesCache toCache() const;
@@ -59,7 +62,7 @@ class FeaturesSearcher
using ObjectPositions = std::map<Database::IdType, std::set<SOM::Position>>; using ObjectPositions = std::map<Database::IdType, std::set<SOM::Position>>;
void init(Wt::Dbo::Session& session, void init(Database::Session& session,
SOM::Network network, SOM::Network network,
ObjectPositions tracksPosition, ObjectPositions tracksPosition,
std::function<bool()> stopRequested); std::function<bool()> stopRequested);
+12 -12
View File
@@ -34,21 +34,21 @@ namespace UserInterface {
Auth::Auth() Auth::Auth()
: Wt::WTemplateFormView(Wt::WString::tr("Lms.Auth.template")) : Wt::WTemplateFormView(Wt::WString::tr("Lms.Auth.template"))
{ {
_model = std::make_shared<Wt::Auth::AuthModel>(LmsApp->getDb().getAuthService(), LmsApp->getDb().getUserDatabase()); _model = std::make_shared<Wt::Auth::AuthModel>(LmsApp->getDbSession().getAuthService(), LmsApp->getDbSession().getUserDatabase());
_model->addPasswordAuth(&Database::Handler::getPasswordService()); _model->addPasswordAuth(&Database::Session::getPasswordService());
// LoginName // LoginName
setFormWidget(Wt::Auth::AuthModel::LoginNameField, std::make_unique<Wt::WLineEdit>()); setFormWidget(Wt::Auth::AuthModel::LoginNameField, std::make_unique<Wt::WLineEdit>());
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto demoUser = Database::User::getDemo(LmsApp->getDboSession()); auto demoUser = Database::User::getDemo(LmsApp->getDbSession());
if (demoUser) if (demoUser)
{ {
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId(std::to_string(demoUser.id())); const std::string userName {LmsApp->getDbSession().getUserLoginName(demoUser)};
_model->setValue(Wt::Auth::AuthModel::LoginNameField, authUser.identity(Wt::Auth::Identity::LoginName)); _model->setValue(Wt::Auth::AuthModel::LoginNameField, userName );
_model->setValue(Wt::Auth::AuthModel::PasswordField, authUser.identity(Wt::Auth::Identity::LoginName)); _model->setValue(Wt::Auth::AuthModel::PasswordField, userName);
} }
} }
@@ -64,9 +64,9 @@ Auth::Auth()
Wt::WPushButton* loginBtn = bindNew<Wt::WPushButton>("login-btn", Wt::WString::tr("Lms.login")); Wt::WPushButton* loginBtn = bindNew<Wt::WPushButton>("login-btn", Wt::WString::tr("Lms.login"));
loginBtn->clicked().connect(this, &Auth::processAuth); loginBtn->clicked().connect(this, &Auth::processAuth);
LmsApp->getDb().getLogin().changed().connect(std::bind([=] LmsApp->getDbSession().getLogin().changed().connect(std::bind([=]
{ {
if (LmsApp->getDb().getLogin().loggedIn()) if (LmsApp->getDbSession().getLogin().loggedIn())
this->setHidden(true); this->setHidden(true);
})); }));
@@ -76,7 +76,7 @@ Auth::Auth()
if (user.isValid()) if (user.isValid())
{ {
LMS_LOG(UI, DEBUG) << "Valid user found from auth token (id = " << user.id() << ")"; LMS_LOG(UI, DEBUG) << "Valid user found from auth token (id = " << user.id() << ")";
_model->loginUser(LmsApp->getDb().getLogin(), user, Wt::Auth::LoginState::Weak); _model->loginUser(LmsApp->getDbSession().getLogin(), user, Wt::Auth::LoginState::Weak);
} }
} }
@@ -86,7 +86,7 @@ Auth::processAuth()
updateModel(_model.get()); updateModel(_model.get());
if (_model->validate()) if (_model->validate())
_model->login(LmsApp->getDb().getLogin()); _model->login(LmsApp->getDbSession().getLogin());
else else
updateView(_model.get()); updateView(_model.get());
} }
@@ -94,7 +94,7 @@ Auth::processAuth()
void void
Auth::logout() Auth::logout()
{ {
_model->logout(LmsApp->getDb().getLogin()); _model->logout(LmsApp->getDbSession().getLogin());
} }
} // namespace UserInterface } // namespace UserInterface
+33 -31
View File
@@ -55,9 +55,9 @@
namespace UserInterface { namespace UserInterface {
std::unique_ptr<Wt::WApplication> std::unique_ptr<Wt::WApplication>
LmsApplication::create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, LmsApplicationGroupContainer& appGroups) LmsApplication::create(const Wt::WEnvironment& env, Database::Database& db, LmsApplicationGroupContainer& appGroups)
{ {
return std::make_unique<LmsApplication>(env, connectionPool, appGroups); return std::make_unique<LmsApplication>(env, db.createSession(), appGroups);
} }
LmsApplication* LmsApplication*
@@ -67,11 +67,11 @@ LmsApplication::instance()
} }
LmsApplication::LmsApplication(const Wt::WEnvironment& env, LmsApplication::LmsApplication(const Wt::WEnvironment& env,
Wt::Dbo::SqlConnectionPool& connectionPool, std::unique_ptr<Database::Session> dbSession,
LmsApplicationGroupContainer& appGroups) LmsApplicationGroupContainer& appGroups)
: Wt::WApplication(env), : Wt::WApplication {env},
_db(connectionPool), _dbSession {std::move(dbSession)},
_appGroups(appGroups) _appGroups {appGroups}
{ {
auto bootstrapTheme = std::make_unique<Wt::WBootstrapTheme>(); auto bootstrapTheme = std::make_unique<Wt::WBootstrapTheme>();
bootstrapTheme->setVersion(Wt::BootstrapVersion::v3); bootstrapTheme->setVersion(Wt::BootstrapVersion::v3);
@@ -116,12 +116,14 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
setTitle("LMS"); setTitle("LMS");
// If here is no account in the database, launch the first connection wizard // Handle Media Scanner events and other session events
bool firstConnection; enableUpdates(true);
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
firstConnection = (Database::User::getAll(LmsApp->getDboSession()).size() == 0); // If here is no account in the database, launch the first connection wizard
bool firstConnection {};
{
auto transaction {_dbSession->createSharedTransaction()};
firstConnection = Database::User::getAll(*_dbSession).empty();
} }
LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << firstConnection; LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << firstConnection;
@@ -132,7 +134,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
} }
else else
{ {
LmsApp->getDb().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent); LmsApp->getDbSession().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
_auth = root()->addNew<Auth>(); _auth = root()->addNew<Auth>();
} }
} }
@@ -260,7 +262,7 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin)
LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'";
for (auto& view : views) for (const auto& view : views)
{ {
if (wApp->internalPathMatches(view.path)) if (wApp->internalPathMatches(view.path))
{ {
@@ -284,19 +286,20 @@ LmsApplication::getApplicationGroup()
void void
LmsApplication::handleAuthEvent() LmsApplication::handleAuthEvent()
{ {
try if (!getDbSession().getLogin().loggedIn())
{
if (!getDb().getLogin().loggedIn())
{ {
LMS_LOG(UI, INFO) << "User '" << _userIdentity << " 'logged out, session = " << sessionId(); LMS_LOG(UI, INFO) << "User '" << _userIdentity << " 'logged out, session = " << sessionId();
goHomeAndQuit(); goHomeAndQuit();
return; return;
} }
else
try
{ {
// post([this]
// {
_userIdentity = getAuthUser().identity(Wt::Auth::Identity::LoginName); _userIdentity = getAuthUser().identity(Wt::Auth::Identity::LoginName);
LmsApplicationInfo info = LmsApplicationInfo::fromEnvironment(environment()); const LmsApplicationInfo info {LmsApplicationInfo::fromEnvironment(environment())};
LMS_LOG(UI, INFO) << "User '" << _userIdentity << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", session = " << sessionId(); LMS_LOG(UI, INFO) << "User '" << _userIdentity << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", session = " << sessionId();
getApplicationGroup().join(info); getApplicationGroup().join(info);
@@ -307,23 +310,22 @@ LmsApplication::handleAuthEvent()
}); });
createHome(); createHome();
} triggerUpdate();
// });
} }
catch (std::exception& e) catch (std::exception& e)
{ {
LMS_LOG(UI, ERROR) << "Error while handling auth event: " << e.what(); LMS_LOG(UI, ERROR) << "Error while handling auth event: " << e.what();
throw LmsException("Internal error"); // Do not put details here at it appears on the user rendered html throw LmsException {"Internal error"}; // Do not put details here at it appears on the user rendered html
} }
} }
void void
LmsApplication::createHome() LmsApplication::createHome()
{ {
// Handle Media Scanner events and other session events
enableUpdates(true);
{ {
Wt::Dbo::Transaction transaction (LmsApp->getDboSession()); auto transaction {_dbSession->createSharedTransaction()};
_isAdmin = LmsApp->getUser()->isAdmin(); _isAdmin = LmsApp->getUser()->isAdmin();
} }
@@ -332,14 +334,14 @@ LmsApplication::createHome()
setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm")); setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm"));
Wt::WTemplate* main = root()->addWidget(std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.template"))); Wt::WTemplate* main {root()->addWidget(std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.template")))};
// Navbar // Navbar
Wt::WNavigationBar* navbar = main->bindNew<Wt::WNavigationBar>("navbar-top"); Wt::WNavigationBar* navbar = main->bindNew<Wt::WNavigationBar>("navbar-top");
navbar->setTitle("LMS", Wt::WLink(Wt::LinkType::InternalPath, "/artists")); navbar->setTitle("LMS", Wt::WLink(Wt::LinkType::InternalPath, "/artists"));
navbar->setResponsive(true); navbar->setResponsive(true);
Wt::WMenu* menu = navbar->addMenu(std::make_unique<Wt::WMenu>()); Wt::WMenu* menu {navbar->addMenu(std::make_unique<Wt::WMenu>())};
{ {
auto menuItem = menu->insertItem(0, Wt::WString::tr("Lms.Explore.artists")); auto menuItem = menu->insertItem(0, Wt::WString::tr("Lms.Explore.artists"));
menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/artists")); menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/artists"));
@@ -417,14 +419,14 @@ LmsApplication::createHome()
mainStack->addNew<UserView>(); mainStack->addNew<UserView>();
} }
explore->tracksAdd.connect([=] (std::vector<Database::Track::pointer> tracks) explore->tracksAdd.connect([=] (const std::vector<Database::IdType>& trackIds)
{ {
playqueue->addTracks(tracks); playqueue->addTracks(trackIds);
}); });
explore->tracksPlay.connect([=] (std::vector<Database::Track::pointer> tracks) explore->tracksPlay.connect([=] (const std::vector<Database::IdType>& trackIds)
{ {
playqueue->playTracks(tracks); playqueue->playTracks(trackIds);
}); });
@@ -459,7 +461,7 @@ LmsApplication::createHome()
// Events from MediaScanner // Events from MediaScanner
{ {
std::string sessionId = LmsApp->sessionId(); const std::string sessionId {LmsApp->sessionId()};
getService<Scanner::MediaScanner>()->scanComplete().connect(this, [=] (Scanner::MediaScanner::Stats stats) getService<Scanner::MediaScanner>()->scanComplete().connect(this, [=] (Scanner::MediaScanner::Stats stats)
{ {
Wt::WServer::instance()->post(sessionId, [=] Wt::WServer::instance()->post(sessionId, [=]
@@ -551,7 +553,7 @@ static std::string msgTypeToString(MsgType type)
void void
LmsApplication::post(std::function<void()> func) LmsApplication::post(std::function<void()> func)
{ {
Wt::WServer::instance()->post(LmsApp->sessionId(), func); Wt::WServer::instance()->post(LmsApp->sessionId(), std::move(func));
} }
static std::string escape(std::string str) static std::string escape(std::string str)
+10 -12
View File
@@ -23,9 +23,8 @@
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <Wt/WApplication.h> #include <Wt/WApplication.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include "database/DatabaseHandler.hpp" #include "database/Database.hpp"
#include "scanner/MediaScanner.hpp" #include "scanner/MediaScanner.hpp"
#include "LmsApplicationGroup.hpp" #include "LmsApplicationGroup.hpp"
@@ -35,6 +34,7 @@ namespace Database {
class Artist; class Artist;
class Cluster; class Cluster;
class Release; class Release;
class User;
} }
namespace UserInterface { namespace UserInterface {
@@ -73,20 +73,18 @@ enum class MsgType
class LmsApplication : public Wt::WApplication class LmsApplication : public Wt::WApplication
{ {
public: public:
LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, LmsApplicationGroupContainer& appGroups); LmsApplication(const Wt::WEnvironment& env, std::unique_ptr<Database::Session> dbSession, LmsApplicationGroupContainer& appGroups);
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Database& db, LmsApplicationGroupContainer& appGroups);
Wt::Dbo::SqlConnectionPool& connectionPool, LmsApplicationGroupContainer& appGroups);
static LmsApplication* instance(); static LmsApplication* instance();
// Session application data // Session application data
std::shared_ptr<ImageResource> getImageResource() { return _imageResource; } std::shared_ptr<ImageResource> getImageResource() { return _imageResource; }
std::shared_ptr<AudioResource> getAudioResource() { return _audioResource; } std::shared_ptr<AudioResource> getAudioResource() { return _audioResource; }
Database::Handler& getDb() { return _db;} Database::Session& getDbSession() { return *_dbSession.get();}
Wt::Dbo::Session& getDboSession() { return _db.getSession();}
const Wt::Auth::User& getAuthUser() { return _db.getLogin().user(); } const Wt::Auth::User& getAuthUser() { return getDbSession().getLogin().user(); }
Database::User::pointer getUser() { return _db.getCurrentUser(); } Wt::Dbo::ptr<Database::User> getUser() { return getDbSession().getLoggedUser(); }
Wt::WString getUserIdentity() { return _userIdentity; } Wt::WString getUserIdentity() { return _userIdentity; }
Events& getEvents() { return _events; } Events& getEvents() { return _events; }
@@ -119,14 +117,14 @@ class LmsApplication : public Wt::WApplication
void createHome(); void createHome();
Wt::Signal<> _preQuit; Wt::Signal<> _preQuit;
Database::Handler _db; std::unique_ptr<Database::Session> _dbSession;
LmsApplicationGroupContainer& _appGroups; LmsApplicationGroupContainer& _appGroups;
Events _events; Events _events;
Wt::WString _userIdentity; Wt::WString _userIdentity;
Auth* _auth = nullptr; Auth* _auth {};
std::shared_ptr<ImageResource> _imageResource; std::shared_ptr<ImageResource> _imageResource;
std::shared_ptr<AudioResource> _audioResource; std::shared_ptr<AudioResource> _audioResource;
bool _isAdmin = false; bool _isAdmin {};
}; };
+3 -3
View File
@@ -59,12 +59,12 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play)
{ {
LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId; LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId;
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto track = Database::Track::getById(LmsApp->getDboSession(), trackId); const auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
try try
{ {
Av::MediaFile mediaFile(track->getPath()); const Av::MediaFile mediaFile {track->getPath()};
auto resource = LmsApp->getAudioResource()->getUrl(trackId); auto resource = LmsApp->getAudioResource()->getUrl(trackId);
auto imgResource = LmsApp->getImageResource()->getTrackUrl(trackId, 64); auto imgResource = LmsApp->getImageResource()->getTrackUrl(trackId, 64);
+12 -8
View File
@@ -70,7 +70,7 @@ std::unique_ptr<Wt::WTemplate> createEntry(Database::Track::pointer track)
namespace UserInterface { namespace UserInterface {
PlayHistory::PlayHistory() PlayHistory::PlayHistory()
: Wt::WTemplate(Wt::WString::tr("Lms.PlayHistory.template")) : Wt::WTemplate {Wt::WString::tr("Lms.PlayHistory.template")}
{ {
addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("tr", &Wt::WTemplate::Functions::tr);
@@ -86,10 +86,14 @@ PlayHistory::PlayHistory()
LmsApp->getEvents().trackLoaded.connect([=](Database::IdType trackId, bool /* play */) LmsApp->getEvents().trackLoaded.connect([=](Database::IdType trackId, bool /* play */)
{ {
Wt::Dbo::Transaction transaction (LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
auto trackEntry = LmsApp->getUser()->getPlayedTrackList().modify()->add(trackId); Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
_entriesContainer->insertWidget(0, createEntry(trackEntry->getTrack())); if (track)
{
Database::TrackListEntry::create(LmsApp->getDbSession(), track, LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession()));
_entriesContainer->insertWidget(0, createEntry(track));
}
}); });
addSome(); addSome();
@@ -98,11 +102,11 @@ PlayHistory::PlayHistory()
void void
PlayHistory::addSome() PlayHistory::addSome()
{ {
Wt::Dbo::Transaction transaction (LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto trackList = LmsApp->getUser()->getPlayedTrackList(); const Database::TrackList::pointer trackList {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())};
auto trackEntries = trackList->getEntriesReverse(_entriesContainer->count(), 50); auto trackEntries {trackList->getEntriesReverse(_entriesContainer->count(), 50)};
for (auto trackEntry : trackEntries) for (const auto& trackEntry : trackEntries)
_entriesContainer->addWidget(createEntry(trackEntry->getTrack())); _entriesContainer->addWidget(createEntry(trackEntry->getTrack()));
_showMore->setHidden(static_cast<std::size_t>(_entriesContainer->count()) >= trackList->getCount()); _showMore->setHidden(static_cast<std::size_t>(_entriesContainer->count()) >= trackList->getCount());
-2
View File
@@ -23,8 +23,6 @@
#include <Wt/WPushButton.h> #include <Wt/WPushButton.h>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include "database/Types.hpp"
namespace UserInterface { namespace UserInterface {
class PlayHistory : public Wt::WTemplate class PlayHistory : public Wt::WTemplate
+84 -79
View File
@@ -19,12 +19,15 @@
#include "PlayQueueView.hpp" #include "PlayQueueView.hpp"
#include <Wt/WText.h>
#include <Wt/WText.h> #include <Wt/WText.h>
#include "database/Track.hpp"
#include "database/TrackList.hpp" #include "database/TrackList.hpp"
#include "main/Service.hpp" #include "main/Service.hpp"
#include "similarity/SimilaritySearcher.hpp" #include "similarity/SimilaritySearcher.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/Utils.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
namespace UserInterface { namespace UserInterface {
@@ -35,7 +38,7 @@ PlayQueue::PlayQueue()
addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("tr", &Wt::WTemplate::Functions::tr);
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
_repeatAll = LmsApp->getUser()->isRepeatAllSet(); _repeatAll = LmsApp->getUser()->isRepeatAllSet();
_radioMode = LmsApp->getUser()->isRadioSet(); _radioMode = LmsApp->getUser()->isRadioSet();
} }
@@ -62,9 +65,15 @@ PlayQueue::PlayQueue()
shuffleBtn->clicked().connect([=] shuffleBtn->clicked().connect([=]
{ {
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
getTrackList().modify()->shuffle(); Database::TrackList::pointer trackList {getTrackList()};
auto entries {trackList->getEntries()};
shuffleContainer(entries);
getTrackList().modify()->clear();
for (const auto& entry : entries)
Database::TrackListEntry::create(LmsApp->getDbSession(), entry->getTrack(), trackList);
} }
_entriesContainer->clear(); _entriesContainer->clear();
addSome(); addSome();
@@ -77,7 +86,7 @@ PlayQueue::PlayQueue()
_repeatAll = !_repeatAll; _repeatAll = !_repeatAll;
updateRepeatBtn(); updateRepeatBtn();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
if (!LmsApp->getUser()->isDemo()) if (!LmsApp->getUser()->isDemo())
LmsApp->getUser().modify()->setRepeatAll(_repeatAll); LmsApp->getUser().modify()->setRepeatAll(_repeatAll);
@@ -91,7 +100,7 @@ PlayQueue::PlayQueue()
_radioMode = !_radioMode; _radioMode = !_radioMode;
updateRadioBtn(); updateRadioBtn();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
if (!LmsApp->getUser()->isDemo()) if (!LmsApp->getUser()->isDemo())
LmsApp->getUser().modify()->setRadio(_radioMode); LmsApp->getUser().modify()->setRadio(_radioMode);
@@ -102,23 +111,21 @@ PlayQueue::PlayQueue()
LmsApp->preQuit().connect([=] LmsApp->preQuit().connect([=]
{ {
if (_tracklistId) auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
if (LmsApp->getUser()->isDemo())
{ {
LMS_LOG(UI, DEBUG) << "Removing tracklist id " << *_tracklistId; LMS_LOG(UI, DEBUG) << "Removing tracklist id " << _tracklistId;
auto tracklist = Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId);
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto tracklist = Database::TrackList::getById(LmsApp->getDboSession(), *_tracklistId);
if (tracklist) if (tracklist)
tracklist.remove(); tracklist.remove();
} }
}); });
updateInfo();
addSome();
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
Database::TrackList::pointer trackList;
if (!LmsApp->getUser()->isDemo()) if (!LmsApp->getUser()->isDemo())
{ {
@@ -126,8 +133,19 @@ PlayQueue::PlayQueue()
{ {
load(LmsApp->getUser()->getCurPlayingTrackPos(), false); load(LmsApp->getUser()->getCurPlayingTrackPos(), false);
}); });
trackList = LmsApp->getUser()->getQueuedTrackList(LmsApp->getDbSession());
} }
else
{
static const std::string currentPlayQueueName {"__current__playqueue__"};
trackList = Database::TrackList::create(LmsApp->getDbSession(), currentPlayQueueName, Database::TrackList::Type::Internal, false, LmsApp->getUser());
} }
_tracklistId = trackList.id();
}
updateInfo();
addSome();
} }
void void
@@ -145,32 +163,17 @@ PlayQueue::updateRadioBtn()
Database::TrackList::pointer Database::TrackList::pointer
PlayQueue::getTrackList() PlayQueue::getTrackList()
{ {
Database::TrackList::pointer res; return Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId);
if (LmsApp->getUser()->isDemo())
{
static const std::string currentPlayQueueName = "__current__playqueue__";
if (!_tracklistId)
{
res = Database::TrackList::create(LmsApp->getDboSession(), currentPlayQueueName, Database::TrackList::Type::Internal, false, LmsApp->getUser());
LmsApp->getDboSession().flush();
_tracklistId = res.id();
return res;
}
return Database::TrackList::getById(LmsApp->getDboSession(), *_tracklistId);
}
return LmsApp->getUser()->getQueuedTrackList();
} }
void void
PlayQueue::clearTracks() PlayQueue::clearTracks()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); {
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
getTrackList().modify()->clear(); getTrackList().modify()->clear();
}
_showMore->setHidden(true); _showMore->setHidden(true);
_entriesContainer->clear(); _entriesContainer->clear();
updateInfo(); updateInfo();
@@ -189,11 +192,12 @@ PlayQueue::load(std::size_t pos, bool play)
{ {
updateCurrentTrack(false); updateCurrentTrack(false);
Database::IdType trackId; Database::IdType trackId {};
bool addRadioTrack {};
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto tracklist = getTrackList(); Database::TrackList::pointer tracklist {getTrackList()};
// If out of range, stop playing // If out of range, stop playing
if (pos >= tracklist->getCount()) if (pos >= tracklist->getCount())
@@ -209,19 +213,22 @@ PlayQueue::load(std::size_t pos, bool play)
// If last and radio mode, fill the next song // If last and radio mode, fill the next song
if (_radioMode && pos == tracklist->getCount() - 1) if (_radioMode && pos == tracklist->getCount() - 1)
addRadioTrack(); addRadioTrack = true;
_trackPos = pos; _trackPos = pos;
auto track = tracklist->getEntry(*_trackPos)->getTrack(); auto track = tracklist->getEntry(*_trackPos)->getTrack();
trackId = track.id(); trackId = track.id();
updateCurrentTrack(true);
if (!LmsApp->getUser()->isDemo()) if (!LmsApp->getUser()->isDemo())
LmsApp->getUser().modify()->setCurPlayingTrackPos(pos); LmsApp->getUser().modify()->setCurPlayingTrackPos(pos);
} }
if (addRadioTrack)
enqueueRadioTrack();
updateCurrentTrack(true);
loadTrack.emit(trackId, play); loadTrack.emit(trackId, play);
} }
@@ -252,7 +259,7 @@ PlayQueue::playNext()
void void
PlayQueue::updateInfo() PlayQueue::updateInfo()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
_nbTracks->setText(Wt::WString::tr("Lms.PlayQueue.nb-tracks").arg(static_cast<unsigned>(getTrackList()->getCount()))); _nbTracks->setText(Wt::WString::tr("Lms.PlayQueue.nb-tracks").arg(static_cast<unsigned>(getTrackList()->getCount())));
} }
@@ -274,55 +281,58 @@ PlayQueue::updateCurrentTrack(bool selected)
} }
void void
PlayQueue::enqueueTracks(const std::vector<Database::Track::pointer>& tracks) PlayQueue::enqueueTracks(const std::vector<Database::IdType>& trackIds)
{ {
// Use a "session" playqueue in order to store the current playqueue {
// so that the user can disconnect and get its playqueue back auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
auto tracklist = getTrackList(); auto tracklist = getTrackList();
for (Database::IdType trackId : trackIds)
{
Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
if (!track)
continue;
for (auto track : tracks) Database::TrackListEntry::create(LmsApp->getDbSession(), track, tracklist);
Database::TrackListEntry::create(LmsApp->getDboSession(), track, tracklist); }
}
updateInfo(); updateInfo();
addSome(); addSome();
} }
void void
PlayQueue::enqueueTrack(Database::Track::pointer track) PlayQueue::enqueueTrack(Database::IdType trackId)
{ {
enqueueTracks(std::vector<Database::Track::pointer>(1, track)); enqueueTracks({trackId});
} }
void void
PlayQueue::addTracks(const std::vector<Database::Track::pointer>& tracks) PlayQueue::addTracks(const std::vector<Database::IdType>& trackIds)
{ {
enqueueTracks(tracks); enqueueTracks(trackIds);
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", tracks.size()).arg(tracks.size()), std::chrono::milliseconds(2000)); LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
} }
void void
PlayQueue::playTracks(const std::vector<Database::Track::pointer>& tracks) PlayQueue::playTracks(const std::vector<Database::IdType>& trackIds)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
clearTracks(); clearTracks();
enqueueTracks(tracks); enqueueTracks(trackIds);
load(0, true); load(0, true);
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-playing", tracks.size()).arg(tracks.size()), std::chrono::milliseconds(2000)); LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-playing", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
} }
void void
PlayQueue::addSome() PlayQueue::addSome()
{ {
Wt::Dbo::Transaction transaction (LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto tracklist = getTrackList(); auto tracklist = getTrackList();
auto tracklistEntries = tracklist->getEntries(_entriesContainer->count(), 50); auto tracklistEntries = tracklist->getEntries(_entriesContainer->count(), 50);
for (auto tracklistEntry : tracklistEntries) for (const Database::TrackListEntry::pointer& tracklistEntry : tracklistEntries)
{ {
auto tracklistEntryId = tracklistEntry.id(); auto tracklistEntryId = tracklistEntry.id();
auto track = tracklistEntry->getTrack(); auto track = tracklistEntry->getTrack();
@@ -367,15 +377,15 @@ PlayQueue::addSome()
{ {
// Remove the entry n both the widget tree and the playqueue // Remove the entry n both the widget tree and the playqueue
{ {
Wt::Dbo::Transaction transaction (LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
auto entryToRemove = Database::TrackListEntry::getById(LmsApp->getDboSession(), tracklistEntryId); Database::TrackListEntry::pointer entryToRemove {Database::TrackListEntry::getById(LmsApp->getDbSession(), tracklistEntryId)};
entryToRemove.remove(); entryToRemove.remove();
} }
if (_trackPos) if (_trackPos)
{ {
auto pos = _entriesContainer->indexOf(entry); auto pos {_entriesContainer->indexOf(entry)};
if (pos > 0 && *_trackPos >= static_cast<std::size_t>(pos)) if (pos > 0 && *_trackPos >= static_cast<std::size_t>(pos))
(*_trackPos)--; (*_trackPos)--;
} }
@@ -391,28 +401,23 @@ PlayQueue::addSome()
} }
void void
PlayQueue::addRadioTrack() PlayQueue::enqueueRadioTrack()
{ {
auto tracklist = getTrackList(); std::vector<Database::IdType> trackIds;
{
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
Database::TrackList::pointer tracklist {getTrackList()};
trackIds = getTrackList()->getTrackIds();
}
std::vector<Database::IdType> trackIds = getTrackList()->getTrackIds();
if (trackIds.empty()) if (trackIds.empty())
return; return;
auto res = getService<Similarity::Searcher>()->getSimilarTracks(LmsApp->getDboSession(), std::set<Database::IdType>(trackIds.begin(), trackIds.end()), 1); const std::vector<Database::IdType> trackToAddIds {getService<Similarity::Searcher>()->getSimilarTracks(LmsApp->getDbSession(), std::set<Database::IdType>(std::cbegin(trackIds), std::cend(trackIds)), 1)};
for (auto trackId : res) enqueueTracks(trackToAddIds);
{
auto trackToAdd = Database::Track::getById(LmsApp->getDboSession(), trackId);
enqueueTrack(trackToAdd);
} }
}
void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Searcher> similaritySearcher)
{
}
} // namespace UserInterface } // namespace UserInterface
+20 -20
View File
@@ -19,23 +19,23 @@
#pragma once #pragma once
#include <Wt/WCheckBox.h>
#include <Wt/WContainerWidget.h> #include <Wt/WContainerWidget.h>
#include <Wt/WPushButton.h> #include <Wt/WPushButton.h>
#include <Wt/WSignal.h>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include <Wt/WText.h> #include <Wt/WText.h>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include "database/TrackList.hpp" #include "database/Types.hpp"
#include "database/Track.hpp"
namespace Similarity namespace Similarity {
{
class Finder; class Finder;
} }
namespace Database {
class TrackList;
}
namespace UserInterface { namespace UserInterface {
class PlayQueue : public Wt::WTemplate class PlayQueue : public Wt::WTemplate
@@ -43,8 +43,8 @@ class PlayQueue : public Wt::WTemplate
public: public:
PlayQueue(); PlayQueue();
void addTracks(const std::vector<Database::Track::pointer>& tracks); void addTracks(const std::vector<Database::IdType>& trackIds);
void playTracks(const std::vector<Database::Track::pointer>& tracks); void playTracks(const std::vector<Database::IdType>& trackIds);
// play the next track in the queue // play the next track in the queue
void playNext(); void playNext();
@@ -59,13 +59,13 @@ class PlayQueue : public Wt::WTemplate
Wt::Signal<> trackUnload; Wt::Signal<> trackUnload;
private: private:
Database::TrackList::pointer getTrackList(); Wt::Dbo::ptr<Database::TrackList> getTrackList();
void clearTracks(); void clearTracks();
void enqueueTracks(const std::vector<Database::Track::pointer>& tracks); void enqueueTracks(const std::vector<Database::IdType>& trackIds);
void enqueueTrack(Database::Track::pointer track); void enqueueTrack(Database::IdType trackId);
void addSome(); void addSome();
void addRadioTrack(); void enqueueRadioTrack();
void updateInfo(); void updateInfo();
void updateCurrentTrack(bool selected); void updateCurrentTrack(bool selected);
void updateRepeatBtn(); void updateRepeatBtn();
@@ -77,14 +77,14 @@ class PlayQueue : public Wt::WTemplate
void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Finder> similarityFinder); void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Finder> similarityFinder);
void addRadioTrackFromClusters(); void addRadioTrackFromClusters();
bool _repeatAll = false; bool _repeatAll {};
bool _radioMode = false; bool _radioMode {};
boost::optional<Database::IdType> _tracklistId; Database::IdType _tracklistId {};
Wt::WContainerWidget* _entriesContainer = nullptr; Wt::WContainerWidget* _entriesContainer {};
Wt::WPushButton* _showMore = nullptr; Wt::WPushButton* _showMore {};
Wt::WText* _nbTracks = nullptr; Wt::WText* _nbTracks {};
Wt::WText* _repeatBtn = nullptr; Wt::WText* _repeatBtn {};
Wt::WText* _radioBtn = nullptr; Wt::WText* _radioBtn {};
boost::optional<std::size_t> _trackPos; // current track position, if set boost::optional<std::size_t> _trackPos; // current track position, if set
}; };
+7 -7
View File
@@ -69,7 +69,7 @@ class SettingsModel : public Wt::WFormModel
void saveData() void saveData()
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
LmsApp->getUser().modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField))); LmsApp->getUser().modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField)));
@@ -82,12 +82,12 @@ class SettingsModel : public Wt::WFormModel
LmsApp->getUser().modify()->setAudioTranscodeFormat(_transcodeFormatModel->getValue(*transcodeFormatRow)); LmsApp->getUser().modify()->setAudioTranscodeFormat(_transcodeFormatModel->getValue(*transcodeFormatRow));
if (!valueText(PasswordField).empty()) if (!valueText(PasswordField).empty())
Handler::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField)); Session::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField));
} }
void loadData() void loadData()
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
setValue(TranscodeEnableField, LmsApp->getUser()->getAudioTranscodeEnable()); setValue(TranscodeEnableField, LmsApp->getUser()->getAudioTranscodeEnable());
if (!LmsApp->getUser()->getAudioTranscodeEnable()) if (!LmsApp->getUser()->getAudioTranscodeEnable())
@@ -116,7 +116,7 @@ class SettingsModel : public Wt::WFormModel
if (!valueText(PasswordField).empty()) if (!valueText(PasswordField).empty())
{ {
// Evaluate the strength of the password // Evaluate the strength of the password
auto res = Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), LmsApp->getUserIdentity(), ""); auto res = Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), LmsApp->getUserIdentity(), "");
if (!res.isValid()) if (!res.isValid())
error = res.message(); error = res.message();
@@ -148,12 +148,12 @@ class SettingsModel : public Wt::WFormModel
{ {
Bitrate maxAudioBitrate; Bitrate maxAudioBitrate;
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
maxAudioBitrate = LmsApp->getUser()->getMaxAudioTranscodeBitrate(); maxAudioBitrate = LmsApp->getUser()->getMaxAudioTranscodeBitrate();
} }
_transcodeBitrateModel = std::make_shared<ValueStringModel<Bitrate>>(); _transcodeBitrateModel = std::make_shared<ValueStringModel<Bitrate>>();
for (Bitrate bitrate : User::audioTranscodeAllowedBitrates) for (const Bitrate bitrate : User::audioTranscodeAllowedBitrates)
{ {
if (bitrate > maxAudioBitrate) if (bitrate > maxAudioBitrate)
break; break;
@@ -243,7 +243,7 @@ SettingsView::refreshView()
{ {
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
if (LmsApp->getUser()->isDemo()) if (LmsApp->getUser()->isDemo())
{ {
+7 -7
View File
@@ -85,10 +85,10 @@ class DatabaseSettingsModel : public Wt::WFormModel
void loadData() void loadData()
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto scanSettings {ScanSettings::get(LmsApp->getDboSession())}; const ScanSettings::pointer scanSettings {ScanSettings::get(LmsApp->getDbSession())};
auto similaritySettings {SimilaritySettings::get(LmsApp->getDboSession())}; const SimilaritySettings::pointer similaritySettings {SimilaritySettings::get(LmsApp->getDbSession())};
setValue(MediaDirectoryField, scanSettings->getMediaDirectory().string()); setValue(MediaDirectoryField, scanSettings->getMediaDirectory().string());
@@ -115,10 +115,10 @@ class DatabaseSettingsModel : public Wt::WFormModel
void saveData() void saveData()
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
auto scanSettings {ScanSettings::get(LmsApp->getDboSession())}; ScanSettings::pointer scanSettings {ScanSettings::get(LmsApp->getDbSession())};
auto similaritySettings {SimilaritySettings::get(LmsApp->getDboSession())}; SimilaritySettings::pointer similaritySettings {SimilaritySettings::get(LmsApp->getDbSession())};
scanSettings.modify()->setMediaDirectory(valueText(MediaDirectoryField).toUTF8()); scanSettings.modify()->setMediaDirectory(valueText(MediaDirectoryField).toUTF8());
@@ -135,7 +135,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
similaritySettings.modify()->setEngineType(_similarityEngineTypeModel->getValue(*similarityEngineTypeRow)); similaritySettings.modify()->setEngineType(_similarityEngineTypeModel->getValue(*similarityEngineTypeRow));
auto clusterTypes {splitString(valueText(TagsField).toUTF8(), " ")}; auto clusterTypes {splitString(valueText(TagsField).toUTF8(), " ")};
scanSettings.modify()->setClusterTypes(std::set<std::string>(clusterTypes.begin(), clusterTypes.end())); scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
} }
private: private:
+4 -11
View File
@@ -54,21 +54,14 @@ class InitWizardModel : public Wt::WFormModel
void saveData() void saveData()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction(LmsApp->getDbSession().createUniqueTransaction());
// Check if a user already exist // Check if a user already exist
// If it's the case, just do nothing // If it's the case, just do nothing
if (!Database::User::getAll(LmsApp->getDboSession()).empty()) if (!Database::User::getAll(LmsApp->getDbSession()).empty())
throw LmsException("Admin user already created"); throw LmsException("Admin user already created");
// Create user Database::User::pointer user {LmsApp->getDbSession().createUser(valueText(AdminLoginField).toUTF8(), valueText(PasswordField).toUTF8())};
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().registerNew();
Database::User::pointer user = LmsApp->getDb().createUser(authUser);
// Account
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(AdminLoginField));
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
user.modify()->setType(Database::User::Type::ADMIN); user.modify()->setType(Database::User::Type::ADMIN);
} }
@@ -81,7 +74,7 @@ class InitWizardModel : public Wt::WFormModel
if (!valueText(PasswordField).empty()) if (!valueText(PasswordField).empty())
{ {
// Evaluate the strength of the password // Evaluate the strength of the password
auto res = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), auto res = Database::Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField),
valueText(AdminLoginField), ""); valueText(AdminLoginField), "");
if (!res.isValid()) if (!res.isValid())
+26 -32
View File
@@ -78,17 +78,16 @@ class UserModel : public Wt::WFormModel
void saveData() void saveData()
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
if (_userId) if (_userId)
{ {
// Update user // Update user
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) ); Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
Database::User::pointer user = LmsApp->getDb().getUser( authUser );
// Account // Account
if (!valueText(PasswordField).empty()) if (!valueText(PasswordField).empty())
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); LmsApp->getDbSession().updateUserPassword(user, valueText(PasswordField).toUTF8());
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))}; auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
if (transcodeBitrateLimitRow) if (transcodeBitrateLimitRow)
@@ -97,12 +96,7 @@ class UserModel : public Wt::WFormModel
else else
{ {
// Create user // Create user
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().registerNew(); Database::User::pointer user = LmsApp->getDbSession().createUser(valueText(LoginField).toUTF8(), valueText(PasswordField).toUTF8());
Database::User::pointer user = LmsApp->getDb().createUser(authUser);
// Account
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField));
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))}; auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
if (transcodeBitrateLimitRow ) if (transcodeBitrateLimitRow )
@@ -120,11 +114,9 @@ class UserModel : public Wt::WFormModel
if (!_userId) if (!_userId)
return; return;
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto authUser {LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) )};
auto user {LmsApp->getDb().getUser(authUser)};
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
if (user == LmsApp->getUser()) if (user == LmsApp->getUser())
throw LmsException("Cannot edit ourselves"); throw LmsException("Cannot edit ourselves");
@@ -133,12 +125,14 @@ class UserModel : public Wt::WFormModel
setValue(AudioTranscodeBitrateLimitField, _bitrateModel->getString(*transcodeBitrateLimitRow)); setValue(AudioTranscodeBitrateLimitField, _bitrateModel->getString(*transcodeBitrateLimitRow));
} }
Wt::WString getLogin() const Wt::WString getLoginName() const
{ {
if (_userId) if (_userId)
{ {
auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) ); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
return authUser.identity(Wt::Auth::Identity::LoginName);
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
return LmsApp->getDbSession().getUserLoginName(user);
} }
else else
return valueText(LoginField); return valueText(LoginField);
@@ -150,8 +144,8 @@ class UserModel : public Wt::WFormModel
if (field == LoginField) if (field == LoginField)
{ {
auto user = LmsApp->getDb().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField)); const Database::User::pointer user {LmsApp->getDbSession().getUser(valueText(LoginField).toUTF8())};
if (user.isValid()) if (user)
error = Wt::WString::tr("Lms.Admin.User.user-already-exists"); error = Wt::WString::tr("Lms.Admin.User.user-already-exists");
} }
else if (field == PasswordField) else if (field == PasswordField)
@@ -161,13 +155,13 @@ class UserModel : public Wt::WFormModel
if (Wt::asNumber(value(DemoField))) if (Wt::asNumber(value(DemoField)))
{ {
//Demo account: password must be the same as the login name //Demo account: password must be the same as the login name
if (valueText(PasswordField) != getLogin()) if (valueText(PasswordField) != getLoginName())
error = Wt::WString::tr("Lms.Admin.User.demo-password-invalid"); error = Wt::WString::tr("Lms.Admin.User.demo-password-invalid");
} }
else else
{ {
// Evaluate the strength of the password for non demo accounts // Evaluate the strength of the password for non demo accounts
auto res = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), getLogin(), ""); auto res = Database::Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), getLoginName(), "");
if (!res.isValid()) if (!res.isValid())
error = res.message(); error = res.message();
@@ -176,9 +170,9 @@ class UserModel : public Wt::WFormModel
} }
else if (field == DemoField) else if (field == DemoField)
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
if (Wt::asNumber(value(DemoField)) && Database::User::getDemo(LmsApp->getDboSession())) if (Wt::asNumber(value(DemoField)) && Database::User::getDemo(LmsApp->getDbSession()))
error = Wt::WString::tr("Lms.Admin.User.demo-account-already-exists"); error = Wt::WString::tr("Lms.Admin.User.demo-account-already-exists");
} }
@@ -226,23 +220,23 @@ UserView::refreshView()
auto userId = readAs<Database::IdType>(wApp->internalPathNextPart("/admin/user/")); auto userId = readAs<Database::IdType>(wApp->internalPathNextPart("/admin/user/"));
LMS_LOG(UI, DEBUG) << "userId = " << (userId ? std::to_string(*userId) : "none");
clear(); clear();
Wt::WTemplateFormView* t = addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.User.template")); Wt::WTemplateFormView* t {addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.User.template"))};
auto model = std::make_shared<UserModel>(userId); auto model {std::make_shared<UserModel>(userId)};
if (userId) if (userId)
{ {
auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*userId) ); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto name = authUser.identity(Wt::Auth::Identity::LoginName);
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(name), Wt::TextFormat::Plain); const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *userId)};
const std::string loginName {LmsApp->getDbSession().getUserLoginName(user)};
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(loginName), Wt::TextFormat::Plain);
t->setCondition("if-has-last-login-attempt", true); t->setCondition("if-has-last-login-attempt", true);
Wt::WLineEdit *lastLoginAttempt = t->bindNew<Wt::WLineEdit>("last-login-attempt"); Wt::WLineEdit *lastLoginAttempt {t->bindNew<Wt::WLineEdit>("last-login-attempt")};
lastLoginAttempt->setText(authUser.lastLoginAttempt().toString()); lastLoginAttempt->setText(LmsApp->getDbSession().getUserLastLoginAttempt(user).toString());
lastLoginAttempt->setEnabled(false); lastLoginAttempt->setEnabled(false);
} }
else else
+13 -17
View File
@@ -59,22 +59,16 @@ UsersView::refreshView()
_container->clear(); _container->clear();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto users = Database::User::getAll(LmsApp->getDboSession()); auto users = Database::User::getAll(LmsApp->getDbSession());
for (auto user : users) for (const auto& user : users)
{ {
auto userId = std::to_string(user.id()); const Database::IdType userId {user.id()};
Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"));
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId(userId); Wt::WTemplate* entry {_container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"))};
if (!authUser.isValid()) {
LMS_LOG(UI, ERROR) << "Skipping invalid userId = " << user.id();
continue;
}
auto login = authUser.identity(Wt::Auth::Identity::LoginName); entry->bindString("name", LmsApp->getDbSession().getUserLoginName(user), Wt::TextFormat::Plain);
entry->bindString("name", login, Wt::TextFormat::Plain);
// Create tag // Create tag
if (user->isAdmin() || user->isDemo()) if (user->isAdmin() || user->isDemo())
@@ -106,16 +100,18 @@ UsersView::refreshView()
{ {
if (btn == Wt::StandardButton::Yes) if (btn == Wt::StandardButton::Yes)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), userId)};
if (user)
LmsApp->getDbSession().removeUser(user);
auto authUser = LmsApp->getDb().getUserDatabase().findWithId(userId);
auto user = LmsApp->getDb().getUser(authUser);
LmsApp->getDb().getUserDatabase().deleteUser( authUser );
user.remove();
_container->removeWidget(entry); _container->removeWidget(entry);
} }
else else
{
delBtn->removeChild(msgBox); delBtn->removeChild(msgBox);
}
}); });
msgBox->show(); msgBox->show();
+7 -10
View File
@@ -63,22 +63,19 @@ ArtistInfo::refresh()
if (!artistId) if (!artistId)
return; return;
auto artistsIds = getService<Similarity::Searcher>()->getSimilarArtists(LmsApp->getDboSession(), *artistId, 5); const std::vector<Database::IdType> artistsIds {getService<Similarity::Searcher>()->getSimilarArtists(LmsApp->getDbSession(), *artistId, 5)};
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
std::vector<Database::Artist::pointer> artists; for (Database::IdType artistId : artistsIds)
for (auto artistId : artistsIds)
{ {
auto artist = Database::Artist::getById(LmsApp->getDboSession(), artistId); Database::Artist::pointer artist {Database::Artist::getById(LmsApp->getDbSession(), artistId)};
if (!artist)
continue;
if (artist)
artists.push_back(artist);
}
for (auto artist : artists)
_similarArtistsContainer->addNew<ArtistLink>(artist); _similarArtistsContainer->addNew<ArtistLink>(artist);
} }
}
} // namespace UserInterface } // namespace UserInterface
+4 -4
View File
@@ -66,22 +66,22 @@ Artist::refresh()
if (!artistId) if (!artistId)
return; return;
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto artist = Database::Artist::getById(LmsApp->getDboSession(), *artistId);
const Database::Artist::pointer artist = Database::Artist::getById(LmsApp->getDbSession(), *artistId);
if (!artist) if (!artist)
{ {
LmsApp->goHome(); LmsApp->goHome();
return; return;
} }
Wt::WTemplate* t = addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template")); Wt::WTemplate* t {addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template"))};
t->addFunction("tr", &Wt::WTemplate::Functions::tr); t->addFunction("tr", &Wt::WTemplate::Functions::tr);
Wt::WContainerWidget* clusterContainers = t->bindNew<Wt::WContainerWidget>("clusters"); Wt::WContainerWidget* clusterContainers = t->bindNew<Wt::WContainerWidget>("clusters");
{ {
auto clusterTypes = ScanSettings::get(LmsApp->getDboSession())->getClusterTypes(); auto clusterTypes = ScanSettings::get(LmsApp->getDbSession())->getClusterTypes();
auto clusterGroups = artist->getClusterGroups(clusterTypes, 3); auto clusterGroups = artist->getClusterGroups(clusterTypes, 3);
for (auto clusters : clusterGroups) for (auto clusters : clusterGroups)
+7 -6
View File
@@ -58,22 +58,23 @@ ArtistsInfo::refreshRecentlyAdded()
{ {
auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1); auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1);
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto artists = Artist::getLastAdded(LmsApp->getDboSession(), after, 5); const std::vector<Database::Artist::pointer> artists {Artist::getLastAdded(LmsApp->getDbSession(), after, 5)};
_recentlyAddedContainer->clear(); _recentlyAddedContainer->clear();
for (auto artist : artists) for (const Database::Artist::pointer& artist : artists)
_recentlyAddedContainer->addNew<ArtistLink>(artist); _recentlyAddedContainer->addNew<ArtistLink>(artist);
} }
void void
ArtistsInfo::refreshMostPlayed() ArtistsInfo::refreshMostPlayed()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto artists = LmsApp->getUser()->getPlayedTrackList()->getTopArtists(5);
const std::vector<Database::Artist::pointer> artists {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopArtists(5)};
_mostPlayedContainer->clear(); _mostPlayedContainer->clear();
for (auto artist : artists) for (const Database::Artist::pointer& artist : artists)
_mostPlayedContainer->addNew<ArtistLink>(artist); _mostPlayedContainer->addNew<ArtistLink>(artist);
} }
+6 -6
View File
@@ -73,17 +73,17 @@ Artists::addSome()
auto clusterIds = _filters->getClusterIds(); auto clusterIds = _filters->getClusterIds();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
bool moreResults; bool moreResults {};
auto artists = Artist::getByFilter(LmsApp->getDboSession(), const std::vector<Artist::pointer> artists {Artist::getByFilter(LmsApp->getDbSession(),
clusterIds, clusterIds,
searchKeywords, searchKeywords,
_container->count(), 20, moreResults); _container->count(), 20, moreResults)};
for (auto artist : artists) for (const auto& artist : artists)
{ {
Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry")); Wt::WTemplate* entry {_container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry"))};
entry->bindWidget("name", LmsApplication::createArtistAnchor(artist)); entry->bindWidget("name", LmsApplication::createArtistAnchor(artist));
} }
+43 -42
View File
@@ -25,6 +25,7 @@
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Track.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
@@ -66,9 +67,7 @@ handleContentsPathChange(Wt::WStackedWidget* stack)
{ "/tracks", IdxTracks }, { "/tracks", IdxTracks },
}; };
LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; for (const auto& index : indexes)
for (auto index : indexes)
{ {
if (wApp->internalPathMatches(index.first)) if (wApp->internalPathMatches(index.first))
{ {
@@ -182,102 +181,104 @@ Explore::Explore()
handleInfoPathChange(infoStack); handleInfoPathChange(infoStack);
} }
// TODO SQL this? static
static std::vector<Database::Track::pointer> getArtistTracks(Wt::Dbo::Session& session, Database::IdType artistId, std::set<Database::IdType> clusters) std::vector<Database::IdType>
getArtistTracks(Database::Session& session, Database::IdType artistId, const std::set<Database::IdType>& clusters)
{ {
std::vector<Database::Track::pointer> res; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto artist = Database::Artist::getById(session, artistId); Database::Artist::pointer artist {Database::Artist::getById(session, artistId)};
if (!artist) if (!artist)
return res; return {};
res = artist->getTracks(); // TODO handle clusters here
const std::vector<Database::Track::pointer> tracks {artist->getTracks()};
std::vector<Database::IdType> res;
res.reserve(tracks.size());
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track.id(); });
return res; return res;
} }
static std::vector<Database::Track::pointer> getReleaseTracks(Wt::Dbo::Session& session, Database::IdType releaseId, std::set<Database::IdType> clusters) static
std::vector<Database::IdType>
getReleaseTracks(Database::Session& session, Database::IdType releaseId, std::set<Database::IdType> clusters)
{ {
std::vector<Database::Track::pointer> res; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto release = Database::Release::getById(session, releaseId); Database::Release::pointer release {Database::Release::getById(session, releaseId)};
if (!release) if (!release)
return res; return {};
res = release->getTracks(clusters); const std::vector<Database::Track::pointer> tracks {release->getTracks(clusters)};
std::vector<Database::IdType> res;
res.reserve(tracks.size());
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track.id(); });
return res; return res;
} }
static std::vector<Database::Track::pointer> getTrack(Wt::Dbo::Session& session, Database::IdType trackId) static
std::vector<Database::IdType>
getTrack(Database::Session& session, Database::IdType trackId)
{ {
std::vector<Database::Track::pointer> res; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto track = Database::Track::getById(session, trackId); Database::Track::pointer track {Database::Track::getById(session, trackId)};
if (track) if (!track)
res.push_back(track); return {};
return res; return {track.id()};
} }
void void
Explore::handleArtistAdd(Database::IdType id) Explore::handleArtistAdd(Database::IdType id)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); tracksAdd.emit(getArtistTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
tracksAdd.emit(getArtistTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
} }
void void
Explore::handleArtistPlay(Database::IdType id) Explore::handleArtistPlay(Database::IdType id)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); tracksPlay.emit(getArtistTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
tracksPlay.emit(getArtistTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
} }
void void
Explore::handleReleaseAdd(Database::IdType id) Explore::handleReleaseAdd(Database::IdType id)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); tracksAdd.emit(getReleaseTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
tracksAdd.emit(getReleaseTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
} }
void void
Explore::handleReleasePlay(Database::IdType id) Explore::handleReleasePlay(Database::IdType id)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); tracksPlay.emit(getReleaseTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
tracksPlay.emit(getReleaseTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
} }
void void
Explore::handleTrackAdd(Database::IdType id) Explore::handleTrackAdd(Database::IdType id)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); tracksAdd.emit(getTrack(LmsApp->getDbSession(), id));
tracksAdd.emit(getTrack(LmsApp->getDboSession(), id));
} }
void void
Explore::handleTrackPlay(Database::IdType id) Explore::handleTrackPlay(Database::IdType id)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); tracksPlay.emit(getTrack(LmsApp->getDbSession(), id));
tracksPlay.emit(getTrack(LmsApp->getDboSession(), id));
} }
void void
Explore::handleTracksAdd(std::vector<Database::Track::pointer> tracks) Explore::handleTracksAdd(const std::vector<Database::IdType>& trackIds)
{ {
tracksAdd.emit(tracks); tracksAdd.emit(trackIds);
} }
void void
Explore::handleTracksPlay(std::vector<Database::Track::pointer> tracks) Explore::handleTracksPlay(const std::vector<Database::IdType>& trackIds)
{ {
tracksPlay.emit(tracks); tracksPlay.emit(trackIds);
} }
} // namespace UserInterface } // namespace UserInterface
+10 -11
View File
@@ -21,7 +21,6 @@
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include "database/Track.hpp"
#include "database/Types.hpp" #include "database/Types.hpp"
namespace UserInterface { namespace UserInterface {
@@ -33,19 +32,19 @@ class Explore : public Wt::WTemplate
public: public:
Explore(); Explore();
Wt::Signal<std::vector<Database::Track::pointer>> tracksAdd; Wt::Signal<std::vector<Database::IdType>> tracksAdd;
Wt::Signal<std::vector<Database::Track::pointer>> tracksPlay; Wt::Signal<std::vector<Database::IdType>> tracksPlay;
private: private:
void handleArtistAdd(Database::IdType id); void handleArtistAdd(Database::IdType artistId);
void handleArtistPlay(Database::IdType id); void handleArtistPlay(Database::IdType artistId);
void handleReleaseAdd(Database::IdType id); void handleReleaseAdd(Database::IdType releaseId);
void handleReleasePlay(Database::IdType id); void handleReleasePlay(Database::IdType releaseId);
void handleTrackAdd(Database::IdType id); void handleTrackAdd(Database::IdType trackId);
void handleTrackPlay(Database::IdType id); void handleTrackPlay(Database::IdType trackId);
void handleTracksAdd(std::vector<Database::Track::pointer> tracks); void handleTracksAdd(const std::vector<Database::IdType>& trackIds);
void handleTracksPlay(std::vector<Database::Track::pointer> tracks); void handleTracksPlay(const std::vector<Database::IdType>& trackIds);
Filters* _filters; Filters* _filters;
}; };
+23 -25
View File
@@ -49,43 +49,41 @@ Filters::showDialog()
// Populate data // Populate data
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto types = Database::ClusterType::getAll(LmsApp->getDboSession()); const auto types {Database::ClusterType::getAll(LmsApp->getDbSession())};
for (const Database::ClusterType::pointer& type : types)
for (auto type : types)
typeCombo->addItem(Wt::WString::fromUTF8(type->getName())); typeCombo->addItem(Wt::WString::fromUTF8(type->getName()));
if (!types.empty()) if (!types.empty())
{ {
auto values = types.front()->getClusters(); const auto values {types.front()->getClusters()};
for (auto value : values) for (const Database::Cluster::pointer& value : values)
{ {
if (_filterIds.find(value.id()) == _filterIds.end()) if (_filterIds.find(value.id()) == _filterIds.end())
valueCombo->addItem(Wt::WString::fromUTF8(value->getName())); valueCombo->addItem(Wt::WString::fromUTF8(value->getName()));
} }
} }
} }
typeCombo->changed().connect(std::bind([=] typeCombo->changed().connect([=]
{ {
auto name = typeCombo->valueText().toUTF8(); const std::string name {typeCombo->valueText().toUTF8()};
valueCombo->clear(); valueCombo->clear();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto clusterType = Database::ClusterType::getByName(LmsApp->getDboSession(), name); auto clusterType = Database::ClusterType::getByName(LmsApp->getDbSession(), name);
auto values = clusterType->getClusters(); const auto values = clusterType->getClusters();
for (auto value : values) for (const Database::Cluster::pointer& value : values)
{ {
if (_filterIds.find(value.id()) == _filterIds.end()) if (_filterIds.find(value.id()) == _filterIds.end())
valueCombo->addItem(Wt::WString::fromUTF8(value->getName())); valueCombo->addItem(Wt::WString::fromUTF8(value->getName()));
} }
})); });
dialog->setModal(true); dialog->setModal(true);
dialog->setMovable(false); dialog->setMovable(false);
@@ -93,26 +91,26 @@ Filters::showDialog()
dialog->setResizable(false); dialog->setResizable(false);
dialog->setClosable(false); dialog->setClosable(false);
dialog->finished().connect(std::bind([=] dialog->finished().connect([=]
{ {
if (dialog->result() != Wt::DialogCode::Accepted) if (dialog->result() != Wt::DialogCode::Accepted)
return; return;
auto type = typeCombo->valueText().toUTF8(); const std::string type {typeCombo->valueText().toUTF8()};
auto value = valueCombo->valueText().toUTF8(); const std::string value {valueCombo->valueText().toUTF8()};
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto clusterType = Database::ClusterType::getByName(LmsApp->getDboSession(), type); Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(LmsApp->getDbSession(), type)};
if (!clusterType) if (!clusterType)
return; return;
auto cluster = clusterType->getCluster(value); Database::Cluster::pointer cluster {clusterType->getCluster(value)};
if (!cluster) if (!cluster)
return; return;
add(cluster.id()); add(cluster.id());
})); });
dialog->show(); dialog->show();
} }
@@ -120,17 +118,17 @@ Filters::showDialog()
void void
Filters::add(Database::IdType clusterId) Filters::add(Database::IdType clusterId)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto cluster = Database::Cluster::getById(LmsApp->getDboSession(), clusterId); Database::Cluster::pointer cluster {Database::Cluster::getById(LmsApp->getDbSession(), clusterId)};
if (!cluster) if (!cluster)
return; return;
auto res = _filterIds.insert(clusterId); auto res {_filterIds.insert(clusterId)};
if (!res.second) if (!res.second)
return; return;
auto filter = _filters->addWidget(LmsApp->createCluster(cluster, true)); auto filter {_filters->addWidget(LmsApp->createCluster(cluster, true))};
filter->clicked().connect(std::bind([=] filter->clicked().connect(std::bind([=]
{ {
_filters->removeWidget(filter); _filters->removeWidget(filter);
+5 -5
View File
@@ -68,11 +68,11 @@ ReleaseInfo::refresh()
if (!releaseId) if (!releaseId)
return; return;
std::vector<Database::IdType> releasesIds {getService<Similarity::Searcher>()->getSimilarReleases(LmsApp->getDboSession(), *releaseId, 5)}; const std::vector<Database::IdType> releasesIds {getService<Similarity::Searcher>()->getSimilarReleases(LmsApp->getDbSession(), *releaseId, 5)};
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
Database::Release::pointer release {Database::Release::getById(LmsApp->getDboSession(), *releaseId)}; Database::Release::pointer release {Database::Release::getById(LmsApp->getDbSession(), *releaseId)};
if (!release) if (!release)
return; return;
@@ -102,13 +102,13 @@ ReleaseInfo::refresh()
std::vector<Database::Release::pointer> similarReleases; std::vector<Database::Release::pointer> similarReleases;
for (Database::IdType id : releasesIds) for (Database::IdType id : releasesIds)
{ {
Database::Release::pointer similarRelease {Database::Release::getById(LmsApp->getDboSession(), id)}; Database::Release::pointer similarRelease {Database::Release::getById(LmsApp->getDbSession(), id)};
if (similarRelease) if (similarRelease)
similarReleases.emplace_back(similarRelease); similarReleases.emplace_back(similarRelease);
} }
for (const auto& similarRelease : similarReleases) for (const Database::Release::pointer& similarRelease : similarReleases)
_similarReleasesContainer->addNew<ReleaseLink>(similarRelease); _similarReleasesContainer->addNew<ReleaseLink>(similarRelease);
} }
+6 -6
View File
@@ -67,9 +67,9 @@ Release::refresh()
if (!releaseId) if (!releaseId)
return; return;
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto release {Database::Release::getById(LmsApp->getDboSession(), *releaseId)}; const Database::Release::pointer release {Database::Release::getById(LmsApp->getDbSession(), *releaseId)};
if (!release) if (!release)
{ {
LmsApp->goHome(); LmsApp->goHome();
@@ -118,12 +118,12 @@ Release::refresh()
Wt::WContainerWidget* clusterContainers {t->bindNew<Wt::WContainerWidget>("clusters")}; Wt::WContainerWidget* clusterContainers {t->bindNew<Wt::WContainerWidget>("clusters")};
{ {
auto clusterTypes {ScanSettings::get(LmsApp->getDboSession())->getClusterTypes()}; const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
auto clusterGroups {release->getClusterGroups(clusterTypes, 3)}; const auto clusterGroups {release->getClusterGroups(clusterTypes, 3)};
for (auto clusters : clusterGroups) for (const auto& clusters : clusterGroups)
{ {
for (auto cluster : clusters) for (const auto& cluster : clusters)
{ {
auto clusterId {cluster.id()}; auto clusterId {cluster.id()};
auto entry {clusterContainers->addWidget(LmsApp->createCluster(cluster))}; auto entry {clusterContainers->addWidget(LmsApp->createCluster(cluster))};
+6 -6
View File
@@ -58,24 +58,24 @@ ReleasesInfo::refreshRecentlyAdded()
{ {
auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1); auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1);
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto releases {Release::getLastAdded(LmsApp->getDboSession(), after, 0, 5)}; const auto releases {Release::getLastAdded(LmsApp->getDbSession(), after, 0, 5)};
_recentlyAddedContainer->clear(); _recentlyAddedContainer->clear();
for (auto release : releases) for (const Database::Release::pointer& release : releases)
_recentlyAddedContainer->addNew<ReleaseLink>(release); _recentlyAddedContainer->addNew<ReleaseLink>(release);
} }
void void
ReleasesInfo::refreshMostPlayed() ReleasesInfo::refreshMostPlayed()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto releases = LmsApp->getUser()->getPlayedTrackList()->getTopReleases(5); const auto releases {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopReleases(5)};
_mostPlayedContainer->clear(); _mostPlayedContainer->clear();
for (auto release : releases) for (const Database::Release::pointer& release : releases)
_mostPlayedContainer->addNew<ReleaseLink>(release); _mostPlayedContainer->addNew<ReleaseLink>(release);
} }
+6 -7
View File
@@ -71,18 +71,17 @@ Releases::refresh()
void void
Releases::addSome() Releases::addSome()
{ {
auto searchKeywords = splitString(_search->text().toUTF8(), " "); const auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
const auto clusterIds {_filters->getClusterIds()};
auto clusterIds = _filters->getClusterIds(); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
bool moreResults; bool moreResults;
auto releases = Release::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, _container->count(), 20, moreResults); const auto releases {Release::getByFilter(LmsApp->getDbSession(), clusterIds, searchKeywords, _container->count(), 20, moreResults)};
for (auto release : releases) for (const Database::Release::pointer& release : releases)
{ {
auto releaseId = release.id(); const Database::IdType releaseId {release.id()};
Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Releases.template.entry")); Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Releases.template.entry"));
entry->addFunction("tr", Wt::WTemplate::Functions::tr); entry->addFunction("tr", Wt::WTemplate::Functions::tr);
+5 -5
View File
@@ -74,10 +74,10 @@ TracksInfo::TracksInfo()
void void
TracksInfo::refreshRecentlyAdded() TracksInfo::refreshRecentlyAdded()
{ {
auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1); const auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1)};
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto tracks = Track::getLastAdded(LmsApp->getDboSession(), after, 5); const auto tracks {Track::getLastAdded(LmsApp->getDbSession(), after, 5)};
_recentlyAddedContainer->clear(); _recentlyAddedContainer->clear();
addEntries(_recentlyAddedContainer, tracks); addEntries(_recentlyAddedContainer, tracks);
@@ -86,8 +86,8 @@ TracksInfo::refreshRecentlyAdded()
void void
TracksInfo::refreshMostPlayed() TracksInfo::refreshMostPlayed()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto tracks = LmsApp->getUser()->getPlayedTrackList()->getTopTracks(5); const auto tracks {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopTracks(5)};
_mostPlayedContainer->clear(); _mostPlayedContainer->clear();
addEntries(_mostPlayedContainer, tracks); addEntries(_mostPlayedContainer, tracks);
+18 -13
View File
@@ -52,14 +52,12 @@ _filters(filters)
Wt::WText* playBtn = bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML); Wt::WText* playBtn = bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=] playBtn->clicked().connect(std::bind([=]
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getTracks()); tracksPlay.emit(getTracks());
})); }));
Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML); Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=] addBtn->clicked().connect(std::bind([=]
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getTracks()); tracksAdd.emit(getTracks());
})); }));
@@ -76,18 +74,24 @@ _filters(filters)
filters->updated().connect(this, &Tracks::refresh); filters->updated().connect(this, &Tracks::refresh);
} }
std::vector<Database::Track::pointer> std::vector<Database::IdType>
Tracks::getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults) Tracks::getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults)
{ {
auto searchKeywords {splitString(_search->text().toUTF8(), " ")}; const auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
auto clusterIds {_filters->getClusterIds()}; const auto clusterIds {_filters->getClusterIds()};
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const auto tracks {Track::getByFilter(LmsApp->getDbSession(), clusterIds, searchKeywords, offset, size, moreResults)};
std::vector<Database::IdType> res;
res.reserve(tracks.size());
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track.id(); });
return res;
return Track::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, offset, size, moreResults);
} }
std::vector<Database::Track::pointer> std::vector<Database::IdType>
Tracks::getTracks() Tracks::getTracks()
{ {
bool moreResults; bool moreResults;
@@ -104,14 +108,15 @@ Tracks::refresh()
void void
Tracks::addSome() Tracks::addSome()
{ {
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
bool moreResults; bool moreResults;
auto tracks {getTracks(_tracksContainer->count(), 20, moreResults)}; const std::vector<Database::IdType> trackIds {getTracks(_tracksContainer->count(), 20, moreResults)};
for (auto track : tracks) for (const Database::IdType trackId : trackIds)
{ {
auto trackId {track.id()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
Wt::WTemplate* entry {_tracksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))}; Wt::WTemplate* entry {_tracksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain); entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
+6 -5
View File
@@ -19,13 +19,14 @@
#pragma once #pragma once
#include <boost/optional.hpp>
#include <Wt/WContainerWidget.h> #include <Wt/WContainerWidget.h>
#include <Wt/WLineEdit.h> #include <Wt/WLineEdit.h>
#include <Wt/WPushButton.h> #include <Wt/WPushButton.h>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include "database/Types.hpp" #include "database/Types.hpp"
#include "database/Track.hpp"
namespace UserInterface { namespace UserInterface {
@@ -38,15 +39,15 @@ class Tracks : public Wt::WTemplate
Wt::Signal<Database::IdType> trackAdd; Wt::Signal<Database::IdType> trackAdd;
Wt::Signal<Database::IdType> trackPlay; Wt::Signal<Database::IdType> trackPlay;
Wt::Signal<std::vector<Database::Track::pointer>> tracksAdd; Wt::Signal<std::vector<Database::IdType>> tracksAdd;
Wt::Signal<std::vector<Database::Track::pointer>> tracksPlay; Wt::Signal<std::vector<Database::IdType>> tracksPlay;
private: private:
void refresh(); void refresh();
void addSome(); void addSome();
std::vector<Database::Track::pointer> getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults); std::vector<Database::IdType> getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults);
std::vector<Database::Track::pointer> getTracks(); std::vector<Database::IdType> getTracks();
Wt::WContainerWidget* _tracksContainer; Wt::WContainerWidget* _tracksContainer;
Wt::WPushButton* _showMore; Wt::WPushButton* _showMore;
+4 -8
View File
@@ -38,9 +38,7 @@ AudioResource:: ~AudioResource()
std::string std::string
AudioResource::getUrl(Database::IdType trackId) const AudioResource::getUrl(Database::IdType trackId) const
{ {
std::string res = url()+ "&trackid=" + std::to_string(trackId); return url()+ "&trackid=" + std::to_string(trackId);
return res;
} }
void void
@@ -82,14 +80,12 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
return; return;
} }
// transactions are not thread safe // DbSession are not thread safe
{ {
Wt::WApplication::UpdateLock lock(LmsApp); Wt::WApplication::UpdateLock lock(LmsApp);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
Database::Track::pointer track = Database::Track::getById(LmsApp->getDboSession(), trackId);
if (!track) if (!track)
{ {
LMS_LOG(UI, ERROR) << "Missing track"; LMS_LOG(UI, ERROR) << "Missing track";
-1
View File
@@ -23,7 +23,6 @@
#include "av/AvTranscoder.hpp" #include "av/AvTranscoder.hpp"
#include "database/DatabaseHandler.hpp"
#include "database/Types.hpp" #include "database/Types.hpp"
+9 -9
View File
@@ -65,7 +65,7 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
if (!sizeStr) if (!sizeStr)
return; return;
auto size = readAs<std::size_t>(*sizeStr); const auto size {readAs<std::size_t>(*sizeStr)};
if (!size || *size > maxSize) if (!size || *size > maxSize)
return; return;
@@ -73,26 +73,26 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
if (trackIdStr) if (trackIdStr)
{ {
auto trackId = readAs<Database::IdType>(*trackIdStr); const auto trackId {readAs<Database::IdType>(*trackIdStr)};
if (!trackId) if (!trackId)
return; return;
// transactions are not thread safe // DbSession are not thread safe
{ {
Wt::WApplication::UpdateLock lock(LmsApp); Wt::WApplication::UpdateLock lock {LmsApp};
cover = getService<CoverArt::Grabber>()->getFromTrack(LmsApp->getDboSession(), *trackId, Image::Format::JPEG, *size); cover = getService<CoverArt::Grabber>()->getFromTrack(LmsApp->getDbSession(), *trackId, Image::Format::JPEG, *size);
} }
} }
else if (releaseIdStr) else if (releaseIdStr)
{ {
auto releaseId = readAs<Database::IdType>(*releaseIdStr); const auto releaseId {readAs<Database::IdType>(*releaseIdStr)};
if (!releaseId) if (!releaseId)
return; return;
// transactions are not thread safe // DbSession are not thread safe
{ {
Wt::WApplication::UpdateLock lock(LmsApp); Wt::WApplication::UpdateLock lock {LmsApp};
cover = getService<CoverArt::Grabber>()->getFromRelease(LmsApp->getDboSession(), *releaseId, Image::Format::JPEG, *size); cover = getService<CoverArt::Grabber>()->getFromRelease(LmsApp->getDbSession(), *releaseId, Image::Format::JPEG, *size);
} }
} }
else else
+1 -2
View File
@@ -24,7 +24,6 @@
#include <Wt/WResource.h> #include <Wt/WResource.h>
#include "database/DatabaseHandler.hpp"
#include "database/Types.hpp" #include "database/Types.hpp"
#include "image/Image.hpp" #include "image/Image.hpp"
@@ -35,7 +34,7 @@ namespace UserInterface {
class ImageResource : public Wt::WResource class ImageResource : public Wt::WResource
{ {
public: public:
static const std::size_t maxSize = 512; static const std::size_t maxSize {512};
~ImageResource(); ~ImageResource();
+3 -2
View File
@@ -64,9 +64,10 @@ readAs(const std::string& str)
std::vector<std::string> std::vector<std::string>
splitString(const std::string& string, const std::string& separators) splitString(const std::string& string, const std::string& separators)
{ {
std::vector<std::string> res; std::string str {stringTrim(string, separators)};
boost::algorithm::split(res, string, boost::is_any_of(separators), boost::token_compress_on); std::vector<std::string> res;
boost::algorithm::split(res, str, boost::is_any_of(separators), boost::token_compress_on);
return res; return res;
} }
+9
View File
@@ -22,6 +22,7 @@
#include <chrono> #include <chrono>
#include <list> #include <list>
#include <map> #include <map>
#include <random>
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
@@ -104,4 +105,12 @@ constexpr const T& clamp( T v, T lo, T hi, Compare comp = {})
return comp(v, lo) ? lo : comp(hi, v) ? hi : v; return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
} }
template <typename Container>
void
shuffleContainer(Container& container)
{
auto now {std::chrono::system_clock::now()};
std::mt19937 randGenerator(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
std::shuffle(std::begin(container), std::end(container), randGenerator);
}
+2 -1
View File
@@ -15,12 +15,13 @@ database_SOURCES = \
$(srcdir)/database/DatabaseTest.cpp \ $(srcdir)/database/DatabaseTest.cpp \
$(top_srcdir)/src/database/Artist.cpp \ $(top_srcdir)/src/database/Artist.cpp \
$(top_srcdir)/src/database/Cluster.cpp \ $(top_srcdir)/src/database/Cluster.cpp \
$(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/Database.cpp \
$(top_srcdir)/src/database/TrackArtistLink.cpp \ $(top_srcdir)/src/database/TrackArtistLink.cpp \
$(top_srcdir)/src/database/TrackFeatures.cpp \ $(top_srcdir)/src/database/TrackFeatures.cpp \
$(top_srcdir)/src/database/TrackList.cpp \ $(top_srcdir)/src/database/TrackList.cpp \
$(top_srcdir)/src/database/Release.cpp \ $(top_srcdir)/src/database/Release.cpp \
$(top_srcdir)/src/database/ScanSettings.cpp \ $(top_srcdir)/src/database/ScanSettings.cpp \
$(top_srcdir)/src/database/Session.cpp \
$(top_srcdir)/src/database/SimilaritySettings.cpp \ $(top_srcdir)/src/database/SimilaritySettings.cpp \
$(top_srcdir)/src/database/SqlQuery.cpp \ $(top_srcdir)/src/database/SqlQuery.cpp \
$(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/Track.cpp \
+163 -127
View File
@@ -23,7 +23,7 @@
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
#include "database/DatabaseHandler.hpp" #include "database/Database.hpp"
#include "database/TrackList.hpp" #include "database/TrackList.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
@@ -50,20 +50,33 @@ class ScopedFileDeleter final
static static
void void
testSingleTrack(Wt::Dbo::Session& session) testRemoveDefaultEntries(Session& session)
{
{
auto transaction {session.createUniqueTransaction()};
auto clusterTypes {ClusterType::getAll(session)};
for (auto& clusterType : clusterTypes)
clusterType.remove();
}
}
static
void
testSingleTrack(Session& session)
{ {
IdType trackId {}; IdType trackId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "MyTrackFile")}; auto track {Track::create(session, "MyTrackFile")};
CHECK(track); CHECK(track);
session.flush(); CHECK(IdIsValid(track.id()));
trackId = track.id(); trackId = track.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
CHECK(track); CHECK(track);
@@ -75,20 +88,19 @@ testSingleTrack(Wt::Dbo::Session& session)
static static
void void
testSingleArtist(Wt::Dbo::Session& session) testSingleArtist(Session& session)
{ {
IdType artistId {}; IdType artistId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::create(session, "MyArtist")}; auto artist {Artist::create(session, "MyArtist")};
CHECK(artist); CHECK(artist);
session.flush();
artistId = artist.id(); artistId = artist.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Artist::getAll(session).size() == 1); CHECK(Artist::getAll(session).size() == 1);
@@ -98,7 +110,7 @@ testSingleArtist(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
CHECK(artist); CHECK(artist);
@@ -108,20 +120,19 @@ testSingleArtist(Wt::Dbo::Session& session)
static static
void void
testSingleRelease(Wt::Dbo::Session& session) testSingleRelease(Session& session)
{ {
IdType releaseId {}; IdType releaseId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto release {Release::create(session, "MyRelease")}; auto release {Release::create(session, "MyRelease")};
CHECK(release); CHECK(release);
session.flush();
releaseId = release.id(); releaseId = release.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto releases {Release::getAllOrphans(session)}; auto releases {Release::getAllOrphans(session)};
CHECK(releases.size() == 1); CHECK(releases.size() == 1);
@@ -133,7 +144,7 @@ testSingleRelease(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto release {Release::getById(session, releaseId)}; auto release {Release::getById(session, releaseId)};
CHECK(release); CHECK(release);
@@ -143,24 +154,23 @@ testSingleRelease(Wt::Dbo::Session& session)
static static
void void
testSingleCluster(Wt::Dbo::Session& session) testSingleCluster(Session& session)
{ {
IdType clusterTypeId {}; IdType clusterTypeId {};
IdType clusterId {}; IdType clusterId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto clusterType {ClusterType::create(session, "MyType")}; auto clusterType {ClusterType::create(session, "MyType")};
CHECK(clusterType); CHECK(clusterType);
auto cluster {Cluster::create(session, clusterType, "MyCluster")}; auto cluster {Cluster::create(session, clusterType, "MyCluster")};
CHECK(cluster); CHECK(cluster);
session.flush();
clusterTypeId = clusterType.id(); clusterTypeId = clusterType.id();
clusterId = cluster.id(); clusterId = cluster.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto clusters {Cluster::getAll(session)}; auto clusters {Cluster::getAll(session)};
CHECK(clusters.size() == 1); CHECK(clusters.size() == 1);
@@ -180,7 +190,7 @@ testSingleCluster(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto cluster {Cluster::getById(session, clusterId)}; auto cluster {Cluster::getById(session, clusterId)};
CHECK(cluster); CHECK(cluster);
@@ -191,7 +201,7 @@ testSingleCluster(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto clusterType {ClusterType::getById(session, clusterTypeId)}; auto clusterType {ClusterType::getById(session, clusterTypeId)};
CHECK(clusterType); CHECK(clusterType);
@@ -202,12 +212,12 @@ testSingleCluster(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleArtist(Wt::Dbo::Session& session) testSingleTrackSingleArtist(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType artistId {}; IdType artistId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "dummy")}; auto track {Track::create(session, "dummy")};
CHECK(track); CHECK(track);
@@ -216,18 +226,17 @@ testSingleTrackSingleArtist(Wt::Dbo::Session& session)
auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)};
session.flush();
trackId = track.id(); trackId = track.id();
artistId = artist.id(); artistId = artist.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Artist::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
CHECK(track); CHECK(track);
@@ -249,7 +258,7 @@ testSingleTrackSingleArtist(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
CHECK(artist); CHECK(artist);
@@ -268,12 +277,12 @@ testSingleTrackSingleArtist(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session) testSingleTrackSingleArtistMultiRoles(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType artistId {}; IdType artistId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "MyTrack")}; auto track {Track::create(session, "MyTrack")};
auto artist {Artist::create(session, "MyArtist")}; auto artist {Artist::create(session, "MyArtist")};
@@ -282,18 +291,17 @@ testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session)
TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::ReleaseArtist); TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::ReleaseArtist);
TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Writer); TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Writer);
session.flush();
trackId = track.id(); trackId = track.id();
artistId = artist.id(); artistId = artist.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Artist::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
CHECK(track); CHECK(track);
@@ -317,7 +325,7 @@ testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
@@ -329,13 +337,13 @@ testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session)
static static
void void
testSingleTrackMultiArtists(Wt::Dbo::Session& session) testSingleTrackMultiArtists(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType artist1Id {}; IdType artist1Id {};
IdType artist2Id {}; IdType artist2Id {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "dummy")}; auto track {Track::create(session, "dummy")};
auto artist1 {Artist::create(session, "artist1")}; auto artist1 {Artist::create(session, "artist1")};
@@ -344,7 +352,6 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session)
TrackArtistLink::create(session, track, artist1, TrackArtistLink::Type::Artist); TrackArtistLink::create(session, track, artist1, TrackArtistLink::Type::Artist);
TrackArtistLink::create(session, track, artist2, TrackArtistLink::Type::Artist); TrackArtistLink::create(session, track, artist2, TrackArtistLink::Type::Artist);
session.flush();
trackId = track.id(); trackId = track.id();
artist1Id = artist1.id(); artist1Id = artist1.id();
artist2Id = artist2.id(); artist2Id = artist2.id();
@@ -353,12 +360,12 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Artist::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
CHECK(track); CHECK(track);
@@ -374,7 +381,7 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist1 {Artist::getById(session, artist1Id)}; auto artist1 {Artist::getById(session, artist1Id)};
CHECK(artist1); CHECK(artist1);
@@ -386,7 +393,6 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session)
CHECK(artist2); CHECK(artist2);
CHECK(artist2->getTracks().front() == track); CHECK(artist2->getTracks().front() == track);
CHECK(artist1->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist1->getTracks(TrackArtistLink::Type::ReleaseArtist).empty());
CHECK(artist1->getTracks(TrackArtistLink::Type::Artist).size() == 1); CHECK(artist1->getTracks(TrackArtistLink::Type::Artist).size() == 1);
CHECK(artist2->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist2->getTracks(TrackArtistLink::Type::ReleaseArtist).empty());
@@ -400,12 +406,12 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleRelease(Wt::Dbo::Session& session) testSingleTrackSingleRelease(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType releaseId {}; IdType releaseId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "dummy")}; auto track {Track::create(session, "dummy")};
CHECK(track); CHECK(track);
@@ -414,13 +420,12 @@ testSingleTrackSingleRelease(Wt::Dbo::Session& session)
track.modify()->setRelease(release); track.modify()->setRelease(release);
session.flush();
trackId = track.id(); trackId = track.id();
releaseId = release.id(); releaseId = release.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Release::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty());
auto release {Release::getById(session, releaseId)}; auto release {Release::getById(session, releaseId)};
@@ -431,7 +436,7 @@ testSingleTrackSingleRelease(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
CHECK(track); CHECK(track);
@@ -441,7 +446,7 @@ testSingleTrackSingleRelease(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
CHECK(Release::getAllOrphans(session).size() == 1); CHECK(Release::getAllOrphans(session).size() == 1);
auto release {Release::getById(session, releaseId)}; auto release {Release::getById(session, releaseId)};
@@ -454,13 +459,13 @@ testSingleTrackSingleRelease(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleCluster(Wt::Dbo::Session& session) testSingleTrackSingleCluster(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType clusterId {}; IdType clusterId {};
IdType clusterTypeId {}; IdType clusterTypeId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "dummy")}; auto track {Track::create(session, "dummy")};
auto clusterType {ClusterType::create(session, "MyType")}; auto clusterType {ClusterType::create(session, "MyType")};
@@ -468,19 +473,18 @@ testSingleTrackSingleCluster(Wt::Dbo::Session& session)
cluster.modify()->addTrack(track); cluster.modify()->addTrack(track);
session.flush();
trackId = track.id(); trackId = track.id();
clusterTypeId = clusterType.id(); clusterTypeId = clusterType.id();
clusterId = cluster.id(); clusterId = cluster.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Cluster::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto tracks {Track::getByFilter(session, {clusterId})}; auto tracks {Track::getByFilter(session, {clusterId})};
CHECK(tracks.size() == 1); CHECK(tracks.size() == 1);
@@ -492,7 +496,7 @@ testSingleTrackSingleCluster(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
auto cluster {Cluster::getById(session, clusterId)}; auto cluster {Cluster::getById(session, clusterId)};
@@ -506,14 +510,14 @@ testSingleTrackSingleCluster(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session) testSingleTrackSingleReleaseSingleCluster(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType releaseId {}; IdType releaseId {};
IdType clusterId {}; IdType clusterId {};
IdType clusterTypeId {}; IdType clusterTypeId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "MyTrackFile")}; auto track {Track::create(session, "MyTrackFile")};
auto release {Release::create(session, "MyRelease")}; auto release {Release::create(session, "MyRelease")};
@@ -523,7 +527,6 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session)
track.modify()->setRelease(release); track.modify()->setRelease(release);
cluster.modify()->addTrack(track); cluster.modify()->addTrack(track);
session.flush();
trackId = track.id(); trackId = track.id();
releaseId = release.id(); releaseId = release.id();
clusterTypeId = clusterType.id(); clusterTypeId = clusterType.id();
@@ -531,13 +534,13 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Cluster::getAllOrphans(session).empty());
CHECK(Release::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto releases {Release::getByFilter(session, {clusterId})}; auto releases {Release::getByFilter(session, {clusterId})};
CHECK(releases.size() == 1); CHECK(releases.size() == 1);
@@ -549,7 +552,7 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto cluster {Cluster::getById(session, clusterId)}; auto cluster {Cluster::getById(session, clusterId)};
CHECK(cluster->getReleasesCount() == 1); CHECK(cluster->getReleasesCount() == 1);
@@ -557,7 +560,7 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
auto release {Release::getById(session, releaseId)}; auto release {Release::getById(session, releaseId)};
@@ -573,7 +576,7 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session) testSingleTrackSingleArtistMultiClusters(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType artistId {}; IdType artistId {};
@@ -581,7 +584,7 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session)
IdType cluster2Id {}; IdType cluster2Id {};
IdType clusterTypeId {}; IdType clusterTypeId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "MyTrackFile")}; auto track {Track::create(session, "MyTrackFile")};
auto artist {Artist::create(session, "MyArtist")}; auto artist {Artist::create(session, "MyArtist")};
@@ -592,7 +595,6 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session)
auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)};
cluster1.modify()->addTrack(track); cluster1.modify()->addTrack(track);
session.flush();
trackId = track.id(); trackId = track.id();
artistId = artist.id(); artistId = artist.id();
clusterTypeId = clusterType.id(); clusterTypeId = clusterType.id();
@@ -601,14 +603,14 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Cluster::getAllOrphans(session).empty());
CHECK(Release::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty());
CHECK(Artist::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto artists {Artist::getByFilter(session, {cluster1Id})}; auto artists {Artist::getByFilter(session, {cluster1Id})};
CHECK(artists.size() == 1); CHECK(artists.size() == 1);
@@ -627,7 +629,7 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto artists {Artist::getByFilter(session, {cluster1Id})}; auto artists {Artist::getByFilter(session, {cluster1Id})};
CHECK(artists.size() == 1); CHECK(artists.size() == 1);
@@ -643,7 +645,7 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
@@ -661,14 +663,14 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session) testSingleTrackSingleArtistMultiRolesMultiClusters(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType artistId {}; IdType artistId {};
IdType clusterId {}; IdType clusterId {};
IdType clusterTypeId {}; IdType clusterTypeId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "MyTrackFile")}; auto track {Track::create(session, "MyTrackFile")};
auto artist {Artist::create(session, "MyArtist")}; auto artist {Artist::create(session, "MyArtist")};
@@ -679,7 +681,6 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session)
TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::ReleaseArtist); TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::ReleaseArtist);
cluster.modify()->addTrack(track); cluster.modify()->addTrack(track);
session.flush();
trackId = track.id(); trackId = track.id();
artistId = artist.id(); artistId = artist.id();
clusterTypeId = clusterType.id(); clusterTypeId = clusterType.id();
@@ -687,14 +688,14 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Cluster::getAllOrphans(session).empty());
CHECK(Release::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty());
CHECK(Artist::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto artists {Artist::getByFilter(session, {clusterId})}; auto artists {Artist::getByFilter(session, {clusterId})};
CHECK(artists.size() == 1); CHECK(artists.size() == 1);
@@ -702,7 +703,7 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::getById(session, trackId)}; auto track {Track::getById(session, trackId)};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
@@ -718,14 +719,14 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session)
static static
void void
testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session) testMultiTracksSingleArtistMultiClusters(Session& session)
{ {
const std::size_t nbTracks {10}; const std::size_t nbTracks {10};
const std::size_t nbClusters {5}; const std::size_t nbClusters {5};
IdType artistId {}; IdType artistId {};
IdType clusterTypeId {}; IdType clusterTypeId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::create(session, "MyArtist")}; auto artist {Artist::create(session, "MyArtist")};
@@ -744,19 +745,18 @@ testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session)
cluster.modify()->addTrack(track); cluster.modify()->addTrack(track);
} }
session.flush();
artistId = artist.id(); artistId = artist.id();
clusterTypeId = clusterType.id(); clusterTypeId = clusterType.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Cluster::getAllOrphans(session).empty());
CHECK(Artist::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
std::vector<Cluster::pointer> clusters {Cluster::getAll(session)}; std::vector<Cluster::pointer> clusters {Cluster::getAll(session)};
CHECK(clusters.size() == nbClusters); CHECK(clusters.size() == nbClusters);
@@ -770,7 +770,7 @@ testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
std::vector<Cluster::pointer> clusters {Cluster::getAll(session)}; std::vector<Cluster::pointer> clusters {Cluster::getAll(session)};
for (auto& cluster : clusters) for (auto& cluster : clusters)
@@ -790,13 +790,13 @@ testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session)
static static
void void
testMultiTracksSingleArtistSingleRelease(Wt::Dbo::Session& session) testMultiTracksSingleArtistSingleRelease(Session& session)
{ {
const std::size_t nbTracks {10}; const std::size_t nbTracks {10};
IdType artistId {}; IdType artistId {};
IdType releaseId {}; IdType releaseId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::create(session, "MyArtist")}; auto artist {Artist::create(session, "MyArtist")};
auto release {Release::create(session, "MyRelease")}; auto release {Release::create(session, "MyRelease")};
@@ -808,19 +808,18 @@ testMultiTracksSingleArtistSingleRelease(Wt::Dbo::Session& session)
track.modify()->setRelease(release); track.modify()->setRelease(release);
} }
session.flush();
artistId = artist.id(); artistId = artist.id();
releaseId = release.id(); releaseId = release.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Release::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty());
CHECK(Artist::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
CHECK(artist); CHECK(artist);
@@ -834,7 +833,7 @@ testMultiTracksSingleArtistSingleRelease(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
std::vector<Track::pointer> tracks {Track::getAll(session)}; std::vector<Track::pointer> tracks {Track::getAll(session)};
for (auto& track : tracks) for (auto& track : tracks)
@@ -849,13 +848,13 @@ testMultiTracksSingleArtistSingleRelease(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleReleaseSingleArtist(Wt::Dbo::Session& session) testSingleTrackSingleReleaseSingleArtist(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType releaseId {}; IdType releaseId {};
IdType artistId {}; IdType artistId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "dummy")}; auto track {Track::create(session, "dummy")};
auto release {Release::create(session, "dummy")}; auto release {Release::create(session, "dummy")};
@@ -864,14 +863,13 @@ testSingleTrackSingleReleaseSingleArtist(Wt::Dbo::Session& session)
auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)};
track.modify()->setRelease(release); track.modify()->setRelease(release);
session.flush();
trackId = track.id(); trackId = track.id();
releaseId = release.id(); releaseId = release.id();
artistId = artist.id(); artistId = artist.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
CHECK(artist); CHECK(artist);
@@ -897,7 +895,7 @@ testSingleTrackSingleReleaseSingleArtist(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session) testSingleTrackSingleReleaseSingleArtistSingleCluster(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType releaseId {}; IdType releaseId {};
@@ -906,7 +904,7 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session)
IdType clusterTypeId {}; IdType clusterTypeId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "MyTrackFile")}; auto track {Track::create(session, "MyTrackFile")};
auto release {Release::create(session, "MyRelease")}; auto release {Release::create(session, "MyRelease")};
@@ -919,7 +917,6 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session)
track.modify()->setRelease(release); track.modify()->setRelease(release);
cluster.modify()->addTrack(track); cluster.modify()->addTrack(track);
session.flush();
trackId = track.id(); trackId = track.id();
releaseId = release.id(); releaseId = release.id();
artistId = artist.id(); artistId = artist.id();
@@ -928,14 +925,15 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Cluster::getAllOrphans(session).empty());
CHECK(Artist::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty());
CHECK(Release::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty());
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto artists {Artist::getByFilter(session, {clusterId})}; auto artists {Artist::getByFilter(session, {clusterId})};
CHECK(artists.size() == 1); CHECK(artists.size() == 1);
@@ -952,7 +950,7 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
auto release {Release::getById(session, releaseId)}; auto release {Release::getById(session, releaseId)};
@@ -970,7 +968,7 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session)
static static
void void
testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session) testSingleTrackSingleReleaseSingleArtistMultiClusters(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType releaseId {}; IdType releaseId {};
@@ -980,7 +978,7 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session)
IdType clusterTypeId {}; IdType clusterTypeId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "MyTrackFile")}; auto track {Track::create(session, "MyTrackFile")};
auto release {Release::create(session, "MyRelease")}; auto release {Release::create(session, "MyRelease")};
@@ -995,7 +993,6 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session)
cluster1.modify()->addTrack(track); cluster1.modify()->addTrack(track);
cluster2.modify()->addTrack(track); cluster2.modify()->addTrack(track);
session.flush();
trackId = track.id(); trackId = track.id();
releaseId = release.id(); releaseId = release.id();
artistId = artist.id(); artistId = artist.id();
@@ -1005,7 +1002,7 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
auto releases {artist->getReleases()}; auto releases {artist->getReleases()};
@@ -1018,7 +1015,7 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::getById(session, artistId)}; auto artist {Artist::getById(session, artistId)};
auto release {Release::getById(session, releaseId)}; auto release {Release::getById(session, releaseId)};
@@ -1038,25 +1035,59 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session)
static static
void void
testSingleStarredArtist(Wt::Dbo::Session& session) testSingleUser(Session& session)
{
IdType userId {};
{
auto transaction {session.createUniqueTransaction()};
auto user {User::create(session)};
CHECK(user);
userId = user.id();
}
{
auto transaction {session.createSharedTransaction()};
auto user {User::getById(session, userId)};
CHECK(user);
CHECK(user->getPlayedTrackList(session)->getCount() == 0);
CHECK(user->getPlayedTrackList(session)->getTopTracks(1).empty());
CHECK(user->getPlayedTrackList(session)->getTopArtists(1).empty());
CHECK(user->getPlayedTrackList(session)->getTopReleases(1).empty());
CHECK(user->getQueuedTrackList(session)->getCount() == 0);
}
{
auto transaction {session.createUniqueTransaction()};
auto user {User::getById(session, userId)};
CHECK(user);
user.remove();
}
}
static
void
testSingleStarredArtist(Session& session)
{ {
IdType artistId {}; IdType artistId {};
IdType userId {}; IdType userId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto artist {Artist::create(session, "MyArtist")}; auto artist {Artist::create(session, "MyArtist")};
CHECK(artist); CHECK(artist);
auto user {User::create(session)}; auto user {User::create(session)};
CHECK(user); CHECK(user);
session.flush();
artistId = artist.id(); artistId = artist.id();
userId = user.id(); userId = user.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1067,7 +1098,7 @@ testSingleStarredArtist(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1080,7 +1111,7 @@ testSingleStarredArtist(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1094,25 +1125,24 @@ testSingleStarredArtist(Wt::Dbo::Session& session)
static static
void void
testSingleStarredRelease(Wt::Dbo::Session& session) testSingleStarredRelease(Session& session)
{ {
IdType releaseId {}; IdType releaseId {};
IdType userId {}; IdType userId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto release {Release::create(session, "MyRelease")}; auto release {Release::create(session, "MyRelease")};
CHECK(release); CHECK(release);
auto user {User::create(session)}; auto user {User::create(session)};
CHECK(user); CHECK(user);
session.flush();
releaseId = release.id(); releaseId = release.id();
userId = user.id(); userId = user.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1123,7 +1153,7 @@ testSingleStarredRelease(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1136,7 +1166,7 @@ testSingleStarredRelease(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1150,25 +1180,24 @@ testSingleStarredRelease(Wt::Dbo::Session& session)
static static
void void
testSingleStarredTrack(Wt::Dbo::Session& session) testSingleStarredTrack(Session& session)
{ {
IdType trackId {}; IdType trackId {};
IdType userId {}; IdType userId {};
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto track {Track::create(session, "MyTrackFile")}; auto track {Track::create(session, "MyTrackFile")};
CHECK(track); CHECK(track);
auto user {User::create(session)}; auto user {User::create(session)};
CHECK(user); CHECK(user);
session.flush();
trackId = track.id(); trackId = track.id();
userId = user.id(); userId = user.id();
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1179,7 +1208,7 @@ testSingleStarredTrack(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createSharedTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1192,7 +1221,7 @@ testSingleStarredTrack(Wt::Dbo::Session& session)
} }
{ {
Wt::Dbo::Transaction transaction {session}; auto transaction {session.createUniqueTransaction()};
auto user {User::getById(session, userId)}; auto user {User::getById(session, userId)};
CHECK(user); CHECK(user);
@@ -1206,9 +1235,9 @@ testSingleStarredTrack(Wt::Dbo::Session& session)
static static
void void
testDatabaseEmpty(Wt::Dbo::Session& session) testDatabaseEmpty(Session& session)
{ {
Wt::Dbo::Transaction transaction {session}; auto uniqueTransaction {session.createUniqueTransaction()};
CHECK(Artist::getAll(session).empty()); CHECK(Artist::getAll(session).empty());
CHECK(Cluster::getAll(session).empty()); CHECK(Cluster::getAll(session).empty());
@@ -1222,26 +1251,29 @@ int main(int argc, char* argv[])
try try
{ {
boost::filesystem::path tmpFile {boost::filesystem::temp_directory_path() / boost::filesystem::unique_path()}; const boost::filesystem::path tmpFile {boost::filesystem::temp_directory_path() / boost::filesystem::unique_path()};
ScopedFileDeleter tmpFileDeleter {tmpFile}; ScopedFileDeleter tmpFileDeleter {tmpFile};
std::cout << "Database test file: '" << tmpFile.string() << "'" << std::endl; std::cout << "Database test file: '" << tmpFile.string() << "'" << std::endl;
std::unique_ptr<Wt::Dbo::SqlConnectionPool> connectionPool{ Handler::createConnectionPool(tmpFile) }; for (std::size_t i = 0; i < 2; ++i)
{
Database::Database db {tmpFile};
std::unique_ptr<Session> session {db.createSession()};
Handler db {*connectionPool}; auto runTest = [&session](const std::string& name, std::function<void(Session&)> testFunc)
Wt::Dbo::Session& session {db.getSession()};
auto runTest = [&session](const std::string& name, std::function<void(Wt::Dbo::Session&)> testFunc)
{ {
std::cout << "Running test '" << name << "'..." << std::endl; std::cout << "Running test '" << name << "'..." << std::endl;
testFunc(session); testFunc(*session);
testDatabaseEmpty(session); testDatabaseEmpty(*session);
std::cout << "Running test '" << name << "': SUCCESS" << std::endl; std::cout << "Running test '" << name << "': SUCCESS" << std::endl;
}; };
#define RUN_TEST(test) runTest(#test, test) #define RUN_TEST(test) runTest(#test, test)
// Special test to remove any default created entries
RUN_TEST(testRemoveDefaultEntries);
RUN_TEST(testSingleTrack); RUN_TEST(testSingleTrack);
RUN_TEST(testSingleArtist); RUN_TEST(testSingleArtist);
RUN_TEST(testSingleRelease); RUN_TEST(testSingleRelease);
@@ -1266,9 +1298,13 @@ int main(int argc, char* argv[])
RUN_TEST(testSingleTrackSingleReleaseSingleArtistSingleCluster); RUN_TEST(testSingleTrackSingleReleaseSingleArtistSingleCluster);
RUN_TEST(testSingleTrackSingleReleaseSingleArtistMultiClusters); RUN_TEST(testSingleTrackSingleReleaseSingleArtistMultiClusters);
RUN_TEST(testSingleUser);
RUN_TEST(testSingleStarredArtist); RUN_TEST(testSingleStarredArtist);
RUN_TEST(testSingleStarredRelease); RUN_TEST(testSingleStarredRelease);
RUN_TEST(testSingleStarredTrack); RUN_TEST(testSingleStarredTrack);
}
} }
catch (std::exception& e) catch (std::exception& e)
{ {