Rework DB session + transactions. Now can handle multiple read only transactions in parallel
This commit is contained in:
@@ -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
@@ -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 \
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>>
|
||||
|
||||
@@ -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
@@ -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
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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...";
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 {};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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))};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
#include "av/AvTranscoder.hpp"
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 \
|
||||
|
||||
+187
-151
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user