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_CHECK_LIB( [pthread],
[pthread_rwlock_unlock],
,
[AC_MSG_ERROR([libpthread not found!])])
AC_CHECK_LIB([wt],
[main],
,
+4 -2
View File
@@ -19,8 +19,8 @@ lms_SOURCES = \
$(srcdir)/database/Artist.hpp \
$(srcdir)/database/Cluster.cpp \
$(srcdir)/database/Cluster.hpp \
$(srcdir)/database/DatabaseHandler.cpp \
$(srcdir)/database/DatabaseHandler.hpp \
$(srcdir)/database/Database.cpp \
$(srcdir)/database/Database.hpp \
$(srcdir)/database/TrackArtistLink.cpp \
$(srcdir)/database/TrackArtistLink.hpp \
$(srcdir)/database/TrackFeatures.cpp \
@@ -32,6 +32,8 @@ lms_SOURCES = \
$(srcdir)/database/Release.hpp \
$(srcdir)/database/ScanSettings.cpp \
$(srcdir)/database/ScanSettings.hpp \
$(srcdir)/database/Session.cpp \
$(srcdir)/database/Session.hpp \
$(srcdir)/database/SimilaritySettings.cpp \
$(srcdir)/database/SimilaritySettings.hpp \
$(srcdir)/database/SqlQuery.cpp \
+149 -142
View File
@@ -22,6 +22,7 @@
#include <mutex>
#include <numeric>
#include <random>
#include <thread>
#include <Wt/Auth/Identity.h>
#include <Wt/WLocalDateTime.h>
@@ -33,6 +34,7 @@
#include "database/Release.hpp"
#include "database/Track.hpp"
#include "database/TrackList.hpp"
#include "database/User.hpp"
#include "main/Service.hpp"
#include "similarity/SimilaritySearcher.hpp"
#include "utils/Logger.hpp"
@@ -113,10 +115,37 @@ struct ClientInfo
struct RequestContext
{
const Wt::Http::ParameterMap& parameters;
Database::Handler& db;
Database::Session& dbSession;
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
using RequestHandlerFunc = std::function<Response(RequestContext& context)>;
static Response handlePingRequest(RequestContext& context);
@@ -289,23 +318,15 @@ getClientInfo(const Wt::Http::ParameterMap& parameters)
return res;
}
static
bool
checkPassword(Database::Handler& db, const ClientInfo& clientInfo)
SubsonicResource::SubsonicResource(Database::Database& db)
: _db {db}
{
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(Wt::Dbo::SqlConnectionPool& connectionPool)
: _db {connectionPool}
SubsonicResource::~SubsonicResource()
{
LMS_LOG(API_SUBSONIC, DEBUG) << "Cleaning db sessions...";
cleanDbSessions();
}
std::vector<std::string>
@@ -371,24 +392,20 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
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 (!checkPassword(_db, clientInfo))
if (!dbSession.checkUserPassword(clientInfo.user, clientInfo.password))
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())};
if (itHandler != requestHandlers.end())
{
Response resp {(itHandler->second)(requestContext)};
lock.unlock();
resp.write(response.out(), 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())};
lock.unlock();
if (!res.mimeType.empty())
response.setMimeType(res.mimeType);
if (!res.data.empty())
@@ -488,7 +503,7 @@ getTrackPath(const Database::Track::pointer& track)
static
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;
@@ -532,7 +547,7 @@ trackToResponseNode(const Database::User::pointer& user, const Database::Track::
trackResponse.setAttribute("starred", reportedStarredDate);
// 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)
{
auto clusters {track->getClusterGroups({clusterType}, 1)};
@@ -545,7 +560,7 @@ trackToResponseNode(const Database::User::pointer& user, const Database::Track::
static
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;
@@ -598,7 +613,7 @@ releaseToResponseNode(const Database::User::pointer& user, const Database::Relea
if (id3)
{
// 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)
{
auto clusters {release->getClusterGroups({clusterType}, 1)};
@@ -668,16 +683,16 @@ handleCreatePlaylistRequest(RequestContext& context)
if (!name && !id)
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)
throw Error {Error::Code::RequestedDataNotFound};
Database::TrackList::pointer tracklist;
if (id)
{
tracklist = Database::TrackList::getById(context.db.getSession(), id->value);
tracklist = Database::TrackList::getById(context.dbSession, id->value);
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != Database::TrackList::Type::Playlist)
@@ -690,16 +705,16 @@ handleCreatePlaylistRequest(RequestContext& context)
}
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)
{
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)
continue;
Database::TrackListEntry::create(context.db.getSession(), track, tracklist );
Database::TrackListEntry::create(context.dbSession, track, tracklist );
}
return Response::createOkResponse();
@@ -713,13 +728,13 @@ handleDeletePlaylistRequest(RequestContext& context)
if (id.type != Id::Type::Playlist)
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)
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
|| tracklist->getUser() != user
|| 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)};
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)
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::Node& randomSongsNode {response.createNode("randomSongs")};
for (const Database::Track::pointer& track : tracks)
randomSongsNode.addArrayChild("song", trackToResponseNode(user, track));
randomSongsNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
return response;
}
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::size_t nbReleases {Database::Release::getCount(session)};
std::size_t nbReleases {Database::Release::getCount(dbSession)};
if (offset > nbReleases)
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::size_t offset)
{
auto release {Database::Release::getAll(session, offset, 1)};
auto release {Database::Release::getAll(dbSession, offset, 1)};
if (!release.empty())
res.emplace_back(release.front());
});
@@ -814,38 +829,38 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
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)
throw Error {Error::Code::RequestedDataNotFound};
if (type == "random")
{
releases = getRandomAlbums(context.db.getSession(), offset, size);
releases = getRandomAlbums(context.dbSession, offset, size);
}
else if (type == "newest")
{
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")
{
releases = Database::Release::getAll(context.db.getSession(), offset, size);
releases = Database::Release::getAll(context.dbSession, offset, size);
}
else if (type == "byGenre")
{
// Mandatory param
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)
{
Database::Cluster::pointer cluster {clusterType->getCluster(genre)};
if (cluster)
{
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")};
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;
}
@@ -882,22 +897,22 @@ handleGetAlbumRequest(RequestContext& context)
if (id.type != Id::Type::Release)
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)
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)
throw Error {Error::Code::RequestedDataNotFound};
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()};
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));
@@ -913,10 +928,10 @@ handleGetArtistRequest(RequestContext& context)
if (id.type != Id::Type::Artist)
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::User::pointer user {context.db.getUser(context.userName)};
Database::Artist::pointer artist {Database::Artist::getById(context.dbSession, id.value)};
Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!artist || !user)
throw Error {Error::Code::RequestedDataNotFound};
@@ -926,7 +941,7 @@ handleGetArtistRequest(RequestContext& context)
auto releases {artist->getReleases()};
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));
@@ -945,10 +960,10 @@ Response handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
// Optional params
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::User::pointer user {context.db.getUser(context.userName)};
Database::Artist::pointer artist {Database::Artist::getById(context.dbSession, id.value)};
Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!artist || !user)
throw Error {Error::Code::RequestedDataNotFound};
@@ -959,10 +974,10 @@ Response handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
if (!artist->getMBID().empty())
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 )
{
Database::Artist::pointer similarArtist {Database::Artist::getById(context.db.getSession(), similarArtistId)};
Database::Artist::pointer similarArtist {Database::Artist::getById(context.dbSession, similarArtistId)};
if (similarArtist)
artistInfoNode.addArrayChild("similarArtist", artistToResponseNode(user, similarArtist, id3));
@@ -989,13 +1004,13 @@ handleGetArtistsRequest(RequestContext& context)
Response::Node& indexNode {artistsNode.createArrayChild("index")};
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)
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)
indexNode.addArrayChild("artist", artistToResponseNode(user, artist, true /* id3 */));
@@ -1014,9 +1029,9 @@ handleGetMusicDirectoryRequest(RequestContext& context)
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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1026,7 +1041,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
{
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)
directoryNode.addArrayChild("child", artistToResponseNode(user, artist, false /* no id3 */));
@@ -1035,7 +1050,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1043,14 +1058,14 @@ handleGetMusicDirectoryRequest(RequestContext& context)
auto releases {artist->getReleases()};
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;
}
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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1058,7 +1073,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
auto tracks {release->getTracks()};
for (const Database::Track::pointer& track : tracks)
directoryNode.addArrayChild("child", trackToResponseNode(user, track));
directoryNode.addArrayChild("child", trackToResponseNode(track, context.dbSession, user));
break;
}
@@ -1090,9 +1105,9 @@ handleGetGenresRequest(RequestContext& context)
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)
{
auto clusters {clusterType->getClusters()};
@@ -1113,13 +1128,13 @@ handleGetIndexesRequest(RequestContext& context)
Response::Node& indexNode {artistsNode.createArrayChild("index")};
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)
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)
indexNode.addArrayChild("artist", artistToResponseNode(user, artist, false /* no id3 */));
@@ -1137,10 +1152,10 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
// Optional params
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::User::pointer user {context.db.getUser(context.userName)};
Database::Artist::pointer artist {Database::Artist::getById(context.dbSession, id.value)};
Database::User::pointer user {context.dbSession.getUser(context.userName)};
if (!user || !artist)
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"
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 )
{
Database::Artist::pointer similarArtist {Database::Artist::getById(context.db.getSession(), similarArtistId)};
Database::Artist::pointer similarArtist {Database::Artist::getById(context.dbSession, similarArtistId)};
if (!similarArtist)
continue;
@@ -1169,7 +1184,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
Response response {Response::createOkResponse()};
Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")};
for (const Database::Track::pointer& track : tracks)
similarSongsNode.addArrayChild("song", trackToResponseNode(user, track));
similarSongsNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
return response;
}
@@ -1191,9 +1206,9 @@ static
Response
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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1209,13 +1224,13 @@ handleGetStarredRequestCommon(RequestContext& context, bool id3)
{
auto releases {user->getStarredReleases()};
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()};
for (const Database::Track::pointer& track : tracks)
starredNode.addArrayChild("song", trackToResponseNode(user, track));
starredNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
}
return response;
@@ -1235,7 +1250,7 @@ handleGetStarred2Request(RequestContext& context)
}
Response::Node
tracklistToResponseNode(const Database::TrackList::pointer& tracklist, Database::Handler& db)
tracklistToResponseNode(const Database::TrackList::pointer& tracklist, Database::Session& dbSession)
{
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("public", tracklist->isPublic() ? "true" : "false");
playlistNode.setAttribute("created", "");
{
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());
}
playlistNode.setAttribute("owner", dbSession.getUserLoginName(tracklist->getUser()));
return playlistNode;
}
@@ -1266,19 +1273,19 @@ handleGetPlaylistRequest(RequestContext& context)
if (id.type != Id::Type::Playlist)
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::TrackList::pointer tracklist {Database::TrackList::getById(context.db.getSession(), id.value)};
Database::User::pointer user {context.dbSession.getUser(context.userName)};
Database::TrackList::pointer tracklist {Database::TrackList::getById(context.dbSession, id.value)};
if (!user || !tracklist)
throw Error {Error::Code::RequestedDataNotFound};
Response response {Response::createOkResponse()};
Response::Node playlistNode {tracklistToResponseNode(tracklist, context.db)};
Response::Node playlistNode {tracklistToResponseNode(tracklist, context.dbSession)};
auto entries {tracklist->getEntries()};
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 );
@@ -1288,18 +1295,18 @@ handleGetPlaylistRequest(RequestContext& context)
Response
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)
throw Error {Error::Code::RequestedDataNotFound};
Response response {Response::createOkResponse()};
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)
playlistsNode.addArrayChild("playlist", tracklistToResponseNode(tracklist, context.db));
playlistsNode.addArrayChild("playlist", tracklistToResponseNode(tracklist, context.dbSession));
return response;
}
@@ -1316,9 +1323,9 @@ handleGetSongsByGenreRequest(RequestContext& context)
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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1326,7 +1333,7 @@ handleGetSongsByGenreRequest(RequestContext& context)
if (!cluster)
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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1334,9 +1341,9 @@ handleGetSongsByGenreRequest(RequestContext& context)
Response::Node& songsByGenreNode {response.createNode("songsByGenre")};
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)
songsByGenreNode.addArrayChild("song", trackToResponseNode(user, track));
songsByGenreNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
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 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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1369,21 +1376,21 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
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)
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)
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)
searchResult2Node.addArrayChild("song", trackToResponseNode(user, track));
searchResult2Node.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
}
return response;
@@ -1439,15 +1446,15 @@ handleStarRequest(RequestContext& context)
{
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)
throw Error {Error::Code::RequestedDataNotFound};
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)
continue;
@@ -1456,7 +1463,7 @@ handleStarRequest(RequestContext& context)
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)
continue;
@@ -1465,7 +1472,7 @@ handleStarRequest(RequestContext& context)
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)
continue;
@@ -1492,15 +1499,15 @@ handleUnstarRequest(RequestContext& context)
{
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)
throw Error {Error::Code::RequestedDataNotFound};
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)
continue;
@@ -1509,7 +1516,7 @@ handleUnstarRequest(RequestContext& context)
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)
continue;
@@ -1518,7 +1525,7 @@ handleUnstarRequest(RequestContext& context)
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)
continue;
@@ -1547,13 +1554,13 @@ handleUpdatePlaylistRequest(RequestContext& context)
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)
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
|| tracklist->getUser() != user
|| tracklist->getType() != Database::TrackList::Type::Playlist)
@@ -1582,11 +1589,11 @@ handleUpdatePlaylistRequest(RequestContext& context)
// Add tracks
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)
continue;
Database::TrackListEntry::create(context.db.getSession(), track, tracklist );
Database::TrackListEntry::create(context.dbSession, track, tracklist );
}
return Response::createOkResponse();
@@ -1604,9 +1611,9 @@ createTranscoder(RequestContext& context)
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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1616,7 +1623,7 @@ createTranscoder(RequestContext& context)
*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)
throw Error {Error::Code::RequestedDataNotFound};
@@ -1685,10 +1692,10 @@ handleGetCoverArt(RequestContext& context, Wt::Http::ResponseContinuation*)
switch (id.type)
{
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;
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;
default:
throw Error {Error::CustomType::BadId};
+4 -4
View File
@@ -23,7 +23,7 @@
#include <Wt/WResource.h>
#include <Wt/Http/Response.h>
#include "database/DatabaseHandler.hpp"
#include "database/Database.hpp"
namespace API::Subsonic
{
@@ -31,15 +31,15 @@ namespace API::Subsonic
class SubsonicResource final : public Wt::WResource
{
public:
SubsonicResource(Wt::Dbo::SqlConnectionPool& connectionPool);
SubsonicResource(Database::Database& db);
~SubsonicResource();
static std::vector<std::string> getPaths();
private:
void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override;
Database::Handler _db;
Database::Database& _db;
};
} // namespace
+29 -30
View File
@@ -24,6 +24,7 @@
#include "av/AvInfo.hpp"
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "utils/Logger.hpp"
@@ -169,31 +170,32 @@ Grabber::getFromTrack(const boost::filesystem::path& p) const
}
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;
boost::optional<Image::Image> cover;
{
Wt::Dbo::Transaction transaction(session);
bool hasCover {};
boost::filesystem::path trackPath;
Track::pointer track = Track::getById(session, trackId);
{
auto transaction {dbSession.createSharedTransaction()};
Track::pointer track = Track::getById(dbSession, trackId);
if (track)
{
bool hasCover = track->hasCover();
boost::filesystem::path trackPath = track->getPath();
transaction.commit();
if (hasCover)
cover = getFromTrack(trackPath);
if (!cover)
cover = getFromDirectory(trackPath.parent_path());
hasCover = track->hasCover();
trackPath = track->getPath();
}
}
if (hasCover)
cover = getFromTrack(trackPath);
if (!cover)
cover = getFromDirectory(trackPath.parent_path());
if (!cover)
cover = getDefaultCover(size);
else
@@ -204,29 +206,26 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::
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<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)
{
auto tracks = release->getTracks();
auto tracks {release->getTracks()};
if (!tracks.empty())
{
auto trackId = tracks.front().id();
transaction.commit();
return getFromTrack(session, trackId, size);
}
trackId = tracks.front().id();
}
}
if (trackId)
return getFromTrack(session, *trackId, size);
if (!cover)
cover = getDefaultCover(size);
else
@@ -236,17 +235,17 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, s
}
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);
}
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);
}
+10 -4
View File
@@ -23,10 +23,16 @@
#include <mutex>
#include <vector>
#include <boost/optional.hpp>
#include "database/Types.hpp"
#include "image/Image.hpp"
namespace Database {
class Session;
}
namespace CoverArt {
class Grabber
@@ -40,13 +46,13 @@ class Grabber
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> getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, 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(Database::Session& dbSession, Database::IdType releaseId, Image::Format format, std::size_t size);
private:
Image::Image getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::size_t size);
Image::Image getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t size);
Image::Image getFromTrack(Database::Session& dbSession, Database::IdType trackId, 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;
std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath) const;
+35 -18
View File
@@ -25,6 +25,7 @@
#include "Cluster.hpp"
#include "Release.hpp"
#include "SqlQuery.hpp"
#include "Session.hpp"
#include "Track.hpp"
#include "User.hpp"
@@ -40,34 +41,44 @@ _MBID {MBID}
}
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());
}
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::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::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>
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)
.limit(size ? static_cast<int>(*size) : -1)
.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>
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());
}
static
Wt::Dbo::Query<Artist::pointer>
getQuery(Wt::Dbo::Session& session,
getQuery(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords)
{
session.checkSharedLocked();
WhereClause where;
std::ostringstream oss;
@@ -115,7 +129,7 @@ getQuery(Wt::Dbo::Session& session,
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())
{
@@ -126,20 +140,22 @@ getQuery(Wt::Dbo::Session& session,
}
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;
return getByFilter(session, clusters, {}, {}, {}, more);
}
std::vector<Artist::pointer>
Artist::getByFilter(Wt::Dbo::Session& session,
Artist::getByFilter(Session& session,
const std::set<IdType>& clusters,
const std::vector<std::string>& keywords,
boost::optional<std::size_t> offset,
boost::optional<std::size_t> size,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> collection = getQuery(session, clusters, keywords)
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
@@ -158,9 +174,10 @@ Artist::getByFilter(Wt::Dbo::Session& session,
}
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)
.groupBy("a.id")
.orderBy("t.file_added DESC")
+10 -10
View File
@@ -36,6 +36,7 @@ namespace Database
class Cluster;
class ClusterType;
class Release;
class Session;
class Track;
class User;
@@ -49,21 +50,21 @@ class Artist : public Wt::Dbo::Dbo<Artist>
Artist(const std::string& name, const std::string& MBID = "");
// Accessors
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static pointer getById(Wt::Dbo::Session& session, IdType id);
static std::vector<pointer> getByName(Wt::Dbo::Session& session, const std::string& name);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
static pointer getByMBID(Session& session, const std::string& MBID);
static pointer getById(Session& session, IdType id);
static std::vector<pointer> getByName(Session& session, const std::string& name);
static std::vector<pointer> getByFilter(Session& session,
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::vector<std::string>& keywords, // name must match all of these keywords
boost::optional<std::size_t> offset,
boost::optional<std::size_t> size,
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> getAllOrphans(Wt::Dbo::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> getAll(Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastAdded(Session& session, Wt::WDateTime after, boost::optional<std::size_t> size = {});
// Accessors
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);
// 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>
void persist(Action& a)
+46 -18
View File
@@ -22,6 +22,7 @@
#include "Artist.hpp"
#include "Release.hpp"
#include "ScanSettings.hpp"
#include "Session.hpp"
#include "SqlQuery.hpp"
#include "Track.hpp"
@@ -38,31 +39,42 @@ Cluster::Cluster(Wt::Dbo::ptr<ClusterType> type, std::string name)
}
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>
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());
}
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());
}
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
@@ -74,6 +86,9 @@ Cluster::addTrack(Wt::Dbo::ptr<Track> track)
std::vector<Wt::Dbo::ptr<Track>>
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")
.where("c.id = ?").bind(self()->id())
.offset(offset)
@@ -113,38 +128,51 @@ ClusterType::ClusterType(std::string name)
}
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());
}
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::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>
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());
}
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
+11 -10
View File
@@ -33,6 +33,7 @@ namespace Database {
class Track;
class ClusterType;
class ScanSettings;
class Session;
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);
// Find utility
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session);
static pointer getById(Wt::Dbo::Session& session, IdType id);
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAllOrphans(Session& session);
static pointer getById(Session& session, IdType id);
// 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
const std::string& getName() const { return _name; }
@@ -89,13 +90,13 @@ class ClusterType : public Wt::Dbo::Dbo<ClusterType>
ClusterType() {}
ClusterType(std::string name);
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session);
static pointer getByName(Wt::Dbo::Session& session, std::string name);
static pointer getById(Wt::Dbo::Session& session, IdType id);
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
static std::vector<pointer> getAllOrphans(Session& session);
static pointer getByName(Session& session, std::string name);
static pointer getById(Session& session, IdType id);
static std::vector<pointer> getAll(Session& session);
static pointer create(Wt::Dbo::Session& session, std::string name);
static void remove(Wt::Dbo::Session& session, std::string name);
static pointer create(Session& session, std::string name);
static void remove(Session& session, std::string name);
// Accessors
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 "Cluster.hpp"
#include "Session.hpp"
#include "SqlQuery.hpp"
#include "Track.hpp"
#include "User.hpp"
@@ -38,41 +39,56 @@ _MBID(MBID)
}
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());
}
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::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::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
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();
}
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)
.limit(size ? static_cast<int>(*size) : -1)
.orderBy("name COLLATE NOCASE");
@@ -81,9 +97,11 @@ Release::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset,
}
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)
.orderBy("RANDOM()");
@@ -91,17 +109,21 @@ Release::getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> si
}
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());
}
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)
.groupBy("r.id")
.orderBy("t.file_added DESC")
@@ -113,9 +135,9 @@ Release::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::opt
static
Wt::Dbo::Query<Release::pointer>
getQuery(Wt::Dbo::Session& session,
getQuery(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<std::string> keywords)
const std::vector<std::string>& keywords)
{
WhereClause where;
@@ -144,7 +166,7 @@ getQuery(Wt::Dbo::Session& session,
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())
query.bind(bindArg);
@@ -153,16 +175,16 @@ getQuery(Wt::Dbo::Session& session,
}
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;
return getByFilter(session, clusterIds, {}, {}, {}, moreResults);
}
std::vector<Release::pointer>
Release::getByFilter(Wt::Dbo::Session& session,
Release::getByFilter(Session& session,
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> size,
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 = "");
// Accessors
static std::size_t getCount(Wt::Dbo::Session& session);
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static std::vector<pointer> getByName(Wt::Dbo::Session& session, const std::string& name);
static pointer getById(Wt::Dbo::Session& session, IdType id);
static std::vector<pointer> getAllOrphans(Wt::Dbo::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> getAllRandom(Wt::Dbo::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::size_t getCount(Session& session);
static pointer getByMBID(Session& session, const std::string& MBID);
static std::vector<pointer> getByName(Session& session, const std::string& name);
static pointer getById(Session& session, IdType id);
static std::vector<pointer> getAllOrphans(Session& session); // no track related
static std::vector<pointer> getAll(Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
static std::vector<pointer> getAllRandom(Session& session, 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(Wt::Dbo::Session& session,
static std::vector<pointer> getByFilter(Session& session, const std::set<IdType>& clusters);
static std::vector<pointer> getByFilter(Session& session,
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> size,
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;
// 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
boost::optional<int> getReleaseYear(bool originalDate = false) const; // 0 if unknown or various
+41 -20
View File
@@ -25,10 +25,11 @@
#include "utils/Utils.hpp"
#include "Cluster.hpp"
#include "Session.hpp"
namespace {
std::set<std::string> defaultClusterTypeNames =
const std::set<std::string> defaultClusterTypeNames =
{
"GENRE",
"ALBUMGROUPING",
@@ -40,31 +41,38 @@ std::set<std::string> defaultClusterTypeNames =
namespace Database {
void
ScanSettings::init(Session& session)
{
session.checkUniqueLocked();
pointer settings {get(session)};
if (settings)
return;
settings = session.getDboSession().add(std::make_unique<ScanSettings>());
settings.modify()->setClusterTypes(session, defaultClusterTypeNames );
}
ScanSettings::pointer
ScanSettings::get(Wt::Dbo::Session& session)
ScanSettings::get(Session& session)
{
pointer settings = session.find<ScanSettings>();
if (!settings)
{
settings = session.add(std::make_unique<ScanSettings>());
settings.modify()->setClusterTypes(defaultClusterTypeNames);
}
session.checkSharedLocked();
return settings;
return session.getDboSession().find<ScanSettings>();
}
std::set<boost::filesystem::path>
ScanSettings::getAudioFileExtensions() const
{
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>
ScanSettings::getClusterTypes() const
{
return std::vector<ClusterType::pointer>(_clusterTypes.begin(), _clusterTypes.end());
return std::vector<ClusterType::pointer>(std::cbegin(_clusterTypes), std::cend(_clusterTypes));
}
void
@@ -73,20 +81,34 @@ ScanSettings::setMediaDirectory(boost::filesystem::path p)
_mediaDirectory = stringTrimEnd(p.string(), "/\\");
}
void
ScanSettings::setClusterTypes(const std::set<std::string>& clusterTypeNames)
template <typename It>
std::set<std::string> getNames(It begin, It end)
{
bool needRescan = false;
assert(session());
std::set<std::string> names;
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
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)
{
LMS_LOG(DB, INFO) << "Creating cluster type " << clusterTypeName;
clusterType = ClusterType::create(*session(), clusterTypeName);
clusterType = ClusterType::create(session, clusterTypeName);
_clusterTypes.insert(clusterType);
needRescan = true;
@@ -94,7 +116,7 @@ ScanSettings::setClusterTypes(const std::set<std::string>& clusterTypeNames)
}
// Delete no longer existing cluster types
for (auto clusterType : _clusterTypes)
for (ClusterType::pointer& clusterType : _clusterTypes)
{
if (std::none_of(clusterTypeNames.begin(), clusterTypeNames.end(),
[clusterType](const std::string& name) { return name == clusterType->getName(); }))
@@ -108,6 +130,5 @@ ScanSettings::setClusterTypes(const std::set<std::string>& clusterTypeNames)
_scanVersion += 1;
}
} // namespace Database
+5 -2
View File
@@ -27,6 +27,7 @@
namespace Database {
class ClusterType;
class Session;
class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
{
@@ -40,7 +41,9 @@ class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
Monthly
};
static pointer get(Wt::Dbo::Session& session);
static void init(Session& session);
static pointer get(Session& session);
// Getters
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 setUpdateStartTime(Wt::WTime t) { _startTime = t; }
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);
template<class Action>
@@ -17,10 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DatabaseHandler.hpp"
#include <Wt/Dbo/FixedSqlConnectionPool.h>
#include <Wt/Dbo/backend/Sqlite3.h>
#include "Session.hpp"
#include <Wt/Auth/Dbo/AuthInfo.h>
#include <Wt/Auth/Dbo/UserDatabase.h>
@@ -43,6 +40,7 @@
#include "TrackArtistLink.hpp"
#include "TrackList.hpp"
#include "TrackFeatures.hpp"
#include "User.hpp"
namespace Database {
@@ -60,15 +58,24 @@ class VersionInfo
public:
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)
versionInfo = session.add(std::make_unique<VersionInfo>());
return session.getDboSession().add(std::make_unique<VersionInfo>());
return versionInfo;
}
static VersionInfo::pointer get(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<VersionInfo>();
}
Version getVersion() const { return _version; }
void setVersion(Version version) { _version = static_cast<int>(version); }
@@ -82,18 +89,18 @@ class VersionInfo
int _version {LMS_DATABASE_VERSION};
};
static
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)"};
Version version;
try
{
version = VersionInfo::get(session)->getVersion();
version = VersionInfo::getOrCreate(*this)->getVersion();
LMS_LOG(DB, INFO) << "Database version = " << version;
if (version == LMS_DATABASE_VERSION)
return;
}
@@ -109,30 +116,30 @@ doDatabaseMigrationIfNeeded(Wt::Dbo::Session& session)
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,
"artist_id" bigint,
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_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_artist" on "user_artist_starred" ("artist_id");)");
session.execute(R"(CREATE TABLE IF NOT EXISTS "user_release_starred" (
_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 TABLE IF NOT EXISTS "user_release_starred" (
"user_id" bigint,
"release_id" bigint,
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_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_release" on "user_release_starred" ("release_id");)");
session.execute(R"(CREATE TABLE IF NOT EXISTS "user_track_starred" (
_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 TABLE IF NOT EXISTS "user_track_starred" (
"user_id" bigint,
"track_id" bigint,
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_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_track" on "user_track_starred" ("track_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");)");
break;
default:
@@ -140,12 +147,12 @@ doDatabaseMigrationIfNeeded(Wt::Dbo::Session& session)
throw LmsException {outdatedMsg};
}
VersionInfo::get(session).modify()->setVersion(LMS_DATABASE_VERSION);
VersionInfo::get(*this).modify()->setVersion(LMS_DATABASE_VERSION);
}
void
Handler::configureAuth(void)
Session::configureAuth(void)
{
authService.setEmailVerificationEnabled(false);
authService.setAuthTokensEnabled(true, "lmsauth");
@@ -176,19 +183,20 @@ Handler::configureAuth(void)
}
const Wt::Auth::AuthService&
Handler::getAuthService()
Session::getAuthService()
{
return authService;
}
const Wt::Auth::PasswordService&
Handler::getPasswordService()
Session::getPasswordService()
{
return passwordService;
}
Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
Session::Session(std::shared_timed_mutex& mutex, Wt::Dbo::SqlConnectionPool& connectionPool)
: _mutex {mutex}
{
_session.setConnectionPool(connectionPool);
@@ -212,9 +220,76 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
_session.mapClass<AuthInfo::AuthTokenType>("auth_token");
_session.mapClass<User>("user");
try {
Wt::Dbo::Transaction transaction {_session};
_users = std::make_unique<UserDatabase>(_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();
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();
}
doDatabaseMigrationIfNeeded(_session);
doDatabaseMigrationIfNeeded();
// Indexes
{
Wt::Dbo::Transaction transaction {_session};
// 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_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE)");
_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)");
}
_users = new UserDatabase(_session);
}
// Initial settings tables
{
auto uniqueTransaction {createUniqueTransaction()};
Handler::~Handler()
{
delete _users;
ScanSettings::init(*this);
SimilaritySettings::init(*this);
}
}
void
Handler::optimize()
Session::optimize()
{
Wt::Dbo::Transaction transaction {_session};
auto uniqueTransaction {createUniqueTransaction()};
_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&
Handler::getUserDatabase()
Session::getUserDatabase()
{
return *_users;
}
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())
return getUser(_login.user());
@@ -286,10 +432,10 @@ Handler::getCurrentUser()
}
User::pointer
Handler::getUser(const Wt::Auth::User& authUser)
Session::getUser(const Wt::Auth::User& authUser)
{
if (!authUser.isValid()) {
LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser";
LMS_LOG(DB, ERROR) << "Session::getUser: invalid authUser";
return User::pointer();
}
@@ -299,7 +445,7 @@ Handler::getUser(const Wt::Auth::User& authUser)
}
User::pointer
Handler::getUser(const std::string& loginName)
Session::getUser(const std::string& loginName)
{
auto authUser {getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, loginName)};
if (!authUser.isValid())
@@ -308,36 +454,4 @@ Handler::getUser(const std::string& loginName)
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
+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
+27 -13
View File
@@ -22,6 +22,7 @@
#include "utils/Logger.hpp"
#include "utils/Utils.hpp"
#include "Session.hpp"
#include "TrackFeatures.hpp"
namespace Database {
@@ -33,7 +34,7 @@ struct TrackFeatureInfo
double weight;
};
static std::vector<TrackFeatureInfo> defaultFeatures =
static const std::vector<TrackFeatureInfo> defaultFeatures =
{
{ "lowlevel.spectral_contrast_coeffs.median", 6, 1. },
{ "lowlevel.erbbands.median", 40, 1. },
@@ -53,24 +54,37 @@ _settings(settings)
}
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
SimilaritySettings::get(Wt::Dbo::Session& session)
void
SimilaritySettings::init(Session& session)
{
pointer settings = session.find<SimilaritySettings>();
if (!settings)
{
settings = session.add(std::make_unique<SimilaritySettings>());
session.checkUniqueLocked();
for (const auto& feature : defaultFeatures)
SimilaritySettingsFeature::create(session, settings, feature.name, feature.nbDimensions, feature.weight);
}
pointer settings {session.getDboSession().find<SimilaritySettings>()};
if (settings)
return;
return settings;
settings = session.getDboSession().add(std::make_unique<SimilaritySettings>());
for (const auto& feature : defaultFeatures)
SimilaritySettingsFeature::create(session, settings, feature.name, feature.nbDimensions, feature.weight);
}
SimilaritySettings::pointer
SimilaritySettings::get(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<SimilaritySettings>();
}
std::vector<Wt::Dbo::ptr<SimilaritySettingsFeature>>
+4 -2
View File
@@ -23,6 +23,7 @@
namespace Database {
class Session;
class SimilaritySettings;
class SimilaritySettingsFeature : public Wt::Dbo::Dbo<SimilaritySettingsFeature>
@@ -33,7 +34,7 @@ class SimilaritySettingsFeature : public Wt::Dbo::Dbo<SimilaritySettingsFeature
SimilaritySettingsFeature() = default;
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; } ;
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>;
// Utils
static pointer get(Wt::Dbo::Session& session);
static void init(Session& session);
static pointer get(Session& session);
// Accessors Read
std::size_t getVersion() const { return _settingsVersion; }
+70 -38
View File
@@ -27,6 +27,7 @@
#include "Cluster.hpp"
#include "Release.hpp"
#include "TrackFeatures.hpp"
#include "Session.hpp"
#include "SqlQuery.hpp"
namespace Database {
@@ -38,18 +39,22 @@ _filePath( p.string() )
}
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)};
return std::vector<Track::pointer>(std::cbegin(res), std::cend(res));
}
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)
.orderBy("RANDOM()")};
@@ -57,67 +62,88 @@ Track::getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> limi
}
std::vector<IdType>
Track::getAllIds(Wt::Dbo::Session& session)
Track::getAllIds(Session& session)
{
Wt::Dbo::Transaction transaction(session);
Wt::Dbo::collection<IdType> res = session.query<IdType>("SELECT id from track");
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM track");
return std::vector<IdType>(res.begin(), res.end());
}
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::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::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::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>
Track::getAllPaths(Wt::Dbo::Session& session)
Track::getAllPaths(Session& session)
{
Wt::Dbo::Transaction transaction(session);
Wt::Dbo::collection<std::string> res = session.query<std::string>("SELECT file_path from track");
session.checkSharedLocked();
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());
}
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());
}
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)
.orderBy("file_added DESC")
.limit(limit);
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<pointer>(res.begin(), res.end());
}
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")
.where("LENGTH(t.mbid) > 0")
.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>
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")
.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());
}
@@ -153,10 +179,12 @@ Track::hasTrackFeatures() const
static
Wt::Dbo::Query< Track::pointer >
getQuery(Wt::Dbo::Session& session,
getQuery(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<std::string> keywords)
const std::vector<std::string>& keywords)
{
session.checkSharedLocked();
WhereClause where;
std::ostringstream oss;
@@ -184,7 +212,7 @@ getQuery(Wt::Dbo::Session& session,
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())
query.bind(bindArg);
@@ -193,13 +221,15 @@ getQuery(Wt::Dbo::Session& session,
}
std::vector<Track::pointer>
Track::getByFilter(Wt::Dbo::Session& session,
Track::getByFilter(Session& session,
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> size,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords)
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
@@ -218,16 +248,18 @@ Track::getByFilter(Wt::Dbo::Session& session,
}
std::vector<Track::pointer>
Track::getByFilter(Wt::Dbo::Session& session,
Track::getByFilter(Session& session,
const std::set<IdType>& clusters)
{
session.checkSharedLocked();
bool moreResults;
return getByFilter(session,
clusters,
std::vector<std::string> {},
boost::optional<std::size_t> {},
boost::optional<std::size_t> {},
{},
{},
{},
moreResults);
}
+16 -16
View File
@@ -53,29 +53,29 @@ class Track : public Wt::Dbo::Dbo<Track>
Track(const boost::filesystem::path& p);
// Find utility functions
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
static pointer getById(Wt::Dbo::Session& session, IdType id);
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
static pointer getByPath(Session& session, const boost::filesystem::path& p);
static pointer getById(Session& session, IdType id);
static pointer getByMBID(Session& session, const std::string& MBID);
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters); // tracks that belong to these clusters
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
const std::set<IdType>& clusters, // tracks that belong to these clusters
const std::vector<std::string> keywords, // name must match all of these keywords
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // tracks that belong to these clusters
const std::vector<std::string>& keywords, // name must match all of these keywords
boost::optional<std::size_t> offset,
boost::optional<std::size_t> size,
bool& moreExpected);
static std::vector<pointer> getAll(Wt::Dbo::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<IdType> getAllIds(Wt::Dbo::Session& session); // nested transaction
static std::vector<boost::filesystem::path> getAllPaths(Wt::Dbo::Session& session); // nested transaction
static std::vector<pointer> getMBIDDuplicates(Wt::Dbo::Session& session);
static std::vector<pointer> getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int size = 1);
static std::vector<pointer> getAllWithMBIDAndMissingFeatures(Wt::Dbo::Session& session); // nested transaction
static std::vector<IdType> getAllIdsWithFeatures(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {}); // nested transaction
static std::vector<pointer> getAll(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(Session& session); // nested transaction
static std::vector<boost::filesystem::path> getAllPaths(Session& session); // nested transaction
static std::vector<pointer> getMBIDDuplicates(Session& session);
static std::vector<pointer> getLastAdded(Session& session, const Wt::WDateTime& after, boost::optional<std::size_t> size = 1);
static std::vector<pointer> getAllWithMBIDAndMissingFeatures(Session& session);
static std::vector<IdType> getAllIdsWithFeatures(Session& session, boost::optional<std::size_t> limit = {});
// 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
void setScanVersion(std::size_t version) { _scanVersion = version; }
+8 -2
View File
@@ -20,6 +20,7 @@
#include "TrackArtistLink.hpp"
#include "Artist.hpp"
#include "Session.hpp"
#include "Track.hpp"
namespace Database {
@@ -32,9 +33,14 @@ _artist {artist}
}
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 {
class Artist;
class Session;
class Track;
class TrackArtistLink
@@ -51,7 +52,7 @@ class TrackArtistLink
TrackArtistLink() = default;
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<Artist> getArtist() const { return _artist; }
+4 -2
View File
@@ -23,6 +23,7 @@
#include <boost/property_tree/json_parser.hpp>
#include "utils/Logger.hpp"
#include "Session.hpp"
#include "Track.hpp"
namespace Database {
@@ -34,9 +35,10 @@ _track(track)
}
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>
+2 -1
View File
@@ -27,6 +27,7 @@
namespace Database {
class Session;
class Track;
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);
// 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;
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 "Cluster.hpp"
#include "Release.hpp"
#include "Session.hpp"
#include "User.hpp"
#include "Track.hpp"
@@ -41,38 +42,35 @@ TrackList::TrackList(const std::string& name, Type type, bool isPublic, Wt::Dbo:
}
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);
auto res = session.add( std::make_unique<TrackList>(name, type, isPublic, user) );
session.flush();
auto res = session.getDboSession().add( std::make_unique<TrackList>(name, type, isPublic, user) );
session.getDboSession().flush();
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::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("type = ?").bind(type)
.where("user_id = ?").bind(user.id());
}
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())
.orderBy("name COLLATE NOCASE");
@@ -80,9 +78,11 @@ TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user)
}
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("type = ?").bind(type)
.orderBy("name COLLATE NOCASE");
@@ -91,9 +91,11 @@ TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user, Type type)
}
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();
}
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>
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::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(tracklist);
auto res = session.add( std::make_unique<TrackListEntry>( track, tracklist) );
session.flush();
auto res = session.getDboSession().add( std::make_unique<TrackListEntry>( track, tracklist) );
session.getDboSession().flush();
return res;
}
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
+12 -13
View File
@@ -30,11 +30,12 @@
namespace Database {
class Artist;
class Cluster;
class Release;
class User;
class Session;
class Track;
class TrackListEntry;
class Cluster;
class User;
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;
// Search utility
static pointer get(Wt::Dbo::Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user);
static pointer getById(Wt::Dbo::Session& session, IdType tracklistId);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user, Type type);
static pointer get(Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user);
static pointer getById(Session& session, IdType tracklistId);
static std::vector<pointer> getAll(Session& session, Wt::Dbo::ptr<User> user);
static std::vector<pointer> getAll(Session& session, Wt::Dbo::ptr<User> user, Type type);
// 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
std::string getName() const { return _name; }
@@ -73,9 +74,7 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
// Modifiers
void setName(const std::string& name) { _name = name; }
void setIsPublic(bool isPublic) { _isPublic = isPublic; }
Wt::Dbo::ptr<TrackListEntry> add(IdType trackId);
void clear() { _entries.clear(); }
void shuffle();
void clear() { _entries.clear(); }
// Get tracks, ordered by position
std::size_t getCount() const;
@@ -120,13 +119,13 @@ class TrackListEntry : public Wt::Dbo::Dbo<TrackListEntry>
using pointer = Wt::Dbo::ptr<TrackListEntry>;
TrackListEntry();
TrackListEntry() = default;
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
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
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
+1 -1
View File
@@ -19,7 +19,7 @@
#pragma once
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/ptr.h>
namespace Database {
using IdType = Wt::Dbo::dbo_default_traits::IdType;
+32 -29
View File
@@ -21,11 +21,15 @@
#include "Artist.hpp"
#include "Release.hpp"
#include "Session.hpp"
#include "Track.hpp"
#include "TrackList.hpp"
namespace Database {
static const std::string playedListName {"__played_tracks__"};
static const std::string queuedListName {"__queued_tracks__"};
const std::set<Bitrate>
User::audioTranscodeAllowedBitrates =
{
@@ -37,35 +41,48 @@ User::audioTranscodeAllowedBitrates =
};
User::User()
: _maxAudioTranscodeBitrate{static_cast<int>(*audioTranscodeAllowedBitrates.rbegin())}
: _maxAudioTranscodeBitrate {static_cast<int>(*audioTranscodeAllowedBitrates.rbegin())}
{
}
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());
}
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;
}
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::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
@@ -95,35 +112,21 @@ User::getMaxAudioTranscodeBitrate(void) const
}
Wt::Dbo::ptr<TrackList>
User::getPlayedTrackList() const
User::getPlayedTrackList(Session& session) const
{
static const std::string listName = "__played_tracks__";
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
session.checkSharedLocked();
auto res = TrackList::get(*session(), listName, TrackList::Type::Internal, self());
if (!res)
res = TrackList::create(*session(), listName, TrackList::Type::Internal, false, self());
return res;
return TrackList::get(session, playedListName, TrackList::Type::Internal, self());
}
Wt::Dbo::ptr<TrackList>
User::getQueuedTrackList() const
User::getQueuedTrackList(Session& session) const
{
static const std::string listName = "__queued_tracks__";
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
session.checkSharedLocked();
auto res = TrackList::get(*session(), listName, TrackList::Type::Internal, self());
if (!res)
res = TrackList::create(*session(), listName, TrackList::Type::Internal, false, self());
return res;
return TrackList::get(session, queuedListName, TrackList::Type::Internal, self());
}
void
+8 -9
View File
@@ -22,17 +22,15 @@
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Auth/Dbo/AuthInfo.h>
#include "Types.hpp"
namespace Database {
class User;
using AuthInfo = Wt::Auth::Dbo::AuthInfo<User>;
class Artist;
class Release;
class Session;
class TrackList;
class Track;
@@ -69,12 +67,13 @@ class User : public Wt::Dbo::Dbo<User>
User();
// utility
static pointer create(Wt::Dbo::Session& session);
static pointer create(Session& session);
// accessors
static pointer getById(Wt::Dbo::Session& session, IdType id);
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
static pointer getDemo(Wt::Dbo::Session& session);
static pointer getById(Session& session, IdType id);
static pointer getByLoginName(const std::string& loginName);
static std::vector<pointer> getAll(Session& session);
static pointer getDemo(Session& session);
// write
void setType(Type type) { _type = type; }
@@ -97,8 +96,8 @@ class User : public Wt::Dbo::Dbo<User>
bool isRepeatAllSet() const { return _repeatAll; }
bool isRadioSet() const { return _radio; }
Wt::Dbo::ptr<TrackList> getQueuedTrackList() const;
Wt::Dbo::ptr<TrackList> getPlayedTrackList() const;
Wt::Dbo::ptr<TrackList> getPlayedTrackList(Session& session) const;
Wt::Dbo::ptr<TrackList> getQueuedTrackList(Session& session) const;
void starArtist(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]);
Av::AvInit();
Av::Transcoder::init();
Database::Handler::configureAuth();
Database::Session::configureAuth();
// 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;
// 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);
@@ -140,7 +140,7 @@ int main(int argc, char* argv[])
ServiceProvider<Similarity::Searcher>::create(similarityFeaturesScannerAddon);
API::Subsonic::SubsonicResource subsonicResource {*connectionPool};
API::Subsonic::SubsonicResource subsonicResource {database};
// bind API resources
if (Config::instance().getBool("api-subsonic", true))
@@ -152,7 +152,7 @@ int main(int argc, char* argv[])
// bind UI entry point
server.addEntryPoint(Wt::EntryPointType::Application,
std::bind(UserInterface::LmsApplication::create,
std::placeholders::_1, std::ref(*connectionPool), std::ref(appGroups)));
std::placeholders::_1, std::ref(database), std::ref(appGroups)));
// Start
LMS_LOG(MAIN, INFO) << "Starting media scanner...";
+53 -61
View File
@@ -85,7 +85,7 @@ isPathInParentPath(const boost::filesystem::path& path, const boost::filesystem:
}
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;
@@ -129,7 +129,7 @@ getOrCreateArtists(Wt::Dbo::Session& session, const std::vector<MetaData::Artist
}
Release::pointer
getOrCreateRelease(Wt::Dbo::Session& session, const MetaData::Album& album)
getOrCreateRelease(Session& session, const MetaData::Album& album)
{
Release::pointer release;
@@ -166,7 +166,7 @@ getOrCreateRelease(Wt::Dbo::Session& session, const MetaData::Album& album)
}
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;
@@ -193,8 +193,8 @@ getOrCreateClusters(Wt::Dbo::Session& session, const MetaData::Clusters& cluster
namespace Scanner {
MediaScanner::MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool)
: _db {connectionPool}
MediaScanner::MediaScanner(std::unique_ptr<Database::Session> dbSession)
: _dbSession {std::move(dbSession)}
{
_ioService.setThreadCount(1);
@@ -443,36 +443,36 @@ MediaScanner::scan(boost::system::error_code err)
}
LMS_LOG(DBUPDATER, INFO) << "Optimizing db...";
_db.optimize();
_dbSession->optimize();
LMS_LOG(DBUPDATER, INFO) << "Optimize db done!";
}
void
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();
_scanVersion = scanSettings->getScanVersion();
_startTime = scanSettings->getUpdateStartTime();
_updatePeriod = scanSettings->getUpdatePeriod();
_scanVersion = scanSettings->getScanVersion();
_startTime = scanSettings->getUpdateStartTime();
_updatePeriod = scanSettings->getUpdatePeriod();
_fileExtensions = scanSettings->getAudioFileExtensions();
_mediaDirectory = scanSettings->getMediaDirectory();
_fileExtensions = scanSettings->getAudioFileExtensions();
_mediaDirectory = scanSettings->getMediaDirectory();
auto clusterTypes = scanSettings->getClusterTypes();
std::set<std::string> clusterTypeNames;
auto clusterTypes = scanSettings->getClusterTypes();
std::set<std::string> clusterTypeNames;
std::transform(clusterTypes.begin(), clusterTypes.end(),
std::inserter(clusterTypeNames, clusterTypeNames.begin()),
[](ClusterType::pointer clusterType) -> std::string { return clusterType->getName(); });
std::transform(std::cbegin(clusterTypes), std::cend(clusterTypes),
std::inserter(clusterTypeNames, clusterTypeNames.begin()),
[](ClusterType::pointer clusterType) { return clusterType->getName(); });
_metadataParser.setClusterTypeNames(clusterTypeNames);
transaction.commit();
_metadataParser.setClusterTypeNames(clusterTypeNames);
}
for (auto& addon : _addons)
addon->refreshSettings();
@@ -506,9 +506,9 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
if (!forceScan)
{
// 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)
{
@@ -526,9 +526,9 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
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 found a least one audio stream
@@ -572,29 +572,27 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
}
// ***** Clusters
std::vector<Cluster::pointer> clusters {getOrCreateClusters(_db.getSession(), trackInfo->clusters)};
std::vector<Cluster::pointer> clusters {getOrCreateClusters(*_dbSession, trackInfo->clusters)};
// ***** Artists
std::vector<Artist::pointer> artists {getOrCreateArtists(_db.getSession(), trackInfo->artists)};
std::vector<Artist::pointer> artists {getOrCreateArtists(*_dbSession, trackInfo->artists)};
// ***** Release artists
std::vector<Artist::pointer> releaseArtists {getOrCreateArtists(_db.getSession(), trackInfo->albumArtists)};
std::vector<Artist::pointer> releaseArtists {getOrCreateArtists(*_dbSession, trackInfo->albumArtists)};
// ***** Release
Release::pointer release;
if (trackInfo->album)
release = getOrCreateRelease(_db.getSession(), *trackInfo->album);
release = getOrCreateRelease(*_dbSession, *trackInfo->album);
// If file already exist, update data
// Otherwise, create it
bool trackAdded {false};
if (!track)
{
// Create a new song
track = Track::create(_db.getSession(), file);
track = Track::create(*_dbSession, file);
LMS_LOG(DBUPDATER, INFO) << "Adding '" << file.string() << "'";
stats.additions++;
trackAdded = true;
}
else
{
@@ -615,10 +613,10 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
track.modify()->clearArtistLinks();
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)
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()->setRelease(release);
@@ -640,19 +638,9 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
track.modify()->setHasCover(trackInfo->hasCover);
track.modify()->setCopyright(trackInfo->copyright);
track.modify()->setCopyrightURL(trackInfo->copyrightURL);
transaction.commit();
for (auto& addon : _addons)
{
if (trackAdded)
addon->trackAdded(track.id());
else
addon->trackUpdated(track.id());
}
}
void
void
MediaScanner::scanMediaDirectory(boost::filesystem::path mediaDirectory, bool forceScan, Stats& stats)
{
boost::system::error_code ec;
@@ -728,7 +716,11 @@ checkFile(const boost::filesystem::path& p, const boost::filesystem::path& media
void
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...";
for (const auto& trackPath : trackPaths)
@@ -738,9 +730,9 @@ MediaScanner::removeMissingTracks(Stats& stats)
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)
{
track.remove();
@@ -755,11 +747,11 @@ MediaScanner::removeOrphanEntries()
{
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters...";
{
Wt::Dbo::Transaction transaction(_db.getSession());
auto transaction {_dbSession->createUniqueTransaction()};
// Now process orphan Cluster (no track)
auto clusters = Cluster::getAllOrphans(_db.getSession());
for (auto cluster : clusters)
auto clusters {Cluster::getAllOrphans(*_dbSession)};
for (auto& cluster : clusters)
{
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'";
cluster.remove();
@@ -768,10 +760,10 @@ MediaScanner::removeOrphanEntries()
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan artists...";
{
Wt::Dbo::Transaction transaction(_db.getSession());
auto transaction {_dbSession->createUniqueTransaction()};
auto artists = Artist::getAllOrphans(_db.getSession());
for (auto artist : artists)
auto artists {Artist::getAllOrphans(*_dbSession)};
for (auto& artist : artists)
{
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
artist.remove();
@@ -780,10 +772,10 @@ MediaScanner::removeOrphanEntries()
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases...";
{
Wt::Dbo::Transaction transaction(_db.getSession());
auto transaction {_dbSession->createUniqueTransaction()};
auto releases = Release::getAllOrphans(_db.getSession());
for (auto release : releases)
auto releases {Release::getAllOrphans(*_dbSession)};
for (auto& release : releases)
{
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'";
release.remove();
@@ -798,10 +790,10 @@ MediaScanner::checkDuplicatedAudioFiles(Stats& stats)
{
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());
for (Track::pointer track : tracks)
const std::vector<Track::pointer> tracks = Database::Track::getMBIDDuplicates(*_dbSession);
for (const Track::pointer& track : tracks)
{
LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath().string() << " - " << track->getName();
stats.duplicateMBID++;
+4 -3
View File
@@ -29,7 +29,7 @@
#include <boost/asio/system_timer.hpp>
#include "database/ScanSettings.hpp"
#include "database/DatabaseHandler.hpp"
#include "database/Session.hpp"
#include "metadata/TagLibParser.hpp"
#include "MediaScannerAddon.hpp"
@@ -39,7 +39,7 @@ namespace Scanner {
class MediaScanner
{
public:
MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool);
MediaScanner(std::unique_ptr<Database::Session> dbSession);
void setAddon(MediaScannerAddon& addon);
@@ -120,6 +120,7 @@ class MediaScanner
void removeOrphanEntries();
void checkDuplicatedAudioFiles(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 notifyInProgress(Stats& stats);
@@ -130,7 +131,7 @@ class MediaScanner
Wt::Signal<Stats> _sigScanInProgress;
std::chrono::system_clock::time_point _lastScanInProgressEmit {};
Wt::Signal<Wt::WDateTime> _sigScheduled;
Database::Handler _db;
std::unique_ptr<Database::Session> _dbSession;
MetaData::TagLibParser _metadataParser;
std::vector<MediaScannerAddon*> _addons;
+2 -1
View File
@@ -29,11 +29,12 @@ class MediaScannerAddon
virtual void refreshSettings() = 0;
virtual void requestStop() = 0;
virtual void preScanComplete() = 0;
virtual void trackAdded(Database::IdType trackId) = 0;
virtual void trackToRemove(Database::IdType trackId) = 0;
virtual void trackUpdated(Database::IdType trackId) = 0;
virtual void preScanComplete() = 0;
};
} // ns Scanner
+12 -13
View File
@@ -31,17 +31,16 @@ Searcher::Searcher(FeaturesScannerAddon& somAddon)
{}
static
Database::SimilaritySettings::EngineType getEngineType(Wt::Dbo::Session& session)
Database::SimilaritySettings::EngineType getEngineType(Database::Session& dbSession)
{
Wt::Dbo::Transaction transaction{session};
return Database::SimilaritySettings::get(session)->getEngineType();
auto transaction {dbSession.createSharedTransaction()};
return Database::SimilaritySettings::get(dbSession)->getEngineType();
}
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(session)};
auto engineType {getEngineType(dbSession)};
auto somSearcher {_somAddon.getSearcher()};
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);
}
else
return ClusterSearcher::getSimilarTracks(session, trackIds, maxCount);
return ClusterSearcher::getSimilarTracks(dbSession, trackIds, maxCount);
}
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()};
if (engineType == Database::SimilaritySettings::EngineType::Features
@@ -67,13 +66,13 @@ Searcher::getSimilarReleases(Wt::Dbo::Session& session, Database::IdType release
return somSearcher->getSimilarReleases(releaseId, maxCount);
}
else
return ClusterSearcher::getSimilarReleases(session, releaseId, maxCount);
return ClusterSearcher::getSimilarReleases(dbSession, releaseId, maxCount);
}
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()};
if (engineType == Database::SimilaritySettings::EngineType::Features
@@ -83,7 +82,7 @@ Searcher::getSimilarArtists(Wt::Dbo::Session& session, Database::IdType artistId
return somSearcher->getSimilarArtists(artistId, maxCount);
}
else
return ClusterSearcher::getSimilarArtists(session, artistId, maxCount);
return ClusterSearcher::getSimilarArtists(dbSession, artistId, maxCount);
}
} // ns Similarity
+8 -3
View File
@@ -24,6 +24,11 @@
#include "database/Types.hpp"
namespace Database
{
class Session;
}
namespace Similarity {
class FeaturesScannerAddon;
@@ -34,9 +39,9 @@ class Searcher
Searcher(FeaturesScannerAddon& somAddon);
// 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> getSimilarReleases(Wt::Dbo::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> getSimilarTracks(Database::Session& session, const std::set<Database::IdType>& tracksId, std::size_t maxCount);
std::vector<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount);
std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount);
private:
@@ -25,23 +25,21 @@
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "utils/Utils.hpp"
namespace Similarity {
namespace ClusterSearcher {
static
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;
for (auto trackId : trackIds)
{
auto track = Database::Track::getById(session, trackId);
auto track {Database::Track::getById(dbSession, trackId)};
if (!track)
continue;
@@ -56,9 +54,10 @@ getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::IdType>& tr
std::vector<Database::IdType> sortedClusterIds;
uniqueAndSortedByOccurence(clusterIds.begin(), clusterIds.end(), std::back_inserter(sortedClusterIds));
std::vector<Database::IdType> res;
for (auto clusterId : clusterIds)
{
auto cluster = Database::Cluster::getById(session, clusterId);
auto cluster {Database::Cluster::getById(dbSession, clusterId)};
if (!cluster)
continue;
@@ -88,13 +87,21 @@ getSimilarTracks(Wt::Dbo::Session& session, const std::set<Database::IdType>& tr
}
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;
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)
return res;
@@ -104,11 +111,10 @@ getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::s
for (const auto& releaseTrack : releaseTracks)
releaseTrackIds.insert(releaseTrack.id());
auto trackIds = getSimilarTracks(session, releaseTrackIds, maxCount * 5);
auto trackIds {getSimilarTracksLocked(dbSession, releaseTrackIds, maxCount * 5)};
for (auto trackId : trackIds)
{
auto track = Database::Track::getById(session, trackId);
auto track {Database::Track::getById(dbSession, trackId)};
if (!track)
continue;
@@ -129,31 +135,30 @@ getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::s
}
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;
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)
return res;
auto artistTracks = artist->getTracks();
auto artistTracks {artist->getTracks()};
std::set<Database::IdType> artistTrackIds;
for (const auto& artistTrack : artistTracks)
artistTrackIds.insert(artistTrack.id());
auto trackIds = getSimilarTracks(session, artistTrackIds, maxCount * 5);
auto trackIds {getSimilarTracksLocked(dbSession, artistTrackIds, maxCount * 5)};
for (auto trackId : trackIds)
{
auto track = Database::Track::getById(session, trackId);
auto track {Database::Track::getById(dbSession, trackId)};
if (!track)
continue;
for (auto trackArtist : track->getArtists())
for (const auto& trackArtist : track->getArtists())
{
if (!trackArtist || trackArtist.id() == artistId)
continue;
@@ -23,13 +23,17 @@
#include "database/Types.hpp"
namespace Database {
class Session;
}
namespace Similarity {
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> getSimilarReleases(Wt::Dbo::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> getSimilarTracks(Database::Session& session, const std::set<Database::IdType>& tracksId, std::size_t maxCount);
std::vector<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount);
std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount);
};
} // namespace Similarity
@@ -38,13 +38,13 @@ struct TrackInfo
};
std::vector<TrackInfo>
getTracksWithMBIDAndMissingFeatures(Wt::Dbo::Session& session)
getTracksWithMBIDAndMissingFeatures(Database::Session& dbSession)
{
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)
res.push_back({track.id(), track->getMBID()});
@@ -53,13 +53,13 @@ getTracksWithMBIDAndMissingFeatures(Wt::Dbo::Session& session)
} // namespace
FeaturesScannerAddon::FeaturesScannerAddon(Wt::Dbo::SqlConnectionPool& connectionPool)
: _db(connectionPool)
FeaturesScannerAddon::FeaturesScannerAddon(std::unique_ptr<Database::Session> dbSession)
: _dbSession {std::move(dbSession)}
{
boost::optional<Similarity::FeaturesCache> cache {Similarity::FeaturesCache::read()};
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())
std::atomic_store(&_searcher, searcher);
}
@@ -80,9 +80,9 @@ FeaturesScannerAddon::requestStop()
void
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)
return;
@@ -93,9 +93,9 @@ void
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";
return;
@@ -103,9 +103,12 @@ FeaturesScannerAddon::preScanComplete()
}
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() << ")";
if (!tracksInfo.empty())
Similarity::FeaturesCache::invalidate();
for (const TrackInfo& trackInfo : tracksInfo)
{
if (_stopRequested)
@@ -114,7 +117,6 @@ FeaturesScannerAddon::preScanComplete()
fetchFeatures(trackInfo.id, trackInfo.mbid);
}
Similarity::FeaturesCache::invalidate();
updateSearcher();
}
@@ -125,8 +127,8 @@ FeaturesScannerAddon::updateSearcher()
std::vector<Database::IdType> trackIds;
{
Wt::Dbo::Transaction transaction {_db.getSession()};
trackIds = Database::Track::getAllIdsWithFeatures(_db.getSession());
auto transaction {_dbSession->createSharedTransaction()};
trackIds = Database::Track::getAllIdsWithFeatures(*_dbSession);
}
if (trackIds.empty())
@@ -136,7 +138,7 @@ FeaturesScannerAddon::updateSearcher()
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())
{
std::atomic_store(&_searcher, searcher);
@@ -165,15 +167,15 @@ FeaturesScannerAddon::fetchFeatures(Database::IdType trackId, const std::string&
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)
return false;
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;
}
@@ -19,9 +19,7 @@
#pragma once
#include <Wt/Dbo/SqlConnectionPool.h>
#include "database/DatabaseHandler.hpp"
#include "database/Session.hpp"
#include "scanner/MediaScannerAddon.hpp"
#include "SimilarityFeaturesSearcher.hpp"
@@ -32,7 +30,7 @@ class FeaturesScannerAddon final : public Scanner::MediaScannerAddon
{
public:
FeaturesScannerAddon(Wt::Dbo::SqlConnectionPool& connectionPool);
FeaturesScannerAddon(std::unique_ptr<Database::Session> dbSession);
std::shared_ptr<FeaturesSearcher> getSearcher();
@@ -40,18 +38,19 @@ class FeaturesScannerAddon final : public Scanner::MediaScannerAddon
void refreshSettings() override {}
void requestStop() override;
void preScanComplete() override;
void trackAdded(Database::IdType trackId) override {}
void trackToRemove(Database::IdType trackId) override {}
void trackUpdated(Database::IdType trackId) override;
void preScanComplete() override;
bool fetchFeatures(Database::IdType trackId, const std::string& MBID);
void updateSearcher();
Database::Handler _db;
std::unique_ptr<Database::Session> _dbSession;
std::shared_ptr<FeaturesSearcher> _searcher;
bool _stopRequested{false};
bool _stopRequested {};
};
FeaturesScannerAddon* setFeaturesScannerAddon(FeaturesScannerAddon addon);
@@ -24,6 +24,7 @@
#include "database/Artist.hpp"
#include "database/SimilaritySettings.hpp"
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/TrackFeatures.hpp"
#include "som/DataNormalizer.hpp"
@@ -43,9 +44,9 @@ using FeatureInfoMap = std::map<std::string, FeatureInfo>;
static
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)};
@@ -68,7 +69,7 @@ getFeatureInfoMapNbDimensions(const FeatureInfoMap& featureInfoMap)
static
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}};
@@ -76,7 +77,7 @@ getInputVectorFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, con
for (auto itFeatureInfo : featuresInfo)
features[itFeatureInfo.first] = {};
Wt::Dbo::Transaction transaction {session};
auto transaction {session.createSharedTransaction()};
Database::Track::pointer track {Database::Track::getById(session, trackId)};
if (!track)
@@ -119,22 +120,27 @@ getInputVectorWeights(const FeatureInfoMap& featuresInfo, std::size_t nbDimensio
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...";
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()};
LMS_LOG(SIMILARITY, DEBUG) << "Features dimension = " << nbDimensions;
featuresInfo = getFeatureInfoMap(session);
nbDimensions = getFeatureInfoMapNbDimensions(featuresInfo);
LMS_LOG(SIMILARITY, DEBUG) << "Getting Tracks with features...";
std::vector<Database::IdType> trackIds {Database::Track::getAllIdsWithFeatures(session)};
LMS_LOG(SIMILARITY, DEBUG) << "Getting Tracks with features DONE";
LMS_LOG(SIMILARITY, DEBUG) << "Features dimension = " << nbDimensions;
transaction.commit();
LMS_LOG(SIMILARITY, DEBUG) << "Getting Tracks with features...";
trackIds = Database::Track::getAllIdsWithFeatures(session);
LMS_LOG(SIMILARITY, DEBUG) << "Getting Tracks with features DONE";
}
std::vector<SOM::InputVector> samples;
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";
}
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...";
@@ -261,7 +267,7 @@ FeaturesSearcher::getSimilarArtists(Database::IdType artistId, std::size_t maxCo
}
void
FeaturesSearcher::dump(Wt::Dbo::Session& session, std::ostream& os) const
FeaturesSearcher::dump(Database::Session& session, std::ostream& os) const
{
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 << "Ref vectors median distance = " << _networkRefVectorsDistanceMedian << std::endl;
Wt::Dbo::Transaction transaction(session);
auto transaction {session.createSharedTransaction()};
for (SOM::Coordinate y {}; y < _network->getHeight(); ++y)
{
@@ -319,7 +325,7 @@ FeaturesSearcher::toCache() const
}
void
FeaturesSearcher::init(Wt::Dbo::Session& session,
FeaturesSearcher::init(Database::Session& session,
SOM::Network network,
std::map<Database::IdType,
std::set<SOM::Position>> tracksPosition,
@@ -342,12 +348,12 @@ FeaturesSearcher::init(Wt::Dbo::Session& session,
if (stopRequested())
return;
Wt::Dbo::Transaction transaction {session};
auto transaction {session.createSharedTransaction()};
Database::IdType trackId {itTrackCoord.first};
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)
continue;
@@ -22,24 +22,27 @@
#include <map>
#include <set>
#include "database/DatabaseHandler.hpp"
#include "database/Types.hpp"
#include "som/DataNormalizer.hpp"
#include "som/Network.hpp"
#include "SimilarityFeaturesCache.hpp"
namespace Similarity {
namespace Database
{
class Session;
}
namespace Similarity {
class FeaturesSearcher
{
public:
// 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)
FeaturesSearcher(Wt::Dbo::Session& session, std::function<bool()> stopRequested);
FeaturesSearcher(Database::Session& session, std::function<bool()> stopRequested);
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> 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;
@@ -59,7 +62,7 @@ class FeaturesSearcher
using ObjectPositions = std::map<Database::IdType, std::set<SOM::Position>>;
void init(Wt::Dbo::Session& session,
void init(Database::Session& session,
SOM::Network network,
ObjectPositions tracksPosition,
std::function<bool()> stopRequested);
+12 -12
View File
@@ -34,21 +34,21 @@ namespace UserInterface {
Auth::Auth()
: Wt::WTemplateFormView(Wt::WString::tr("Lms.Auth.template"))
{
_model = std::make_shared<Wt::Auth::AuthModel>(LmsApp->getDb().getAuthService(), LmsApp->getDb().getUserDatabase());
_model->addPasswordAuth(&Database::Handler::getPasswordService());
_model = std::make_shared<Wt::Auth::AuthModel>(LmsApp->getDbSession().getAuthService(), LmsApp->getDbSession().getUserDatabase());
_model->addPasswordAuth(&Database::Session::getPasswordService());
// LoginName
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)
{
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId(std::to_string(demoUser.id()));
_model->setValue(Wt::Auth::AuthModel::LoginNameField, authUser.identity(Wt::Auth::Identity::LoginName));
_model->setValue(Wt::Auth::AuthModel::PasswordField, authUser.identity(Wt::Auth::Identity::LoginName));
const std::string userName {LmsApp->getDbSession().getUserLoginName(demoUser)};
_model->setValue(Wt::Auth::AuthModel::LoginNameField, userName );
_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"));
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);
}));
@@ -76,7 +76,7 @@ Auth::Auth()
if (user.isValid())
{
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());
if (_model->validate())
_model->login(LmsApp->getDb().getLogin());
_model->login(LmsApp->getDbSession().getLogin());
else
updateView(_model.get());
}
@@ -94,7 +94,7 @@ Auth::processAuth()
void
Auth::logout()
{
_model->logout(LmsApp->getDb().getLogin());
_model->logout(LmsApp->getDbSession().getLogin());
}
} // namespace UserInterface
+47 -45
View File
@@ -55,9 +55,9 @@
namespace UserInterface {
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*
@@ -67,11 +67,11 @@ LmsApplication::instance()
}
LmsApplication::LmsApplication(const Wt::WEnvironment& env,
Wt::Dbo::SqlConnectionPool& connectionPool,
std::unique_ptr<Database::Session> dbSession,
LmsApplicationGroupContainer& appGroups)
: Wt::WApplication(env),
_db(connectionPool),
_appGroups(appGroups)
: Wt::WApplication {env},
_dbSession {std::move(dbSession)},
_appGroups {appGroups}
{
auto bootstrapTheme = std::make_unique<Wt::WBootstrapTheme>();
bootstrapTheme->setVersion(Wt::BootstrapVersion::v3);
@@ -116,12 +116,14 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
setTitle("LMS");
// If here is no account in the database, launch the first connection wizard
bool firstConnection;
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
// Handle Media Scanner events and other session events
enableUpdates(true);
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;
@@ -132,7 +134,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
}
else
{
LmsApp->getDb().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
LmsApp->getDbSession().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
_auth = root()->addNew<Auth>();
}
}
@@ -260,7 +262,7 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin)
LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'";
for (auto& view : views)
for (const auto& view : views)
{
if (wApp->internalPathMatches(view.path))
{
@@ -284,46 +286,46 @@ LmsApplication::getApplicationGroup()
void
LmsApplication::handleAuthEvent()
{
if (!getDbSession().getLogin().loggedIn())
{
LMS_LOG(UI, INFO) << "User '" << _userIdentity << " 'logged out, session = " << sessionId();
goHomeAndQuit();
return;
}
try
{
if (!getDb().getLogin().loggedIn())
// post([this]
// {
_userIdentity = getAuthUser().identity(Wt::Auth::Identity::LoginName);
const LmsApplicationInfo info {LmsApplicationInfo::fromEnvironment(environment())};
LMS_LOG(UI, INFO) << "User '" << _userIdentity << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", session = " << sessionId();
getApplicationGroup().join(info);
getApplicationGroup().postOthers([info]
{
LMS_LOG(UI, INFO) << "User '" << _userIdentity << " 'logged out, session = " << sessionId();
LmsApp->getEvents().appOpen(info);
});
goHomeAndQuit();
return;
}
else
{
_userIdentity = getAuthUser().identity(Wt::Auth::Identity::LoginName);
LmsApplicationInfo info = LmsApplicationInfo::fromEnvironment(environment());
LMS_LOG(UI, INFO) << "User '" << _userIdentity << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", session = " << sessionId();
getApplicationGroup().join(info);
getApplicationGroup().postOthers([info]
{
LmsApp->getEvents().appOpen(info);
});
createHome();
}
createHome();
triggerUpdate();
// });
}
catch (std::exception& e)
{
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
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();
}
@@ -332,14 +334,14 @@ LmsApplication::createHome()
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
Wt::WNavigationBar* navbar = main->bindNew<Wt::WNavigationBar>("navbar-top");
navbar->setTitle("LMS", Wt::WLink(Wt::LinkType::InternalPath, "/artists"));
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"));
menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/artists"));
@@ -417,14 +419,14 @@ LmsApplication::createHome()
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
{
std::string sessionId = LmsApp->sessionId();
const std::string sessionId {LmsApp->sessionId()};
getService<Scanner::MediaScanner>()->scanComplete().connect(this, [=] (Scanner::MediaScanner::Stats stats)
{
Wt::WServer::instance()->post(sessionId, [=]
@@ -551,7 +553,7 @@ static std::string msgTypeToString(MsgType type)
void
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)
+16 -18
View File
@@ -23,9 +23,8 @@
#include <boost/optional.hpp>
#include <Wt/WApplication.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include "database/DatabaseHandler.hpp"
#include "database/Database.hpp"
#include "scanner/MediaScanner.hpp"
#include "LmsApplicationGroup.hpp"
@@ -35,6 +34,7 @@ namespace Database {
class Artist;
class Cluster;
class Release;
class User;
}
namespace UserInterface {
@@ -73,20 +73,18 @@ enum class MsgType
class LmsApplication : public Wt::WApplication
{
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,
Wt::Dbo::SqlConnectionPool& connectionPool, LmsApplicationGroupContainer& appGroups);
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Database& db, LmsApplicationGroupContainer& appGroups);
static LmsApplication* instance();
// Session application data
std::shared_ptr<ImageResource> getImageResource() { return _imageResource; }
std::shared_ptr<AudioResource> getAudioResource() { return _audioResource; }
Database::Handler& getDb() { return _db;}
Wt::Dbo::Session& getDboSession() { return _db.getSession();}
Database::Session& getDbSession() { return *_dbSession.get();}
const Wt::Auth::User& getAuthUser() { return _db.getLogin().user(); }
Database::User::pointer getUser() { return _db.getCurrentUser(); }
const Wt::Auth::User& getAuthUser() { return getDbSession().getLogin().user(); }
Wt::Dbo::ptr<Database::User> getUser() { return getDbSession().getLoggedUser(); }
Wt::WString getUserIdentity() { return _userIdentity; }
Events& getEvents() { return _events; }
@@ -118,15 +116,15 @@ class LmsApplication : public Wt::WApplication
void createHome();
Wt::Signal<> _preQuit;
Database::Handler _db;
LmsApplicationGroupContainer& _appGroups;
Events _events;
Wt::WString _userIdentity;
Auth* _auth = nullptr;
std::shared_ptr<ImageResource> _imageResource;
std::shared_ptr<AudioResource> _audioResource;
bool _isAdmin = false;
Wt::Signal<> _preQuit;
std::unique_ptr<Database::Session> _dbSession;
LmsApplicationGroupContainer& _appGroups;
Events _events;
Wt::WString _userIdentity;
Auth* _auth {};
std::shared_ptr<ImageResource> _imageResource;
std::shared_ptr<AudioResource> _audioResource;
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;
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto track = Database::Track::getById(LmsApp->getDboSession(), trackId);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
try
{
Av::MediaFile mediaFile(track->getPath());
const Av::MediaFile mediaFile {track->getPath()};
auto resource = LmsApp->getAudioResource()->getUrl(trackId);
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 {
PlayHistory::PlayHistory()
: Wt::WTemplate(Wt::WString::tr("Lms.PlayHistory.template"))
: Wt::WTemplate {Wt::WString::tr("Lms.PlayHistory.template")}
{
addFunction("tr", &Wt::WTemplate::Functions::tr);
@@ -86,10 +86,14 @@ PlayHistory::PlayHistory()
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);
_entriesContainer->insertWidget(0, createEntry(trackEntry->getTrack()));
Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
if (track)
{
Database::TrackListEntry::create(LmsApp->getDbSession(), track, LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession()));
_entriesContainer->insertWidget(0, createEntry(track));
}
});
addSome();
@@ -98,11 +102,11 @@ PlayHistory::PlayHistory()
void
PlayHistory::addSome()
{
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto trackList = LmsApp->getUser()->getPlayedTrackList();
auto trackEntries = trackList->getEntriesReverse(_entriesContainer->count(), 50);
for (auto trackEntry : trackEntries)
const Database::TrackList::pointer trackList {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())};
auto trackEntries {trackList->getEntriesReverse(_entriesContainer->count(), 50)};
for (const auto& trackEntry : trackEntries)
_entriesContainer->addWidget(createEntry(trackEntry->getTrack()));
_showMore->setHidden(static_cast<std::size_t>(_entriesContainer->count()) >= trackList->getCount());
-2
View File
@@ -23,8 +23,6 @@
#include <Wt/WPushButton.h>
#include <Wt/WTemplate.h>
#include "database/Types.hpp"
namespace UserInterface {
class PlayHistory : public Wt::WTemplate
+85 -80
View File
@@ -19,12 +19,15 @@
#include "PlayQueueView.hpp"
#include <Wt/WText.h>
#include <Wt/WText.h>
#include "database/Track.hpp"
#include "database/TrackList.hpp"
#include "main/Service.hpp"
#include "similarity/SimilaritySearcher.hpp"
#include "utils/Logger.hpp"
#include "utils/Utils.hpp"
#include "LmsApplication.hpp"
namespace UserInterface {
@@ -35,7 +38,7 @@ PlayQueue::PlayQueue()
addFunction("tr", &Wt::WTemplate::Functions::tr);
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
_repeatAll = LmsApp->getUser()->isRepeatAllSet();
_radioMode = LmsApp->getUser()->isRadioSet();
}
@@ -62,9 +65,15 @@ PlayQueue::PlayQueue()
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();
addSome();
@@ -77,7 +86,7 @@ PlayQueue::PlayQueue()
_repeatAll = !_repeatAll;
updateRepeatBtn();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
if (!LmsApp->getUser()->isDemo())
LmsApp->getUser().modify()->setRepeatAll(_repeatAll);
@@ -91,7 +100,7 @@ PlayQueue::PlayQueue()
_radioMode = !_radioMode;
updateRadioBtn();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
if (!LmsApp->getUser()->isDemo())
LmsApp->getUser().modify()->setRadio(_radioMode);
@@ -102,23 +111,21 @@ PlayQueue::PlayQueue()
LmsApp->preQuit().connect([=]
{
if (_tracklistId)
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
if (LmsApp->getUser()->isDemo())
{
LMS_LOG(UI, DEBUG) << "Removing tracklist id " << *_tracklistId;
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto tracklist = Database::TrackList::getById(LmsApp->getDboSession(), *_tracklistId);
LMS_LOG(UI, DEBUG) << "Removing tracklist id " << _tracklistId;
auto tracklist = Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId);
if (tracklist)
tracklist.remove();
}
});
updateInfo();
addSome();
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
Database::TrackList::pointer trackList;
if (!LmsApp->getUser()->isDemo())
{
@@ -126,8 +133,19 @@ PlayQueue::PlayQueue()
{
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
@@ -145,32 +163,17 @@ PlayQueue::updateRadioBtn()
Database::TrackList::pointer
PlayQueue::getTrackList()
{
Database::TrackList::pointer res;
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();
return Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId);
}
void
PlayQueue::clearTracks()
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
{
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
getTrackList().modify()->clear();
}
getTrackList().modify()->clear();
_showMore->setHidden(true);
_entriesContainer->clear();
updateInfo();
@@ -189,11 +192,12 @@ PlayQueue::load(std::size_t pos, bool play)
{
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 (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 (_radioMode && pos == tracklist->getCount() - 1)
addRadioTrack();
addRadioTrack = true;
_trackPos = pos;
auto track = tracklist->getEntry(*_trackPos)->getTrack();
trackId = track.id();
updateCurrentTrack(true);
if (!LmsApp->getUser()->isDemo())
LmsApp->getUser().modify()->setCurPlayingTrackPos(pos);
}
if (addRadioTrack)
enqueueRadioTrack();
updateCurrentTrack(true);
loadTrack.emit(trackId, play);
}
@@ -252,7 +259,7 @@ PlayQueue::playNext()
void
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())));
}
@@ -274,55 +281,58 @@ PlayQueue::updateCurrentTrack(bool selected)
}
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->getDboSession(), track, tracklist);
Database::TrackListEntry::create(LmsApp->getDbSession(), track, tracklist);
}
}
updateInfo();
addSome();
}
void
PlayQueue::enqueueTrack(Database::Track::pointer track)
PlayQueue::enqueueTrack(Database::IdType trackId)
{
enqueueTracks(std::vector<Database::Track::pointer>(1, track));
enqueueTracks({trackId});
}
void
PlayQueue::addTracks(const std::vector<Database::Track::pointer>& tracks)
PlayQueue::addTracks(const std::vector<Database::IdType>& trackIds)
{
enqueueTracks(tracks);
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", tracks.size()).arg(tracks.size()), std::chrono::milliseconds(2000));
enqueueTracks(trackIds);
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
}
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();
enqueueTracks(tracks);
enqueueTracks(trackIds);
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
PlayQueue::addSome()
{
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto tracklist = getTrackList();
auto tracklistEntries = tracklist->getEntries(_entriesContainer->count(), 50);
for (auto tracklistEntry : tracklistEntries)
for (const Database::TrackListEntry::pointer& tracklistEntry : tracklistEntries)
{
auto tracklistEntryId = tracklistEntry.id();
auto track = tracklistEntry->getTrack();
@@ -367,15 +377,15 @@ PlayQueue::addSome()
{
// 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();
}
if (_trackPos)
{
auto pos = _entriesContainer->indexOf(entry);
auto pos {_entriesContainer->indexOf(entry)};
if (pos > 0 && *_trackPos >= static_cast<std::size_t>(pos))
(*_trackPos)--;
}
@@ -391,28 +401,23 @@ PlayQueue::addSome()
}
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())
return;
auto res = getService<Similarity::Searcher>()->getSimilarTracks(LmsApp->getDboSession(), std::set<Database::IdType>(trackIds.begin(), trackIds.end()), 1);
for (auto trackId : res)
{
auto trackToAdd = Database::Track::getById(LmsApp->getDboSession(), trackId);
enqueueTrack(trackToAdd);
}
const std::vector<Database::IdType> trackToAddIds {getService<Similarity::Searcher>()->getSimilarTracks(LmsApp->getDbSession(), std::set<Database::IdType>(std::cbegin(trackIds), std::cend(trackIds)), 1)};
enqueueTracks(trackToAddIds);
}
void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Searcher> similaritySearcher)
{
}
} // namespace UserInterface
+20 -20
View File
@@ -19,23 +19,23 @@
#pragma once
#include <Wt/WCheckBox.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WPushButton.h>
#include <Wt/WSignal.h>
#include <Wt/WTemplate.h>
#include <Wt/WText.h>
#include <boost/optional.hpp>
#include "database/TrackList.hpp"
#include "database/Track.hpp"
#include "database/Types.hpp"
namespace Similarity
{
namespace Similarity {
class Finder;
}
namespace Database {
class TrackList;
}
namespace UserInterface {
class PlayQueue : public Wt::WTemplate
@@ -43,8 +43,8 @@ class PlayQueue : public Wt::WTemplate
public:
PlayQueue();
void addTracks(const std::vector<Database::Track::pointer>& tracks);
void playTracks(const std::vector<Database::Track::pointer>& tracks);
void addTracks(const std::vector<Database::IdType>& trackIds);
void playTracks(const std::vector<Database::IdType>& trackIds);
// play the next track in the queue
void playNext();
@@ -59,13 +59,13 @@ class PlayQueue : public Wt::WTemplate
Wt::Signal<> trackUnload;
private:
Database::TrackList::pointer getTrackList();
Wt::Dbo::ptr<Database::TrackList> getTrackList();
void clearTracks();
void enqueueTracks(const std::vector<Database::Track::pointer>& tracks);
void enqueueTrack(Database::Track::pointer track);
void enqueueTracks(const std::vector<Database::IdType>& trackIds);
void enqueueTrack(Database::IdType trackId);
void addSome();
void addRadioTrack();
void enqueueRadioTrack();
void updateInfo();
void updateCurrentTrack(bool selected);
void updateRepeatBtn();
@@ -77,14 +77,14 @@ class PlayQueue : public Wt::WTemplate
void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Finder> similarityFinder);
void addRadioTrackFromClusters();
bool _repeatAll = false;
bool _radioMode = false;
boost::optional<Database::IdType> _tracklistId;
Wt::WContainerWidget* _entriesContainer = nullptr;
Wt::WPushButton* _showMore = nullptr;
Wt::WText* _nbTracks = nullptr;
Wt::WText* _repeatBtn = nullptr;
Wt::WText* _radioBtn = nullptr;
bool _repeatAll {};
bool _radioMode {};
Database::IdType _tracklistId {};
Wt::WContainerWidget* _entriesContainer {};
Wt::WPushButton* _showMore {};
Wt::WText* _nbTracks {};
Wt::WText* _repeatBtn {};
Wt::WText* _radioBtn {};
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()
{
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
LmsApp->getUser().modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField)));
@@ -82,12 +82,12 @@ class SettingsModel : public Wt::WFormModel
LmsApp->getUser().modify()->setAudioTranscodeFormat(_transcodeFormatModel->getValue(*transcodeFormatRow));
if (!valueText(PasswordField).empty())
Handler::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField));
Session::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField));
}
void loadData()
{
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
setValue(TranscodeEnableField, LmsApp->getUser()->getAudioTranscodeEnable());
if (!LmsApp->getUser()->getAudioTranscodeEnable())
@@ -116,7 +116,7 @@ class SettingsModel : public Wt::WFormModel
if (!valueText(PasswordField).empty())
{
// 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())
error = res.message();
@@ -148,12 +148,12 @@ class SettingsModel : public Wt::WFormModel
{
Bitrate maxAudioBitrate;
{
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
maxAudioBitrate = LmsApp->getUser()->getMaxAudioTranscodeBitrate();
}
_transcodeBitrateModel = std::make_shared<ValueStringModel<Bitrate>>();
for (Bitrate bitrate : User::audioTranscodeAllowedBitrates)
for (const Bitrate bitrate : User::audioTranscodeAllowedBitrates)
{
if (bitrate > maxAudioBitrate)
break;
@@ -243,7 +243,7 @@ SettingsView::refreshView()
{
{
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
if (LmsApp->getUser()->isDemo())
{
+8 -8
View File
@@ -85,10 +85,10 @@ class DatabaseSettingsModel : public Wt::WFormModel
void loadData()
{
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto scanSettings {ScanSettings::get(LmsApp->getDboSession())};
auto similaritySettings {SimilaritySettings::get(LmsApp->getDboSession())};
const ScanSettings::pointer scanSettings {ScanSettings::get(LmsApp->getDbSession())};
const SimilaritySettings::pointer similaritySettings {SimilaritySettings::get(LmsApp->getDbSession())};
setValue(MediaDirectoryField, scanSettings->getMediaDirectory().string());
@@ -108,17 +108,17 @@ class DatabaseSettingsModel : public Wt::WFormModel
if (!clusterTypes.empty())
{
std::vector<std::string> names;
std::transform(clusterTypes.begin(), clusterTypes.end(),std::back_inserter(names), [](auto clusterType) { return clusterType->getName(); });
std::transform(clusterTypes.begin(), clusterTypes.end(), std::back_inserter(names), [](auto clusterType) { return clusterType->getName(); });
setValue(TagsField, joinStrings(names, " "));
}
}
void saveData()
{
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
auto scanSettings {ScanSettings::get(LmsApp->getDboSession())};
auto similaritySettings {SimilaritySettings::get(LmsApp->getDboSession())};
ScanSettings::pointer scanSettings {ScanSettings::get(LmsApp->getDbSession())};
SimilaritySettings::pointer similaritySettings {SimilaritySettings::get(LmsApp->getDbSession())};
scanSettings.modify()->setMediaDirectory(valueText(MediaDirectoryField).toUTF8());
@@ -135,7 +135,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
similaritySettings.modify()->setEngineType(_similarityEngineTypeModel->getValue(*similarityEngineTypeRow));
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:
+4 -11
View File
@@ -54,21 +54,14 @@ class InitWizardModel : public Wt::WFormModel
void saveData()
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction(LmsApp->getDbSession().createUniqueTransaction());
// Check if a user already exist
// 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");
// Create user
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));
Database::User::pointer user {LmsApp->getDbSession().createUser(valueText(AdminLoginField).toUTF8(), valueText(PasswordField).toUTF8())};
user.modify()->setType(Database::User::Type::ADMIN);
}
@@ -81,7 +74,7 @@ class InitWizardModel : public Wt::WFormModel
if (!valueText(PasswordField).empty())
{
// 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), "");
if (!res.isValid())
+26 -32
View File
@@ -78,17 +78,16 @@ class UserModel : public Wt::WFormModel
void saveData()
{
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
if (_userId)
{
// Update user
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) );
Database::User::pointer user = LmsApp->getDb().getUser( authUser );
Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
// Account
if (!valueText(PasswordField).empty())
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
LmsApp->getDbSession().updateUserPassword(user, valueText(PasswordField).toUTF8());
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
if (transcodeBitrateLimitRow)
@@ -97,12 +96,7 @@ class UserModel : public Wt::WFormModel
else
{
// Create user
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().registerNew();
Database::User::pointer user = LmsApp->getDb().createUser(authUser);
// Account
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField));
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
Database::User::pointer user = LmsApp->getDbSession().createUser(valueText(LoginField).toUTF8(), valueText(PasswordField).toUTF8());
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
if (transcodeBitrateLimitRow )
@@ -120,11 +114,9 @@ class UserModel : public Wt::WFormModel
if (!_userId)
return;
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto authUser {LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) )};
auto user {LmsApp->getDb().getUser(authUser)};
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
if (user == LmsApp->getUser())
throw LmsException("Cannot edit ourselves");
@@ -133,12 +125,14 @@ class UserModel : public Wt::WFormModel
setValue(AudioTranscodeBitrateLimitField, _bitrateModel->getString(*transcodeBitrateLimitRow));
}
Wt::WString getLogin() const
Wt::WString getLoginName() const
{
if (_userId)
{
auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) );
return authUser.identity(Wt::Auth::Identity::LoginName);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
return LmsApp->getDbSession().getUserLoginName(user);
}
else
return valueText(LoginField);
@@ -150,8 +144,8 @@ class UserModel : public Wt::WFormModel
if (field == LoginField)
{
auto user = LmsApp->getDb().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField));
if (user.isValid())
const Database::User::pointer user {LmsApp->getDbSession().getUser(valueText(LoginField).toUTF8())};
if (user)
error = Wt::WString::tr("Lms.Admin.User.user-already-exists");
}
else if (field == PasswordField)
@@ -161,13 +155,13 @@ class UserModel : public Wt::WFormModel
if (Wt::asNumber(value(DemoField)))
{
//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");
}
else
{
// 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())
error = res.message();
@@ -176,9 +170,9 @@ class UserModel : public Wt::WFormModel
}
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");
}
@@ -226,23 +220,23 @@ UserView::refreshView()
auto userId = readAs<Database::IdType>(wApp->internalPathNextPart("/admin/user/"));
LMS_LOG(UI, DEBUG) << "userId = " << (userId ? std::to_string(*userId) : "none");
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)
{
auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*userId) );
auto name = authUser.identity(Wt::Auth::Identity::LoginName);
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(name), Wt::TextFormat::Plain);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
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);
Wt::WLineEdit *lastLoginAttempt = t->bindNew<Wt::WLineEdit>("last-login-attempt");
lastLoginAttempt->setText(authUser.lastLoginAttempt().toString());
Wt::WLineEdit *lastLoginAttempt {t->bindNew<Wt::WLineEdit>("last-login-attempt")};
lastLoginAttempt->setText(LmsApp->getDbSession().getUserLastLoginAttempt(user).toString());
lastLoginAttempt->setEnabled(false);
}
else
+13 -17
View File
@@ -59,22 +59,16 @@ UsersView::refreshView()
_container->clear();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto users = Database::User::getAll(LmsApp->getDboSession());
for (auto user : users)
auto users = Database::User::getAll(LmsApp->getDbSession());
for (const auto& user : users)
{
auto userId = std::to_string(user.id());
Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"));
const Database::IdType userId {user.id()};
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId(userId);
if (!authUser.isValid()) {
LMS_LOG(UI, ERROR) << "Skipping invalid userId = " << user.id();
continue;
}
Wt::WTemplate* entry {_container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"))};
auto login = authUser.identity(Wt::Auth::Identity::LoginName);
entry->bindString("name", login, Wt::TextFormat::Plain);
entry->bindString("name", LmsApp->getDbSession().getUserLoginName(user), Wt::TextFormat::Plain);
// Create tag
if (user->isAdmin() || user->isDemo())
@@ -106,16 +100,18 @@ UsersView::refreshView()
{
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);
}
else
{
delBtn->removeChild(msgBox);
}
});
msgBox->show();
+7 -10
View File
@@ -63,21 +63,18 @@ ArtistInfo::refresh()
if (!artistId)
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 (auto artistId : artistsIds)
for (Database::IdType 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);
}
}
} // namespace UserInterface
+4 -4
View File
@@ -66,22 +66,22 @@ Artist::refresh()
if (!artistId)
return;
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto artist = Database::Artist::getById(LmsApp->getDboSession(), *artistId);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const Database::Artist::pointer artist = Database::Artist::getById(LmsApp->getDbSession(), *artistId);
if (!artist)
{
LmsApp->goHome();
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);
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);
for (auto clusters : clusterGroups)
+7 -6
View File
@@ -58,22 +58,23 @@ ArtistsInfo::refreshRecentlyAdded()
{
auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1);
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto artists = Artist::getLastAdded(LmsApp->getDboSession(), after, 5);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const std::vector<Database::Artist::pointer> artists {Artist::getLastAdded(LmsApp->getDbSession(), after, 5)};
_recentlyAddedContainer->clear();
for (auto artist : artists)
for (const Database::Artist::pointer& artist : artists)
_recentlyAddedContainer->addNew<ArtistLink>(artist);
}
void
ArtistsInfo::refreshMostPlayed()
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto artists = LmsApp->getUser()->getPlayedTrackList()->getTopArtists(5);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const std::vector<Database::Artist::pointer> artists {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopArtists(5)};
_mostPlayedContainer->clear();
for (auto artist : artists)
for (const Database::Artist::pointer& artist : artists)
_mostPlayedContainer->addNew<ArtistLink>(artist);
}
+6 -6
View File
@@ -73,17 +73,17 @@ Artists::addSome()
auto clusterIds = _filters->getClusterIds();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
bool moreResults;
auto artists = Artist::getByFilter(LmsApp->getDboSession(),
bool moreResults {};
const std::vector<Artist::pointer> artists {Artist::getByFilter(LmsApp->getDbSession(),
clusterIds,
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));
}
+43 -42
View File
@@ -25,6 +25,7 @@
#include "database/Artist.hpp"
#include "database/Release.hpp"
#include "database/Track.hpp"
#include "utils/Logger.hpp"
#include "LmsApplication.hpp"
@@ -66,9 +67,7 @@ handleContentsPathChange(Wt::WStackedWidget* stack)
{ "/tracks", IdxTracks },
};
LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'";
for (auto index : indexes)
for (const auto& index : indexes)
{
if (wApp->internalPathMatches(index.first))
{
@@ -182,102 +181,104 @@ Explore::Explore()
handleInfoPathChange(infoStack);
}
// TODO SQL this?
static std::vector<Database::Track::pointer> getArtistTracks(Wt::Dbo::Session& session, Database::IdType artistId, std::set<Database::IdType> clusters)
static
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)
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;
}
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)
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;
}
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);
if (track)
res.push_back(track);
Database::Track::pointer track {Database::Track::getById(session, trackId)};
if (!track)
return {};
return res;
return {track.id()};
}
void
Explore::handleArtistAdd(Database::IdType id)
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getArtistTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
tracksAdd.emit(getArtistTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
}
void
Explore::handleArtistPlay(Database::IdType id)
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getArtistTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
tracksPlay.emit(getArtistTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
}
void
Explore::handleReleaseAdd(Database::IdType id)
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getReleaseTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
tracksAdd.emit(getReleaseTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
}
void
Explore::handleReleasePlay(Database::IdType id)
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getReleaseTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
tracksPlay.emit(getReleaseTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
}
void
Explore::handleTrackAdd(Database::IdType id)
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getTrack(LmsApp->getDboSession(), id));
tracksAdd.emit(getTrack(LmsApp->getDbSession(), id));
}
void
Explore::handleTrackPlay(Database::IdType id)
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getTrack(LmsApp->getDboSession(), id));
tracksPlay.emit(getTrack(LmsApp->getDbSession(), id));
}
void
Explore::handleTracksAdd(std::vector<Database::Track::pointer> tracks)
Explore::handleTracksAdd(const std::vector<Database::IdType>& trackIds)
{
tracksAdd.emit(tracks);
tracksAdd.emit(trackIds);
}
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
+10 -11
View File
@@ -21,7 +21,6 @@
#include <Wt/WTemplate.h>
#include "database/Track.hpp"
#include "database/Types.hpp"
namespace UserInterface {
@@ -33,19 +32,19 @@ class Explore : public Wt::WTemplate
public:
Explore();
Wt::Signal<std::vector<Database::Track::pointer>> tracksAdd;
Wt::Signal<std::vector<Database::Track::pointer>> tracksPlay;
Wt::Signal<std::vector<Database::IdType>> tracksAdd;
Wt::Signal<std::vector<Database::IdType>> tracksPlay;
private:
void handleArtistAdd(Database::IdType id);
void handleArtistPlay(Database::IdType id);
void handleReleaseAdd(Database::IdType id);
void handleReleasePlay(Database::IdType id);
void handleTrackAdd(Database::IdType id);
void handleTrackPlay(Database::IdType id);
void handleTracksAdd(std::vector<Database::Track::pointer> tracks);
void handleTracksPlay(std::vector<Database::Track::pointer> tracks);
void handleArtistAdd(Database::IdType artistId);
void handleArtistPlay(Database::IdType artistId);
void handleReleaseAdd(Database::IdType releaseId);
void handleReleasePlay(Database::IdType releaseId);
void handleTrackAdd(Database::IdType trackId);
void handleTrackPlay(Database::IdType trackId);
void handleTracksAdd(const std::vector<Database::IdType>& trackIds);
void handleTracksPlay(const std::vector<Database::IdType>& trackIds);
Filters* _filters;
};
+23 -25
View File
@@ -49,43 +49,41 @@ Filters::showDialog()
// Populate data
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
auto types = Database::ClusterType::getAll(LmsApp->getDboSession());
for (auto type : types)
const auto types {Database::ClusterType::getAll(LmsApp->getDbSession())};
for (const Database::ClusterType::pointer& type : types)
typeCombo->addItem(Wt::WString::fromUTF8(type->getName()));
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())
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();
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();
for (auto value : values)
const auto values = clusterType->getClusters();
for (const Database::Cluster::pointer& value : values)
{
if (_filterIds.find(value.id()) == _filterIds.end())
valueCombo->addItem(Wt::WString::fromUTF8(value->getName()));
}
}));
});
dialog->setModal(true);
dialog->setMovable(false);
@@ -93,26 +91,26 @@ Filters::showDialog()
dialog->setResizable(false);
dialog->setClosable(false);
dialog->finished().connect(std::bind([=]
dialog->finished().connect([=]
{
if (dialog->result() != Wt::DialogCode::Accepted)
return;
auto type = typeCombo->valueText().toUTF8();
auto value = valueCombo->valueText().toUTF8();
const std::string type {typeCombo->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)
return;
auto cluster = clusterType->getCluster(value);
Database::Cluster::pointer cluster {clusterType->getCluster(value)};
if (!cluster)
return;
add(cluster.id());
}));
});
dialog->show();
}
@@ -120,17 +118,17 @@ Filters::showDialog()
void
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)
return;
auto res = _filterIds.insert(clusterId);
auto res {_filterIds.insert(clusterId)};
if (!res.second)
return;
auto filter = _filters->addWidget(LmsApp->createCluster(cluster, true));
auto filter {_filters->addWidget(LmsApp->createCluster(cluster, true))};
filter->clicked().connect(std::bind([=]
{
_filters->removeWidget(filter);
+5 -5
View File
@@ -68,11 +68,11 @@ ReleaseInfo::refresh()
if (!releaseId)
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)
return;
@@ -102,13 +102,13 @@ ReleaseInfo::refresh()
std::vector<Database::Release::pointer> similarReleases;
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)
similarReleases.emplace_back(similarRelease);
}
for (const auto& similarRelease : similarReleases)
for (const Database::Release::pointer& similarRelease : similarReleases)
_similarReleasesContainer->addNew<ReleaseLink>(similarRelease);
}
+6 -6
View File
@@ -67,9 +67,9 @@ Release::refresh()
if (!releaseId)
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)
{
LmsApp->goHome();
@@ -118,12 +118,12 @@ Release::refresh()
Wt::WContainerWidget* clusterContainers {t->bindNew<Wt::WContainerWidget>("clusters")};
{
auto clusterTypes {ScanSettings::get(LmsApp->getDboSession())->getClusterTypes()};
auto clusterGroups {release->getClusterGroups(clusterTypes, 3)};
const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
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 entry {clusterContainers->addWidget(LmsApp->createCluster(cluster))};
+6 -6
View File
@@ -58,24 +58,24 @@ ReleasesInfo::refreshRecentlyAdded()
{
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();
for (auto release : releases)
for (const Database::Release::pointer& release : releases)
_recentlyAddedContainer->addNew<ReleaseLink>(release);
}
void
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();
for (auto release : releases)
for (const Database::Release::pointer& release : releases)
_mostPlayedContainer->addNew<ReleaseLink>(release);
}
+6 -7
View File
@@ -71,18 +71,17 @@ Releases::refresh()
void
Releases::addSome()
{
auto searchKeywords = splitString(_search->text().toUTF8(), " ");
const auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
const auto clusterIds {_filters->getClusterIds()};
auto clusterIds = _filters->getClusterIds();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
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"));
entry->addFunction("tr", Wt::WTemplate::Functions::tr);
+5 -5
View File
@@ -74,10 +74,10 @@ TracksInfo::TracksInfo()
void
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 tracks = Track::getLastAdded(LmsApp->getDboSession(), after, 5);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const auto tracks {Track::getLastAdded(LmsApp->getDbSession(), after, 5)};
_recentlyAddedContainer->clear();
addEntries(_recentlyAddedContainer, tracks);
@@ -86,8 +86,8 @@ TracksInfo::refreshRecentlyAdded()
void
TracksInfo::refreshMostPlayed()
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto tracks = LmsApp->getUser()->getPlayedTrackList()->getTopTracks(5);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const auto tracks {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopTracks(5)};
_mostPlayedContainer->clear();
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);
playBtn->clicked().connect(std::bind([=]
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getTracks());
}));
Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=]
{
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getTracks());
}));
@@ -76,18 +74,24 @@ _filters(filters)
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)
{
auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
auto clusterIds {_filters->getClusterIds()};
const auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
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()
{
bool moreResults;
@@ -104,14 +108,15 @@ Tracks::refresh()
void
Tracks::addSome()
{
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
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"))};
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
+6 -5
View File
@@ -19,13 +19,14 @@
#pragma once
#include <boost/optional.hpp>
#include <Wt/WContainerWidget.h>
#include <Wt/WLineEdit.h>
#include <Wt/WPushButton.h>
#include <Wt/WTemplate.h>
#include "database/Types.hpp"
#include "database/Track.hpp"
namespace UserInterface {
@@ -38,15 +39,15 @@ class Tracks : public Wt::WTemplate
Wt::Signal<Database::IdType> trackAdd;
Wt::Signal<Database::IdType> trackPlay;
Wt::Signal<std::vector<Database::Track::pointer>> tracksAdd;
Wt::Signal<std::vector<Database::Track::pointer>> tracksPlay;
Wt::Signal<std::vector<Database::IdType>> tracksAdd;
Wt::Signal<std::vector<Database::IdType>> tracksPlay;
private:
void refresh();
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::Track::pointer> getTracks();
std::vector<Database::IdType> getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults);
std::vector<Database::IdType> getTracks();
Wt::WContainerWidget* _tracksContainer;
Wt::WPushButton* _showMore;
+4 -8
View File
@@ -38,9 +38,7 @@ AudioResource:: ~AudioResource()
std::string
AudioResource::getUrl(Database::IdType trackId) const
{
std::string res = url()+ "&trackid=" + std::to_string(trackId);
return res;
return url()+ "&trackid=" + std::to_string(trackId);
}
void
@@ -82,14 +80,12 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
return;
}
// transactions are not thread safe
// DbSession are not thread safe
{
Wt::WApplication::UpdateLock lock(LmsApp);
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
Database::Track::pointer track = Database::Track::getById(LmsApp->getDboSession(), trackId);
const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
if (!track)
{
LMS_LOG(UI, ERROR) << "Missing track";
-1
View File
@@ -23,7 +23,6 @@
#include "av/AvTranscoder.hpp"
#include "database/DatabaseHandler.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)
return;
auto size = readAs<std::size_t>(*sizeStr);
const auto size {readAs<std::size_t>(*sizeStr)};
if (!size || *size > maxSize)
return;
@@ -73,26 +73,26 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
if (trackIdStr)
{
auto trackId = readAs<Database::IdType>(*trackIdStr);
const auto trackId {readAs<Database::IdType>(*trackIdStr)};
if (!trackId)
return;
// transactions are not thread safe
// DbSession are not thread safe
{
Wt::WApplication::UpdateLock lock(LmsApp);
cover = getService<CoverArt::Grabber>()->getFromTrack(LmsApp->getDboSession(), *trackId, Image::Format::JPEG, *size);
Wt::WApplication::UpdateLock lock {LmsApp};
cover = getService<CoverArt::Grabber>()->getFromTrack(LmsApp->getDbSession(), *trackId, Image::Format::JPEG, *size);
}
}
else if (releaseIdStr)
{
auto releaseId = readAs<Database::IdType>(*releaseIdStr);
const auto releaseId {readAs<Database::IdType>(*releaseIdStr)};
if (!releaseId)
return;
// transactions are not thread safe
// DbSession are not thread safe
{
Wt::WApplication::UpdateLock lock(LmsApp);
cover = getService<CoverArt::Grabber>()->getFromRelease(LmsApp->getDboSession(), *releaseId, Image::Format::JPEG, *size);
Wt::WApplication::UpdateLock lock {LmsApp};
cover = getService<CoverArt::Grabber>()->getFromRelease(LmsApp->getDbSession(), *releaseId, Image::Format::JPEG, *size);
}
}
else
+1 -2
View File
@@ -24,7 +24,6 @@
#include <Wt/WResource.h>
#include "database/DatabaseHandler.hpp"
#include "database/Types.hpp"
#include "image/Image.hpp"
@@ -35,7 +34,7 @@ namespace UserInterface {
class ImageResource : public Wt::WResource
{
public:
static const std::size_t maxSize = 512;
static const std::size_t maxSize {512};
~ImageResource();
+3 -2
View File
@@ -64,9 +64,10 @@ readAs(const std::string& str)
std::vector<std::string>
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;
}
+9
View File
@@ -22,6 +22,7 @@
#include <chrono>
#include <list>
#include <map>
#include <random>
#include <string>
#include <sstream>
#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;
}
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 \
$(top_srcdir)/src/database/Artist.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/TrackFeatures.cpp \
$(top_srcdir)/src/database/TrackList.cpp \
$(top_srcdir)/src/database/Release.cpp \
$(top_srcdir)/src/database/ScanSettings.cpp \
$(top_srcdir)/src/database/Session.cpp \
$(top_srcdir)/src/database/SimilaritySettings.cpp \
$(top_srcdir)/src/database/SqlQuery.cpp \
$(top_srcdir)/src/database/Track.cpp \
File diff suppressed because it is too large Load Diff