diff --git a/configure.ac b/configure.ac index 32205bb5..3fab8f92 100644 --- a/configure.ac +++ b/configure.ac @@ -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], , diff --git a/src/Makefile.am b/src/Makefile.am index 0c243f71..b353bddd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/api/subsonic/SubsonicResource.cpp b/src/api/subsonic/SubsonicResource.cpp index 94d534d9..e1bf1215 100644 --- a/src/api/subsonic/SubsonicResource.cpp +++ b/src/api/subsonic/SubsonicResource.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -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> dbSessions; + +static +Database::Session& +getOrCreateDbSession(Database::Database& db) +{ + static std::mutex mutex; + + std::unique_lock 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; 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 @@ -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 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(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 getRandomAlbums(Wt::Dbo::Session& session, std::size_t offset, std::size_t size) +std::vector getRandomAlbums(Database::Session& dbSession, std::size_t offset, std::size_t size) { std::vector 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 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 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(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(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()->getSimilarArtists(context.db.getSession(), artist.id(), count)}; + auto similarArtistsId {getService()->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(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()->getSimilarArtists(context.db.getSession(), artist.id(), 5)}; + auto similarArtistsId {getService()->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(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(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(context.parameters, "songCount").get_value_or(20)}; std::size_t songOffset {getParameterAs(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 trackPositionsToRemove {getMultiParametersAs(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()->getFromTrack(context.db.getSession(), id.value, Image::Format::JPEG, size); + res.data = getService()->getFromTrack(context.dbSession, id.value, Image::Format::JPEG, size); break; case Id::Type::Release: - res.data = getService()->getFromRelease(context.db.getSession(), id.value, Image::Format::JPEG, size); + res.data = getService()->getFromRelease(context.dbSession, id.value, Image::Format::JPEG, size); break; default: throw Error {Error::CustomType::BadId}; diff --git a/src/api/subsonic/SubsonicResource.hpp b/src/api/subsonic/SubsonicResource.hpp index ca0ac427..c2179c14 100644 --- a/src/api/subsonic/SubsonicResource.hpp +++ b/src/api/subsonic/SubsonicResource.hpp @@ -23,7 +23,7 @@ #include #include -#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 getPaths(); private: - void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override; - Database::Handler _db; + Database::Database& _db; }; } // namespace diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index 7bdb8df1..1174bf1f 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -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 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 cover; + boost::optional 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 -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 -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); } diff --git a/src/cover/CoverArtGrabber.hpp b/src/cover/CoverArtGrabber.hpp index 9ae412ab..c00af025 100644 --- a/src/cover/CoverArtGrabber.hpp +++ b/src/cover/CoverArtGrabber.hpp @@ -23,10 +23,16 @@ #include #include +#include + #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 getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, Image::Format format, std::size_t size); - std::vector getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, Image::Format format, std::size_t size); + std::vector getFromTrack(Database::Session& dbSession, Database::IdType trackId, Image::Format format, std::size_t size); + std::vector 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 getFromTrack(const boost::filesystem::path& path) const; std::vector getCoverPaths(const boost::filesystem::path& directoryPath) const; diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp index 828cb37f..c172b5eb 100644 --- a/src/database/Artist.cpp +++ b/src/database/Artist.cpp @@ -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::getByName(Wt::Dbo::Session& session, const std::string& name) +Artist::getByName(Session& session, const std::string& name) { - Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string{name, 0, _maxNameLength} ); + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().find().where("name = ?").bind( std::string{name, 0, _maxNameLength} ); return std::vector(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().where("mbid = ?").bind(mbid); + session.checkSharedLocked(); + return session.getDboSession().find().where("mbid = ?").bind(mbid); } Artist::pointer -Artist::getById(Wt::Dbo::Session& session, IdType id) +Artist::getById(Session& session, IdType id) { - return session.find().where("id = ?").bind(id); + session.checkSharedLocked(); + return session.getDboSession().find().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(name, MBID)); + session.checkUniqueLocked(); + + Artist::pointer res {session.getDboSession().add(std::make_unique(name, MBID))}; + session.getDboSession().flush(); + + return res; } std::vector -Artist::getAll(Wt::Dbo::Session& session, boost::optional offset, boost::optional size) +Artist::getAll(Session& session, boost::optional offset, boost::optional size) { - Wt::Dbo::collection res = session.find() + session.checkSharedLocked(); + Wt::Dbo::collection res = session.getDboSession().find() .offset(offset ? static_cast(*offset) : -1) .limit(size ? static_cast(*size) : -1) .orderBy("sort_name COLLATE NOCASE"); @@ -76,19 +87,22 @@ Artist::getAll(Wt::Dbo::Session& session, boost::optional offset, b } std::vector -Artist::getAllOrphans(Wt::Dbo::Session& session) +Artist::getAllOrphans(Session& session) { - Wt::Dbo::collection> res {session.query>("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> res {session.getDboSession().query>("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(res.begin(), res.end()); } static Wt::Dbo::Query -getQuery(Wt::Dbo::Session& session, +getQuery(Session& session, const std::set& clusterIds, const std::vector& 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 query = session.query( oss.str() ); + Wt::Dbo::Query query = session.getDboSession().query( oss.str() ); for (const std::string& bindArg : where.getBindArgs()) { @@ -126,20 +140,22 @@ getQuery(Wt::Dbo::Session& session, } std::vector -Artist::getByFilter(Wt::Dbo::Session& session, const std::set& clusters) +Artist::getByFilter(Session& session, const std::set& clusters) { + session.checkSharedLocked(); bool more; return getByFilter(session, clusters, {}, {}, {}, more); } std::vector -Artist::getByFilter(Wt::Dbo::Session& session, +Artist::getByFilter(Session& session, const std::set& clusters, const std::vector& keywords, boost::optional offset, boost::optional size, bool& moreResults) { + session.checkSharedLocked(); Wt::Dbo::collection collection = getQuery(session, clusters, keywords) .limit(size ? static_cast(*size) + 1 : -1) .offset(offset ? static_cast(*offset) : -1); @@ -158,9 +174,10 @@ Artist::getByFilter(Wt::Dbo::Session& session, } std::vector -Artist::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional limit) +Artist::getLastAdded(Session& session, Wt::WDateTime after, boost::optional limit) { - Wt::Dbo::collection res = session.query("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 res = session.getDboSession().query("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") diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp index 92934cd5..2000f778 100644 --- a/src/database/Artist.hpp +++ b/src/database/Artist.hpp @@ -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(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 getByName(Wt::Dbo::Session& session, const std::string& name); - static std::vector getByFilter(Wt::Dbo::Session& session, + static pointer getByMBID(Session& session, const std::string& MBID); + static pointer getById(Session& session, IdType id); + static std::vector getByName(Session& session, const std::string& name); + static std::vector getByFilter(Session& session, const std::set& clusters); // at least one track that belongs to these clusters - static std::vector getByFilter(Wt::Dbo::Session& session, + static std::vector getByFilter(Session& session, const std::set& clusters, // at least one track that belongs to these clusters const std::vector& keywords, // name must match all of these keywords boost::optional offset, boost::optional size, bool& moreExpected); - static std::vector getAll(Wt::Dbo::Session& session, boost::optional offset = {}, boost::optional size = {}); - static std::vector getAllOrphans(Wt::Dbo::Session& session); // No track related - static std::vector getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional size = {}); + static std::vector getAll(Session& session, boost::optional offset = {}, boost::optional size = {}); + static std::vector getAllOrphans(Session& session); // No track related + static std::vector getLastAdded(Session& session, Wt::WDateTime after, boost::optional size = {}); // Accessors const std::string& getName(void) const { return _name; } @@ -84,8 +85,7 @@ class Artist : public Wt::Dbo::Dbo 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 void persist(Action& a) diff --git a/src/database/Cluster.cpp b/src/database/Cluster.cpp index 3fac783f..6f196496 100644 --- a/src/database/Cluster.cpp +++ b/src/database/Cluster.cpp @@ -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 type, std::string name) } Cluster::pointer -Cluster::create(Wt::Dbo::Session& session, Wt::Dbo::ptr type, std::string name) +Cluster::create(Session& session, Wt::Dbo::ptr type, std::string name) { - return session.add(std::make_unique(type, name)); + session.checkUniqueLocked(); + + Cluster::pointer res {session.getDboSession().add(std::make_unique(type, name))}; + session.getDboSession().flush(); + + return res; } std::vector -Cluster::getAll(Wt::Dbo::Session& session) +Cluster::getAll(Session& session) { - Wt::Dbo::collection res = session.find(); + session.checkSharedLocked(); + + Wt::Dbo::collection res {session.getDboSession().find()}; return std::vector(res.begin(), res.end()); } std::vector -Cluster::getAllOrphans(Wt::Dbo::Session& session) +Cluster::getAllOrphans(Session& session) { - Wt::Dbo::collection res {session.query("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 res {session.getDboSession().query("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(res.begin(), res.end()); } Cluster::pointer -Cluster::getById(Wt::Dbo::Session& session, IdType id) +Cluster::getById(Session& session, IdType id) { - return session.find().where("id = ?").bind(id); + session.checkSharedLocked(); + + return session.getDboSession().find().where("id = ?").bind(id); } void @@ -74,6 +86,9 @@ Cluster::addTrack(Wt::Dbo::ptr track) std::vector> Cluster::getTracks(int offset, int limit) const { + assert(session()); + assert(IdIsValid(self()->id())); + Wt::Dbo::collection res = session()->query("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::getAllOrphans(Wt::Dbo::Session& session) +ClusterType::getAllOrphans(Session& session) { - Wt::Dbo::collection res = session.query>("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 res = session.getDboSession().query>("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(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().where("name = ?").bind(name); + session.checkSharedLocked(); + + return session.getDboSession().find().where("name = ?").bind(name); } ClusterType::pointer -ClusterType::getById(Wt::Dbo::Session& session, IdType id) +ClusterType::getById(Session& session, IdType id) { - return session.find().where("id= ?").bind(id); + session.checkSharedLocked(); + + return session.getDboSession().find().where("id= ?").bind(id); } std::vector -ClusterType::getAll(Wt::Dbo::Session& session) +ClusterType::getAll(Session& session) { - Wt::Dbo::collection res = session.find(); + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().find(); return std::vector(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(name)); + session.checkUniqueLocked(); + + ClusterType::pointer res {session.getDboSession().add(std::make_unique(name))}; + session.getDboSession().flush(); + + return res; } Cluster::pointer diff --git a/src/database/Cluster.hpp b/src/database/Cluster.hpp index 18e55edc..162ff4c6 100644 --- a/src/database/Cluster.hpp +++ b/src/database/Cluster.hpp @@ -33,6 +33,7 @@ namespace Database { class Track; class ClusterType; class ScanSettings; +class Session; class Cluster : public Wt::Dbo::Dbo { @@ -43,12 +44,12 @@ class Cluster : public Wt::Dbo::Dbo Cluster(Wt::Dbo::ptr type, std::string name); // Find utility - static std::vector getAll(Wt::Dbo::Session& session); - static std::vector getAllOrphans(Wt::Dbo::Session& session); - static pointer getById(Wt::Dbo::Session& session, IdType id); + static std::vector getAll(Session& session); + static std::vector getAllOrphans(Session& session); + static pointer getById(Session& session, IdType id); // Create utility - static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr type, std::string name); + static pointer create(Session& session, Wt::Dbo::ptr type, std::string name); // Accessors const std::string& getName() const { return _name; } @@ -89,13 +90,13 @@ class ClusterType : public Wt::Dbo::Dbo ClusterType() {} ClusterType(std::string name); - static std::vector 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 getAll(Wt::Dbo::Session& session); + static std::vector getAllOrphans(Session& session); + static pointer getByName(Session& session, std::string name); + static pointer getById(Session& session, IdType id); + static std::vector 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; } diff --git a/src/database/Database.cpp b/src/database/Database.cpp new file mode 100644 index 00000000..6d87593b --- /dev/null +++ b/src/database/Database.cpp @@ -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 . + */ + +#include "Database.hpp" + +#include +#include + +#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 connection {std::make_unique(dbPath.string())}; + connection->executeSql("pragma journal_mode=WAL"); +// connection->setProperty("show-queries", "true"); + + auto connectionPool = std::make_unique(std::move(connection), 10); + connectionPool->setTimeout(std::chrono::seconds(10)); + + _connectionPool = std::move(connectionPool); + + { + auto session {createSession()}; + session->prepareTables(); + } + +} + +std::unique_ptr +Database::createSession() +{ + return std::unique_ptr{new Session {_sharedMutex, *_connectionPool.get()}}; +} + +} // namespace Database + + diff --git a/src/database/Database.hpp b/src/database/Database.hpp new file mode 100644 index 00000000..9104b507 --- /dev/null +++ b/src/database/Database.hpp @@ -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 . + */ + +#pragma once + +#include + +#include + +#include + +#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 createSession(); + + private: + std::shared_timed_mutex _sharedMutex; + std::unique_ptr _connectionPool; +}; + +} // namespace Database + + diff --git a/src/database/DatabaseHandler.hpp b/src/database/DatabaseHandler.hpp deleted file mode 100644 index e1fa8078..00000000 --- a/src/database/DatabaseHandler.hpp +++ /dev/null @@ -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 . - */ - -#pragma once - -#include -#include - -#include -#include - -#include -#include -#include - -#include "User.hpp" - -namespace Database { - -using UserDatabase = Wt::Auth::Dbo::UserDatabase; - -// 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 getCurrentUser(); // get the current user, may return empty - Wt::Dbo::ptr getUser(const std::string& loginName); - Wt::Dbo::ptr getUser(const Wt::Auth::User& authUser); - Wt::Dbo::ptr 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 createConnectionPool(boost::filesystem::path db); - - private: - - Wt::Dbo::Session _session; - UserDatabase* _users; - Wt::Auth::Login _login; - -}; - -} // namespace Database - - diff --git a/src/database/Release.cpp b/src/database/Release.cpp index f5fd11fc..8922e955 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -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::getByName(Wt::Dbo::Session& session, const std::string& name) +Release::getByName(Session& session, const std::string& name) { - Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); + session.checkUniqueLocked(); + + Wt::Dbo::collection res = session.getDboSession().find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); return std::vector(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().where("mbid = ?").bind(mbid); + session.checkSharedLocked(); + + return session.getDboSession().find().where("mbid = ?").bind(mbid); } Release::pointer -Release::getById(Wt::Dbo::Session& session, IdType id) +Release::getById(Session& session, IdType id) { - return session.find().where("id = ?").bind(id); + session.checkSharedLocked(); + + return session.getDboSession().find().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(name, MBID)); + session.checkSharedLocked(); + + Release::pointer res {session.getDboSession().add(std::make_unique(name, MBID))}; + session.getDboSession().flush(); + + return res; } std::size_t -Release::getCount(Wt::Dbo::Session& session) +Release::getCount(Session& session) { - Wt::Dbo::collection releases {session.find()}; + session.checkSharedLocked(); + + Wt::Dbo::collection releases {session.getDboSession().find()}; return releases.size(); } std::vector -Release::getAll(Wt::Dbo::Session& session, boost::optional offset, boost::optional size) +Release::getAll(Session& session, boost::optional offset, boost::optional size) { - Wt::Dbo::collection res = session.find() + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().find() .offset(offset ? static_cast(*offset) : -1) .limit(size ? static_cast(*size) : -1) .orderBy("name COLLATE NOCASE"); @@ -81,9 +97,11 @@ Release::getAll(Wt::Dbo::Session& session, boost::optional offset, } std::vector -Release::getAllRandom(Wt::Dbo::Session& session, boost::optional size) +Release::getAllRandom(Session& session, boost::optional size) { - Wt::Dbo::collection res = session.find() + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().find() .limit(size ? static_cast(*size) : -1) .orderBy("RANDOM()"); @@ -91,17 +109,21 @@ Release::getAllRandom(Wt::Dbo::Session& session, boost::optional si } std::vector -Release::getAllOrphans(Wt::Dbo::Session& session) +Release::getAllOrphans(Session& session) { - Wt::Dbo::collection res = session.query>("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 res = session.getDboSession().query>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL"); return std::vector(res.begin(), res.end()); } std::vector -Release::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional offset, boost::optional limit) +Release::getLastAdded(Session& session, Wt::WDateTime after, boost::optional offset, boost::optional limit) { - Wt::Dbo::collection res = session.query("SELECT r from release r INNER JOIN track t ON r.id = t.release_id") + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().query("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 -getQuery(Wt::Dbo::Session& session, +getQuery(Session& session, const std::set& clusterIds, - const std::vector keywords) + const std::vector& keywords) { WhereClause where; @@ -144,7 +166,7 @@ getQuery(Wt::Dbo::Session& session, oss << " ORDER BY r.name COLLATE NOCASE"; - Wt::Dbo::Query query = session.query( oss.str() ); + Wt::Dbo::Query query = session.getDboSession().query( oss.str() ); for (const std::string& bindArg : where.getBindArgs()) query.bind(bindArg); @@ -153,16 +175,16 @@ getQuery(Wt::Dbo::Session& session, } std::vector -Release::getByFilter(Wt::Dbo::Session& session, const std::set& clusterIds) +Release::getByFilter(Session& session, const std::set& clusterIds) { bool moreResults; return getByFilter(session, clusterIds, {}, {}, {}, moreResults); } std::vector -Release::getByFilter(Wt::Dbo::Session& session, +Release::getByFilter(Session& session, const std::set& clusterIds, - const std::vector keywords, + const std::vector& keywords, boost::optional offset, boost::optional size, bool& moreResults) diff --git a/src/database/Release.hpp b/src/database/Release.hpp index 2a9d76a9..cd617d12 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -46,19 +46,19 @@ class Release : public Wt::Dbo::Dbo 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 getByName(Wt::Dbo::Session& session, const std::string& name); - static pointer getById(Wt::Dbo::Session& session, IdType id); - static std::vector getAllOrphans(Wt::Dbo::Session& session); // no track related - static std::vector getAll(Wt::Dbo::Session& session, boost::optional offset = {}, boost::optional size = {}); - static std::vector getAllRandom(Wt::Dbo::Session& session, boost::optional size = {}); - static std::vector getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional offset = {}, boost::optional size = {}); + static std::size_t getCount(Session& session); + static pointer getByMBID(Session& session, const std::string& MBID); + static std::vector getByName(Session& session, const std::string& name); + static pointer getById(Session& session, IdType id); + static std::vector getAllOrphans(Session& session); // no track related + static std::vector getAll(Session& session, boost::optional offset = {}, boost::optional size = {}); + static std::vector getAllRandom(Session& session, boost::optional size = {}); + static std::vector getLastAdded(Session& session, Wt::WDateTime after, boost::optional offset = {}, boost::optional size = {}); - static std::vector getByFilter(Wt::Dbo::Session& session, const std::set& clusters); - static std::vector getByFilter(Wt::Dbo::Session& session, + static std::vector getByFilter(Session& session, const std::set& clusters); + static std::vector getByFilter(Session& session, const std::set& clusters, // at least one track that belongs to these clusters - const std::vector keywords, // name must match all of these keywords + const std::vector& keywords, // name must match all of these keywords boost::optional offset, boost::optional size, bool& moreExpected); @@ -72,7 +72,7 @@ class Release : public Wt::Dbo::Dbo std::vector>> getClusterGroups(std::vector> 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 getReleaseYear(bool originalDate = false) const; // 0 if unknown or various diff --git a/src/database/ScanSettings.cpp b/src/database/ScanSettings.cpp index bffdbe1f..7b28ae35 100644 --- a/src/database/ScanSettings.cpp +++ b/src/database/ScanSettings.cpp @@ -25,10 +25,11 @@ #include "utils/Utils.hpp" #include "Cluster.hpp" +#include "Session.hpp" namespace { -std::set defaultClusterTypeNames = +const std::set defaultClusterTypeNames = { "GENRE", "ALBUMGROUPING", @@ -40,31 +41,38 @@ std::set defaultClusterTypeNames = namespace Database { +void +ScanSettings::init(Session& session) +{ + session.checkUniqueLocked(); + + pointer settings {get(session)}; + if (settings) + return; + + settings = session.getDboSession().add(std::make_unique()); + settings.modify()->setClusterTypes(session, defaultClusterTypeNames ); +} ScanSettings::pointer -ScanSettings::get(Wt::Dbo::Session& session) +ScanSettings::get(Session& session) { - pointer settings = session.find(); - if (!settings) - { - settings = session.add(std::make_unique()); - settings.modify()->setClusterTypes(defaultClusterTypeNames); - } + session.checkSharedLocked(); - return settings; + return session.getDboSession().find(); } std::set ScanSettings::getAudioFileExtensions() const { auto extensions = splitString(_audioFileExtensions, " "); - return std::set(extensions.begin(), extensions.end()); + return std::set(std::cbegin(extensions), std::cend(extensions)); } std::vector ScanSettings::getClusterTypes() const { - return std::vector(_clusterTypes.begin(), _clusterTypes.end()); + return std::vector(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& clusterTypeNames) +template +std::set getNames(It begin, It end) { - bool needRescan = false; - assert(session()); + std::set 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& 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& 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& clusterTypeNames) _scanVersion += 1; } - } // namespace Database diff --git a/src/database/ScanSettings.hpp b/src/database/ScanSettings.hpp index e1831521..f9ff709c 100644 --- a/src/database/ScanSettings.hpp +++ b/src/database/ScanSettings.hpp @@ -27,6 +27,7 @@ namespace Database { class ClusterType; +class Session; class ScanSettings : public Wt::Dbo::Dbo { @@ -40,7 +41,9 @@ class ScanSettings : public Wt::Dbo::Dbo 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 void setMediaDirectory(boost::filesystem::path p); void setUpdateStartTime(Wt::WTime t) { _startTime = t; } void setUpdatePeriod(UpdatePeriod p) { _updatePeriod = p; } - void setClusterTypes(const std::set& clusterTypeNames); + void setClusterTypes(Session& session, const std::set& clusterTypeNames); void setAudioFileExtensions(std::set fileExtensions); template diff --git a/src/database/DatabaseHandler.cpp b/src/database/Session.cpp similarity index 62% rename from src/database/DatabaseHandler.cpp rename to src/database/Session.cpp index b1c5a276..3c18507f 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/Session.cpp @@ -17,10 +17,7 @@ * along with LMS. If not, see . */ -#include "DatabaseHandler.hpp" - -#include -#include +#include "Session.hpp" #include #include @@ -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; - static VersionInfo::pointer get(Wt::Dbo::Session& session) + static VersionInfo::pointer getOrCreate(Session& session) { - pointer versionInfo {session.find()}; + session.checkUniqueLocked(); + + pointer versionInfo {session.getDboSession().find()}; if (!versionInfo) - versionInfo = session.add(std::make_unique()); + return session.getDboSession().add(std::make_unique()); return versionInfo; } + static VersionInfo::pointer get(Session& session) + { + session.checkSharedLocked(); + + return session.getDboSession().find(); + } + Version getVersion() const { return _version; } void setVersion(Version version) { _version = static_cast(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("auth_token"); _session.mapClass("user"); - try { - Wt::Dbo::Transaction transaction {_session}; + _users = std::make_unique(_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 +Session::createUniqueTransaction() +{ + return std::unique_ptr(new UniqueTransaction{_mutex, _session}); +} + +std::unique_ptr +Session::createSharedTransaction() +{ + return std::unique_ptr(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) +{ + 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, 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) +{ + 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 = _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()); - Wt::Dbo::ptr authInfo = _users->find(authUser); - authInfo.modify()->setUser(user); - - return user; -} - -std::unique_ptr -Handler::createConnectionPool(boost::filesystem::path p) -{ - LMS_LOG(DB, INFO) << "Creating connection pool on file " << p.string(); - - auto connection = std::make_unique(p.string()); - connection->executeSql("pragma journal_mode=WAL"); -// connection->setProperty("show-queries", "true"); - - auto pool = std::make_unique(std::move(connection), 1); - pool->setTimeout(std::chrono::seconds(10)); - - return pool; -} - - } // namespace Database diff --git a/src/database/Session.hpp b/src/database/Session.hpp new file mode 100644 index 00000000..fe144d0a --- /dev/null +++ b/src/database/Session.hpp @@ -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 . + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "User.hpp" + +namespace Database { + +using AuthInfo = Wt::Auth::Dbo::AuthInfo; +using UserDatabase = Wt::Auth::Dbo::UserDatabase; + +class UniqueTransaction +{ + public: + ~UniqueTransaction(); + + private: + friend class Session; + UniqueTransaction(std::shared_timed_mutex& mutex, Wt::Dbo::Session& session); + + std::unique_lock _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 _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 createUniqueTransaction(); + std::unique_ptr createSharedTransaction(); + + void checkUniqueLocked(); + void checkSharedLocked(); + + void optimize(); + + // User management + Wt::Dbo::ptr getLoggedUser(); // get the current user, may return empty + Wt::Dbo::ptr getUser(const std::string& loginName); + std::string getUserLoginName(Wt::Dbo::ptr user); + Wt::Dbo::ptr createUser(const std::string& loginName, const std::string& password); + void removeUser(Wt::Dbo::ptr user); + bool checkUserPassword(const std::string& loginName, const std::string& password); + void updateUserPassword(Wt::Dbo::ptr user, const std::string& password); + Wt::WDateTime getUserLastLoginAttempt(Wt::Dbo::ptr 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 getUser(const Wt::Auth::User& authUser); + + std::shared_timed_mutex& _mutex; + Wt::Dbo::Session _session; + std::unique_ptr _users; + Wt::Auth::Login _login; + +}; + +} // namespace Database + + diff --git a/src/database/SimilaritySettings.cpp b/src/database/SimilaritySettings.cpp index 7c2000fc..27418e76 100644 --- a/src/database/SimilaritySettings.cpp +++ b/src/database/SimilaritySettings.cpp @@ -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 defaultFeatures = +static const std::vector 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 settings, const std::string& name, std::size_t nbDimensions, double weight) +SimilaritySettingsFeature::create(Session& session, Wt::Dbo::ptr settings, const std::string& name, std::size_t nbDimensions, double weight) { - return session.add(std::make_unique(settings, name, nbDimensions, weight)); + session.checkUniqueLocked(); + + SimilaritySettingsFeature::pointer res {session.getDboSession().add(std::make_unique(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(); - if (!settings) - { - settings = session.add(std::make_unique()); + session.checkUniqueLocked(); - for (const auto& feature : defaultFeatures) - SimilaritySettingsFeature::create(session, settings, feature.name, feature.nbDimensions, feature.weight); - } + pointer settings {session.getDboSession().find()}; + if (settings) + return; - return settings; + settings = session.getDboSession().add(std::make_unique()); + 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(); } std::vector> diff --git a/src/database/SimilaritySettings.hpp b/src/database/SimilaritySettings.hpp index f7ee9056..beda6e5f 100644 --- a/src/database/SimilaritySettings.hpp +++ b/src/database/SimilaritySettings.hpp @@ -23,6 +23,7 @@ namespace Database { +class Session; class SimilaritySettings; class SimilaritySettingsFeature : public Wt::Dbo::Dbo @@ -33,7 +34,7 @@ class SimilaritySettingsFeature : public Wt::Dbo::Dbo settings, const std::string& name, std::size_t nbDimensions, double weight); - static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr settings, const std::string& name, std::size_t nbDimensions, double weight = 1); + static pointer create(Session& session, Wt::Dbo::ptr 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(_nbDimensions); } @@ -70,7 +71,8 @@ class SimilaritySettings : public Wt::Dbo::Dbo using pointer = Wt::Dbo::ptr; // 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; } diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 3a5897e6..52140c58 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -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::getAll(Wt::Dbo::Session& session, boost::optional limit) +Track::getAll(Session& session, boost::optional limit) { - Wt::Dbo::collection res {session.find() + session.checkSharedLocked(); + + Wt::Dbo::collection res {session.getDboSession().find() .limit(limit ? static_cast(*limit) : -1)}; return std::vector(std::cbegin(res), std::cend(res)); } std::vector -Track::getAllRandom(Wt::Dbo::Session& session, boost::optional limit) +Track::getAllRandom(Session& session, boost::optional limit) { - Wt::Dbo::collection res {session.find() + session.checkSharedLocked(); + + Wt::Dbo::collection res {session.getDboSession().find() .limit(limit ? static_cast(*limit) : -1) .orderBy("RANDOM()")}; @@ -57,67 +62,88 @@ Track::getAllRandom(Wt::Dbo::Session& session, boost::optional limi } std::vector -Track::getAllIds(Wt::Dbo::Session& session) +Track::getAllIds(Session& session) { - Wt::Dbo::Transaction transaction(session); - Wt::Dbo::collection res = session.query("SELECT id from track"); + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().query("SELECT id FROM track"); return std::vector(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().where("file_path = ?").bind(p.string()); + session.checkSharedLocked(); + + return session.getDboSession().find().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().where("id = ?").bind(id); + session.checkSharedLocked(); + + return session.getDboSession().find() + .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().where("mbid = ?").bind(mbid); + session.checkSharedLocked(); + + return session.getDboSession().find() + .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(p)); + session.checkUniqueLocked(); + + Track::pointer res {session.getDboSession().add(std::make_unique(p))}; + session.getDboSession().flush(); + + return res; } std::vector -Track::getAllPaths(Wt::Dbo::Session& session) +Track::getAllPaths(Session& session) { - Wt::Dbo::Transaction transaction(session); - Wt::Dbo::collection res = session.query("SELECT file_path from track"); + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().query("SELECT file_path FROM track"); return std::vector(res.begin(), res.end()); } std::vector -Track::getMBIDDuplicates(Wt::Dbo::Session& session) +Track::getMBIDDuplicates(Session& session) { - Wt::Dbo::collection res = session.query( "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 res = session.getDboSession().query( "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(res.begin(), res.end()); } std::vector -Track::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int limit) +Track::getLastAdded(Session& session, const Wt::WDateTime& after, boost::optional limit) { - Wt::Dbo::collection res = session.find() + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().find() .where("file_added > ?").bind(after) .orderBy("file_added DESC") - .limit(limit); + .limit(limit ? static_cast(*limit) : -1); return std::vector(res.begin(), res.end()); } std::vector -Track::getAllWithMBIDAndMissingFeatures(Wt::Dbo::Session& session) +Track::getAllWithMBIDAndMissingFeatures(Session& session) { - Wt::Dbo::collection res = session.query + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().query ("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 -Track::getAllIdsWithFeatures(Wt::Dbo::Session& session, boost::optional limit) +Track::getAllIdsWithFeatures(Session& session, boost::optional limit) { - int size {limit ? static_cast(*limit) : -1}; + session.checkSharedLocked(); - Wt::Dbo::collection res = session.query + Wt::Dbo::collection res = session.getDboSession().query ("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(*limit) : -1); return std::vector(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& clusterIds, - const std::vector keywords) + const std::vector& 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 query = session.query( oss.str() ); + Wt::Dbo::Query query = session.getDboSession().query( oss.str() ); for (const std::string& bindArg : where.getBindArgs()) query.bind(bindArg); @@ -193,13 +221,15 @@ getQuery(Wt::Dbo::Session& session, } std::vector -Track::getByFilter(Wt::Dbo::Session& session, +Track::getByFilter(Session& session, const std::set& clusterIds, - const std::vector keywords, + const std::vector& keywords, boost::optional offset, boost::optional size, bool& moreResults) { + session.checkSharedLocked(); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords) .limit(size ? static_cast(*size) + 1 : -1) .offset(offset ? static_cast(*offset) : -1); @@ -218,16 +248,18 @@ Track::getByFilter(Wt::Dbo::Session& session, } std::vector -Track::getByFilter(Wt::Dbo::Session& session, +Track::getByFilter(Session& session, const std::set& clusters) { + session.checkSharedLocked(); + bool moreResults; return getByFilter(session, clusters, - std::vector {}, - boost::optional {}, - boost::optional {}, + {}, + {}, + {}, moreResults); } diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 92a00216..52f5e410 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -53,29 +53,29 @@ class Track : public Wt::Dbo::Dbo 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 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 getByFilter(Session& session, const std::set& clusters); // tracks that belong to these clusters - static std::vector getByFilter(Wt::Dbo::Session& session, - const std::set& clusters, // tracks that belong to these clusters - const std::vector keywords, // name must match all of these keywords + static std::vector getByFilter(Session& session, + const std::set& clusters, // tracks that belong to these clusters + const std::vector& keywords, // name must match all of these keywords boost::optional offset, boost::optional size, bool& moreExpected); - static std::vector getAll(Wt::Dbo::Session& session, boost::optional limit = {}); - static std::vector getAllRandom(Wt::Dbo::Session& session, boost::optional limit = {}); - static std::vector getAllIds(Wt::Dbo::Session& session); // nested transaction - static std::vector getAllPaths(Wt::Dbo::Session& session); // nested transaction - static std::vector getMBIDDuplicates(Wt::Dbo::Session& session); - static std::vector getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int size = 1); - static std::vector getAllWithMBIDAndMissingFeatures(Wt::Dbo::Session& session); // nested transaction - static std::vector getAllIdsWithFeatures(Wt::Dbo::Session& session, boost::optional limit = {}); // nested transaction + static std::vector getAll(Session& session, boost::optional limit = {}); + static std::vector getAllRandom(Session& session, boost::optional limit = {}); + static std::vector getAllIds(Session& session); // nested transaction + static std::vector getAllPaths(Session& session); // nested transaction + static std::vector getMBIDDuplicates(Session& session); + static std::vector getLastAdded(Session& session, const Wt::WDateTime& after, boost::optional size = 1); + static std::vector getAllWithMBIDAndMissingFeatures(Session& session); + static std::vector getAllIdsWithFeatures(Session& session, boost::optional 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; } diff --git a/src/database/TrackArtistLink.cpp b/src/database/TrackArtistLink.cpp index 9ca3c26a..64ce9281 100644 --- a/src/database/TrackArtistLink.cpp +++ b/src/database/TrackArtistLink.cpp @@ -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, Wt::Dbo::ptr artist,Type type) +TrackArtistLink::create(Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr artist,Type type) { - return session.add(std::make_unique(track, artist, type)); + session.checkUniqueLocked(); + + TrackArtistLink::pointer res {session.getDboSession().add(std::make_unique(track, artist, type))}; + session.getDboSession().flush(); + + return res; } } diff --git a/src/database/TrackArtistLink.hpp b/src/database/TrackArtistLink.hpp index 65143f63..7601d0c9 100644 --- a/src/database/TrackArtistLink.hpp +++ b/src/database/TrackArtistLink.hpp @@ -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, Wt::Dbo::ptr artist, Type type); - static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr artist,Type type); + static pointer create(Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr artist,Type type); Wt::Dbo::ptr getTrack() const { return _track; } Wt::Dbo::ptr getArtist() const { return _artist; } diff --git a/src/database/TrackFeatures.cpp b/src/database/TrackFeatures.cpp index 5e7e94eb..7cdc6be9 100644 --- a/src/database/TrackFeatures.cpp +++ b/src/database/TrackFeatures.cpp @@ -23,6 +23,7 @@ #include #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, const std::string& jsonEncodedFeatures) +TrackFeatures::create(Session& session, Wt::Dbo::ptr track, const std::string& jsonEncodedFeatures) { - return session.add(std::make_unique(track, jsonEncodedFeatures)); + session.checkUniqueLocked(); + return session.getDboSession().add(std::make_unique(track, jsonEncodedFeatures)); } std::vector diff --git a/src/database/TrackFeatures.hpp b/src/database/TrackFeatures.hpp index cd884d26..69e5f185 100644 --- a/src/database/TrackFeatures.hpp +++ b/src/database/TrackFeatures.hpp @@ -27,6 +27,7 @@ namespace Database { +class Session; class Track; class TrackFeatures : public Wt::Dbo::Dbo @@ -39,7 +40,7 @@ class TrackFeatures : public Wt::Dbo::Dbo TrackFeatures(Wt::Dbo::ptr track, const std::string& jsonEncodedFeatures); // Create utility - static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, const std::string& jsonEncodedFeatures); + static pointer create(Session& session, Wt::Dbo::ptr track, const std::string& jsonEncodedFeatures); std::vector getFeatures(const std::string& featureNode) const; bool getFeatures(std::map /*values*/>& featureNodes) const; diff --git a/src/database/TrackList.cpp b/src/database/TrackList.cpp index ca72b864..32732687 100644 --- a/src/database/TrackList.cpp +++ b/src/database/TrackList.cpp @@ -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) +TrackList::create(Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr user) { + session.checkUniqueLocked(); assert(user); - auto res = session.add( std::make_unique(name, type, isPublic, user) ); - session.flush(); + auto res = session.getDboSession().add( std::make_unique(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) +TrackList::get(Session& session, const std::string& name, Type type, Wt::Dbo::ptr user) { - return session.find() + session.checkSharedLocked(); + assert(user); + + return session.getDboSession().find() .where("name = ?").bind(name) .where("type = ?").bind(type) .where("user_id = ?").bind(user.id()); } std::vector -TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr user) +TrackList::getAll(Session& session, Wt::Dbo::ptr user) { - Wt::Dbo::collection res = session.find() + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().find() .where("user_id = ?").bind(user.id()) .orderBy("name COLLATE NOCASE"); @@ -80,9 +78,11 @@ TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr user) } std::vector -TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr user, Type type) +TrackList::getAll(Session& session, Wt::Dbo::ptr user, Type type) { - Wt::Dbo::collection res = session.find() + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().find() .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, Type type) } TrackList::pointer -TrackList::getById(Wt::Dbo::Session& session, IdType id) +TrackList::getById(Session& session, IdType id) { - return session.find().where("id = ?").bind(id); + session.checkSharedLocked(); + + return session.getDboSession().find().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(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 TrackList::getTopArtists(std::size_t limit) const { @@ -269,26 +254,25 @@ TrackListEntry::TrackListEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr track, Wt::Dbo::ptr tracklist) +TrackListEntry::create(Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr tracklist) { + session.checkUniqueLocked(); assert(track); assert(tracklist); - auto res = session.add( std::make_unique( track, tracklist) ); - session.flush(); + auto res = session.getDboSession().add( std::make_unique( 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().where("id = ?").bind(id); + session.checkSharedLocked(); + + return session.getDboSession().find().where("id = ?").bind(id); } } // namespace Database diff --git a/src/database/TrackList.hpp b/src/database/TrackList.hpp index c1208610..b21239e6 100644 --- a/src/database/TrackList.hpp +++ b/src/database/TrackList.hpp @@ -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 { @@ -56,13 +57,13 @@ class TrackList : public Wt::Dbo::Dbo std::vector> 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); - static pointer getById(Wt::Dbo::Session& session, IdType tracklistId); - static std::vector getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr user); - static std::vector getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr user, Type type); + static pointer get(Session& session, const std::string& name, Type type, Wt::Dbo::ptr user); + static pointer getById(Session& session, IdType tracklistId); + static std::vector getAll(Session& session, Wt::Dbo::ptr user); + static std::vector getAll(Session& session, Wt::Dbo::ptr user, Type type); // Create utility - static pointer create(Wt::Dbo::Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr user); + static pointer create(Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr user); // Accessors std::string getName() const { return _name; } @@ -73,9 +74,7 @@ class TrackList : public Wt::Dbo::Dbo // Modifiers void setName(const std::string& name) { _name = name; } void setIsPublic(bool isPublic) { _isPublic = isPublic; } - Wt::Dbo::ptr 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 using pointer = Wt::Dbo::ptr; - TrackListEntry(); + TrackListEntry() = default; TrackListEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr 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, Wt::Dbo::ptr tracklist); + static pointer create(Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr tracklist); // Accessors Wt::Dbo::ptr getTrack() const { return _track; } diff --git a/src/database/Types.hpp b/src/database/Types.hpp index 2ff23abd..704e6897 100644 --- a/src/database/Types.hpp +++ b/src/database/Types.hpp @@ -19,7 +19,7 @@ #pragma once -#include +#include namespace Database { using IdType = Wt::Dbo::dbo_default_traits::IdType; diff --git a/src/database/User.cpp b/src/database/User.cpp index 7911df91..9f5aa28b 100644 --- a/src/database/User.cpp +++ b/src/database/User.cpp @@ -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 User::audioTranscodeAllowedBitrates = { @@ -37,35 +41,48 @@ User::audioTranscodeAllowedBitrates = }; User::User() -: _maxAudioTranscodeBitrate{static_cast(*audioTranscodeAllowedBitrates.rbegin())} +: _maxAudioTranscodeBitrate {static_cast(*audioTranscodeAllowedBitrates.rbegin())} { } std::vector -User::getAll(Wt::Dbo::Session& session) +User::getAll(Session& session) { - Wt::Dbo::collection res = session.find(); + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().find(); return std::vector(res.begin(), res.end()); } User::pointer -User::getDemo(Wt::Dbo::Session& session) +User::getDemo(Session& session) { - pointer res = session.find().where("type = ?").bind(Type::DEMO); + session.checkSharedLocked(); + + pointer res = session.getDboSession().find().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()); + session.checkUniqueLocked(); + + User::pointer user {session.getDboSession().add(std::make_unique())}; + + 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().where("id = ?").bind( id ); + return session.getDboSession().find().where("id = ?").bind( id ); } void @@ -95,35 +112,21 @@ User::getMaxAudioTranscodeBitrate(void) const } Wt::Dbo::ptr -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 -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 diff --git a/src/database/User.hpp b/src/database/User.hpp index a9690162..26121296 100644 --- a/src/database/User.hpp +++ b/src/database/User.hpp @@ -22,17 +22,15 @@ #include #include -#include #include "Types.hpp" namespace Database { -class User; -using AuthInfo = Wt::Auth::Dbo::AuthInfo; class Artist; class Release; +class Session; class TrackList; class Track; @@ -69,12 +67,13 @@ class User : public Wt::Dbo::Dbo 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 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 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 bool isRepeatAllSet() const { return _repeatAll; } bool isRadioSet() const { return _radio; } - Wt::Dbo::ptr getQueuedTrackList() const; - Wt::Dbo::ptr getPlayedTrackList() const; + Wt::Dbo::ptr getPlayedTrackList(Session& session) const; + Wt::Dbo::ptr getQueuedTrackList(Session& session) const; void starArtist(Wt::Dbo::ptr artist); void unstarArtist(Wt::Dbo::ptr artist); diff --git a/src/main/main.cpp b/src/main/main.cpp index 972fe3c3..1e3ab455 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -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::create(*connectionPool)}; + Scanner::MediaScanner& mediaScanner {ServiceProvider::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::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..."; diff --git a/src/scanner/MediaScanner.cpp b/src/scanner/MediaScanner.cpp index 6e4746b0..3f141517 100644 --- a/src/scanner/MediaScanner.cpp +++ b/src/scanner/MediaScanner.cpp @@ -85,7 +85,7 @@ isPathInParentPath(const boost::filesystem::path& path, const boost::filesystem: } std::vector -getOrCreateArtists(Wt::Dbo::Session& session, const std::vector& artistsInfo) +getOrCreateArtists(Session& session, const std::vector& artistsInfo) { std::vector artists; @@ -129,7 +129,7 @@ getOrCreateArtists(Wt::Dbo::Session& session, const std::vector -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 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 clusterTypeNames; + auto clusterTypes = scanSettings->getClusterTypes(); + std::set 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::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::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 clusters {getOrCreateClusters(_db.getSession(), trackInfo->clusters)}; + std::vector clusters {getOrCreateClusters(*_dbSession, trackInfo->clusters)}; // ***** Artists - std::vector artists {getOrCreateArtists(_db.getSession(), trackInfo->artists)}; + std::vector artists {getOrCreateArtists(*_dbSession, trackInfo->artists)}; // ***** Release artists - std::vector releaseArtists {getOrCreateArtists(_db.getSession(), trackInfo->albumArtists)}; + std::vector 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 trackPaths = Track::getAllPaths(_db.getSession());; + std::vector 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 tracks = Database::Track::getMBIDDuplicates(_db.getSession()); - for (Track::pointer track : tracks) + const std::vector 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++; diff --git a/src/scanner/MediaScanner.hpp b/src/scanner/MediaScanner.hpp index 9a732c86..ab0b70dc 100644 --- a/src/scanner/MediaScanner.hpp +++ b/src/scanner/MediaScanner.hpp @@ -29,7 +29,7 @@ #include #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 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 _sigScanInProgress; std::chrono::system_clock::time_point _lastScanInProgressEmit {}; Wt::Signal _sigScheduled; - Database::Handler _db; + std::unique_ptr _dbSession; MetaData::TagLibParser _metadataParser; std::vector _addons; diff --git a/src/scanner/MediaScannerAddon.hpp b/src/scanner/MediaScannerAddon.hpp index 325aba07..fdfd9577 100644 --- a/src/scanner/MediaScannerAddon.hpp +++ b/src/scanner/MediaScannerAddon.hpp @@ -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 diff --git a/src/similarity/SimilaritySearcher.cpp b/src/similarity/SimilaritySearcher.cpp index f7d3d920..e34abb22 100644 --- a/src/similarity/SimilaritySearcher.cpp +++ b/src/similarity/SimilaritySearcher.cpp @@ -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 -Searcher::getSimilarTracks(Wt::Dbo::Session& session, const std::set& trackIds, std::size_t maxCount) +Searcher::getSimilarTracks(Database::Session& dbSession, const std::set& 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::setgetSimilarTracks(trackIds, maxCount); } else - return ClusterSearcher::getSimilarTracks(session, trackIds, maxCount); + return ClusterSearcher::getSimilarTracks(dbSession, trackIds, maxCount); } std::vector -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 -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 diff --git a/src/similarity/SimilaritySearcher.hpp b/src/similarity/SimilaritySearcher.hpp index 80cf05e2..56d942ca 100644 --- a/src/similarity/SimilaritySearcher.hpp +++ b/src/similarity/SimilaritySearcher.hpp @@ -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 getSimilarTracks(Wt::Dbo::Session& session, const std::set& tracksId, std::size_t maxCount); - std::vector getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t maxCount); - std::vector getSimilarArtists(Wt::Dbo::Session& session, Database::IdType artistId, std::size_t maxCount); + std::vector getSimilarTracks(Database::Session& session, const std::set& tracksId, std::size_t maxCount); + std::vector getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount); + std::vector getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount); private: diff --git a/src/similarity/cluster/SimilarityClusterSearcher.cpp b/src/similarity/cluster/SimilarityClusterSearcher.cpp index 58f0e6f5..40eee04c 100644 --- a/src/similarity/cluster/SimilarityClusterSearcher.cpp +++ b/src/similarity/cluster/SimilarityClusterSearcher.cpp @@ -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 -getSimilarTracks(Wt::Dbo::Session& session, const std::set& trackIds, std::size_t maxCount) +getSimilarTracksLocked(Database::Session& dbSession, const std::set& trackIds, std::size_t maxCount) { - std::vector res; - - Wt::Dbo::Transaction transaction(session); - std::vector 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& tr std::vector sortedClusterIds; uniqueAndSortedByOccurence(clusterIds.begin(), clusterIds.end(), std::back_inserter(sortedClusterIds)); + std::vector 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& tr } std::vector -getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t maxCount) +getSimilarTracks(Database::Session& dbSession, const std::set& trackIds, std::size_t maxCount) +{ + auto transaction {dbSession.createSharedTransaction()}; + + return getSimilarTracksLocked(dbSession, trackIds, maxCount); +} + +std::vector +getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount) { std::vector 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 -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 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 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; diff --git a/src/similarity/cluster/SimilarityClusterSearcher.hpp b/src/similarity/cluster/SimilarityClusterSearcher.hpp index b1fcb05d..176fc362 100644 --- a/src/similarity/cluster/SimilarityClusterSearcher.hpp +++ b/src/similarity/cluster/SimilarityClusterSearcher.hpp @@ -23,13 +23,17 @@ #include "database/Types.hpp" +namespace Database { + class Session; +} + namespace Similarity { namespace ClusterSearcher { - std::vector getSimilarTracks(Wt::Dbo::Session& session, const std::set& tracksId, std::size_t maxCount); - std::vector getSimilarReleases(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t maxCount); - std::vector getSimilarArtists(Wt::Dbo::Session& session, Database::IdType artistId, std::size_t maxCount); + std::vector getSimilarTracks(Database::Session& session, const std::set& tracksId, std::size_t maxCount); + std::vector getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount); + std::vector getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount); }; } // namespace Similarity diff --git a/src/similarity/features/SimilarityFeaturesScannerAddon.cpp b/src/similarity/features/SimilarityFeaturesScannerAddon.cpp index 2a51e543..713906c0 100644 --- a/src/similarity/features/SimilarityFeaturesScannerAddon.cpp +++ b/src/similarity/features/SimilarityFeaturesScannerAddon.cpp @@ -38,13 +38,13 @@ struct TrackInfo }; std::vector -getTracksWithMBIDAndMissingFeatures(Wt::Dbo::Session& session) +getTracksWithMBIDAndMissingFeatures(Database::Session& dbSession) { std::vector 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 dbSession) +: _dbSession {std::move(dbSession)} { boost::optional cache {Similarity::FeaturesCache::read()}; if (cache) { - auto searcher {std::make_shared(_db.getSession(), *cache, [&]() { return _stopRequested; })}; + auto searcher {std::make_shared(*_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 tracksInfo {getTracksWithMBIDAndMissingFeatures(_db.getSession())}; + const std::vector 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 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(_db.getSession(), [&]() { return _stopRequested; })}; + auto searcher {std::make_shared(*_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 track {Database::Track::getById(_db.getSession(), trackId)}; + Wt::Dbo::ptr 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; } diff --git a/src/similarity/features/SimilarityFeaturesScannerAddon.hpp b/src/similarity/features/SimilarityFeaturesScannerAddon.hpp index ff59125d..93c2a0a2 100644 --- a/src/similarity/features/SimilarityFeaturesScannerAddon.hpp +++ b/src/similarity/features/SimilarityFeaturesScannerAddon.hpp @@ -19,9 +19,7 @@ #pragma once -#include - -#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 dbSession); std::shared_ptr 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 _dbSession; std::shared_ptr _searcher; - bool _stopRequested{false}; + bool _stopRequested {}; }; FeaturesScannerAddon* setFeaturesScannerAddon(FeaturesScannerAddon addon); diff --git a/src/similarity/features/SimilarityFeaturesSearcher.cpp b/src/similarity/features/SimilarityFeaturesSearcher.cpp index 941e58f7..fbd200d4 100644 --- a/src/similarity/features/SimilarityFeaturesSearcher.cpp +++ b/src/similarity/features/SimilarityFeaturesSearcher.cpp @@ -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; 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 -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 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 stopRequested) +FeaturesSearcher::FeaturesSearcher(Database::Session& session, std::function stopRequested) { LMS_LOG(SIMILARITY, INFO) << "Constructing features searcher..."; - Wt::Dbo::Transaction transaction {session}; + std::size_t nbDimensions; + FeatureInfoMap featuresInfo; + std::vector 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 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 samples; std::vector samplesTrackIds; @@ -209,7 +215,7 @@ FeaturesSearcher::FeaturesSearcher(Wt::Dbo::Session& session, std::function stopRequested) +FeaturesSearcher::FeaturesSearcher(Database::Session& session, FeaturesCache cache, std::function 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> 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& 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; diff --git a/src/similarity/features/SimilarityFeaturesSearcher.hpp b/src/similarity/features/SimilarityFeaturesSearcher.hpp index 9325ec20..6ed68273 100644 --- a/src/similarity/features/SimilarityFeaturesSearcher.hpp +++ b/src/similarity/features/SimilarityFeaturesSearcher.hpp @@ -22,24 +22,27 @@ #include #include -#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 stopRequested); + FeaturesSearcher(Database::Session& session, FeaturesCache cache, std::function stopRequested); // Use training (may be very slow) - FeaturesSearcher(Wt::Dbo::Session& session, std::function stopRequested); + FeaturesSearcher(Database::Session& session, std::function stopRequested); bool isValid() const; @@ -51,7 +54,7 @@ class FeaturesSearcher std::vector getSimilarReleases(Database::IdType releaseId, std::size_t maxCount) const; std::vector 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>; - void init(Wt::Dbo::Session& session, + void init(Database::Session& session, SOM::Network network, ObjectPositions tracksPosition, std::function stopRequested); diff --git a/src/ui/Auth.cpp b/src/ui/Auth.cpp index c3f4d4b5..b3ea96a2 100644 --- a/src/ui/Auth.cpp +++ b/src/ui/Auth.cpp @@ -34,21 +34,21 @@ namespace UserInterface { Auth::Auth() : Wt::WTemplateFormView(Wt::WString::tr("Lms.Auth.template")) { - _model = std::make_shared(LmsApp->getDb().getAuthService(), LmsApp->getDb().getUserDatabase()); - _model->addPasswordAuth(&Database::Handler::getPasswordService()); + _model = std::make_shared(LmsApp->getDbSession().getAuthService(), LmsApp->getDbSession().getUserDatabase()); + _model->addPasswordAuth(&Database::Session::getPasswordService()); // LoginName setFormWidget(Wt::Auth::AuthModel::LoginNameField, std::make_unique()); { - 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("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 diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 87c40549..381a2a9b 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -55,9 +55,9 @@ namespace UserInterface { std::unique_ptr -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(env, connectionPool, appGroups); + return std::make_unique(env, db.createSession(), appGroups); } LmsApplication* @@ -67,11 +67,11 @@ LmsApplication::instance() } LmsApplication::LmsApplication(const Wt::WEnvironment& env, - Wt::Dbo::SqlConnectionPool& connectionPool, + std::unique_ptr dbSession, LmsApplicationGroupContainer& appGroups) -: Wt::WApplication(env), - _db(connectionPool), - _appGroups(appGroups) +: Wt::WApplication {env}, + _dbSession {std::move(dbSession)}, + _appGroups {appGroups} { auto bootstrapTheme = std::make_unique(); 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(); } } @@ -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::WString::tr("Lms.template"))); + Wt::WTemplate* main {root()->addWidget(std::make_unique(Wt::WString::tr("Lms.template")))}; // Navbar Wt::WNavigationBar* navbar = main->bindNew("navbar-top"); navbar->setTitle("LMS", Wt::WLink(Wt::LinkType::InternalPath, "/artists")); navbar->setResponsive(true); - Wt::WMenu* menu = navbar->addMenu(std::make_unique()); + Wt::WMenu* menu {navbar->addMenu(std::make_unique())}; { 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(); } - explore->tracksAdd.connect([=] (std::vector tracks) + explore->tracksAdd.connect([=] (const std::vector& trackIds) { - playqueue->addTracks(tracks); + playqueue->addTracks(trackIds); }); - explore->tracksPlay.connect([=] (std::vector tracks) + explore->tracksPlay.connect([=] (const std::vector& 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()->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 func) { - Wt::WServer::instance()->post(LmsApp->sessionId(), func); + Wt::WServer::instance()->post(LmsApp->sessionId(), std::move(func)); } static std::string escape(std::string str) diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index 6d75158e..e4690fae 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -23,9 +23,8 @@ #include #include -#include -#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 dbSession, LmsApplicationGroupContainer& appGroups); - static std::unique_ptr create(const Wt::WEnvironment& env, - Wt::Dbo::SqlConnectionPool& connectionPool, LmsApplicationGroupContainer& appGroups); + static std::unique_ptr create(const Wt::WEnvironment& env, Database::Database& db, LmsApplicationGroupContainer& appGroups); static LmsApplication* instance(); // Session application data std::shared_ptr getImageResource() { return _imageResource; } std::shared_ptr 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 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; - std::shared_ptr _audioResource; - bool _isAdmin = false; + Wt::Signal<> _preQuit; + std::unique_ptr _dbSession; + LmsApplicationGroupContainer& _appGroups; + Events _events; + Wt::WString _userIdentity; + Auth* _auth {}; + std::shared_ptr _imageResource; + std::shared_ptr _audioResource; + bool _isAdmin {}; }; diff --git a/src/ui/MediaPlayer.cpp b/src/ui/MediaPlayer.cpp index da896817..ae4c9595 100644 --- a/src/ui/MediaPlayer.cpp +++ b/src/ui/MediaPlayer.cpp @@ -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); diff --git a/src/ui/PlayHistoryView.cpp b/src/ui/PlayHistoryView.cpp index 20f92761..2df44771 100644 --- a/src/ui/PlayHistoryView.cpp +++ b/src/ui/PlayHistoryView.cpp @@ -70,7 +70,7 @@ std::unique_ptr 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(_entriesContainer->count()) >= trackList->getCount()); diff --git a/src/ui/PlayHistoryView.hpp b/src/ui/PlayHistoryView.hpp index 36cf73c2..d29503b5 100644 --- a/src/ui/PlayHistoryView.hpp +++ b/src/ui/PlayHistoryView.hpp @@ -23,8 +23,6 @@ #include #include -#include "database/Types.hpp" - namespace UserInterface { class PlayHistory : public Wt::WTemplate diff --git a/src/ui/PlayQueueView.cpp b/src/ui/PlayQueueView.cpp index 95cd986b..be06290d 100644 --- a/src/ui/PlayQueueView.cpp +++ b/src/ui/PlayQueueView.cpp @@ -19,12 +19,15 @@ #include "PlayQueueView.hpp" +#include #include +#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(getTrackList()->getCount()))); } @@ -274,55 +281,58 @@ PlayQueue::updateCurrentTrack(bool selected) } void -PlayQueue::enqueueTracks(const std::vector& tracks) +PlayQueue::enqueueTracks(const std::vector& 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(1, track)); + enqueueTracks({trackId}); } void -PlayQueue::addTracks(const std::vector& tracks) +PlayQueue::addTracks(const std::vector& 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& tracks) +PlayQueue::playTracks(const std::vector& 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(pos)) (*_trackPos)--; } @@ -391,28 +401,23 @@ PlayQueue::addSome() } void -PlayQueue::addRadioTrack() +PlayQueue::enqueueRadioTrack() { - auto tracklist = getTrackList(); + std::vector trackIds; + + { + auto transaction {LmsApp->getDbSession().createSharedTransaction()}; + Database::TrackList::pointer tracklist {getTrackList()}; + + trackIds = getTrackList()->getTrackIds(); + } - std::vector trackIds = getTrackList()->getTrackIds(); if (trackIds.empty()) return; - auto res = getService()->getSimilarTracks(LmsApp->getDboSession(), std::set(trackIds.begin(), trackIds.end()), 1); - for (auto trackId : res) - { - auto trackToAdd = Database::Track::getById(LmsApp->getDboSession(), trackId); - enqueueTrack(trackToAdd); - } - + const std::vector trackToAddIds {getService()->getSimilarTracks(LmsApp->getDbSession(), std::set(std::cbegin(trackIds), std::cend(trackIds)), 1)}; + enqueueTracks(trackToAddIds); } -void addRadioTrackFromSimilarity(std::shared_ptr similaritySearcher) -{ - -} - - } // namespace UserInterface diff --git a/src/ui/PlayQueueView.hpp b/src/ui/PlayQueueView.hpp index 56e3b2fd..e8d0cd0b 100644 --- a/src/ui/PlayQueueView.hpp +++ b/src/ui/PlayQueueView.hpp @@ -19,23 +19,23 @@ #pragma once -#include #include #include -#include #include #include #include -#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& tracks); - void playTracks(const std::vector& tracks); + void addTracks(const std::vector& trackIds); + void playTracks(const std::vector& 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 getTrackList(); void clearTracks(); - void enqueueTracks(const std::vector& tracks); - void enqueueTrack(Database::Track::pointer track); + void enqueueTracks(const std::vector& 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 similarityFinder); void addRadioTrackFromClusters(); - bool _repeatAll = false; - bool _radioMode = false; - boost::optional _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 _trackPos; // current track position, if set }; diff --git a/src/ui/SettingsView.cpp b/src/ui/SettingsView.cpp index 90a03e1a..b0a8e0c6 100644 --- a/src/ui/SettingsView.cpp +++ b/src/ui/SettingsView.cpp @@ -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>(); - 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()) { diff --git a/src/ui/admin/DatabaseSettingsView.cpp b/src/ui/admin/DatabaseSettingsView.cpp index 4794c714..441c6ead 100644 --- a/src/ui/admin/DatabaseSettingsView.cpp +++ b/src/ui/admin/DatabaseSettingsView.cpp @@ -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 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(clusterTypes.begin(), clusterTypes.end())); + scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set(clusterTypes.begin(), clusterTypes.end())); } private: diff --git a/src/ui/admin/InitWizardView.cpp b/src/ui/admin/InitWizardView.cpp index 9c7a6c3a..da35e79d 100644 --- a/src/ui/admin/InitWizardView.cpp +++ b/src/ui/admin/InitWizardView.cpp @@ -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()) diff --git a/src/ui/admin/UserView.cpp b/src/ui/admin/UserView.cpp index becfc877..65d4a997 100644 --- a/src/ui/admin/UserView.cpp +++ b/src/ui/admin/UserView.cpp @@ -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(wApp->internalPathNextPart("/admin/user/")); - LMS_LOG(UI, DEBUG) << "userId = " << (userId ? std::to_string(*userId) : "none"); - clear(); - Wt::WTemplateFormView* t = addNew(Wt::WString::tr("Lms.Admin.User.template")); + Wt::WTemplateFormView* t {addNew(Wt::WString::tr("Lms.Admin.User.template"))}; - auto model = std::make_shared(userId); + auto model {std::make_shared(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("last-login-attempt"); - lastLoginAttempt->setText(authUser.lastLoginAttempt().toString()); + Wt::WLineEdit *lastLoginAttempt {t->bindNew("last-login-attempt")}; + lastLoginAttempt->setText(LmsApp->getDbSession().getUserLastLoginAttempt(user).toString()); lastLoginAttempt->setEnabled(false); } else diff --git a/src/ui/admin/UsersView.cpp b/src/ui/admin/UsersView.cpp index b3e249d4..d276dc13 100644 --- a/src/ui/admin/UsersView.cpp +++ b/src/ui/admin/UsersView.cpp @@ -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::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::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(); diff --git a/src/ui/explore/ArtistInfoView.cpp b/src/ui/explore/ArtistInfoView.cpp index d253eacd..e7c446f8 100644 --- a/src/ui/explore/ArtistInfoView.cpp +++ b/src/ui/explore/ArtistInfoView.cpp @@ -63,21 +63,18 @@ ArtistInfo::refresh() if (!artistId) return; - auto artistsIds = getService()->getSimilarArtists(LmsApp->getDboSession(), *artistId, 5); + const std::vector artistsIds {getService()->getSimilarArtists(LmsApp->getDbSession(), *artistId, 5)}; - Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); + auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - std::vector 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(artist); + } } } // namespace UserInterface diff --git a/src/ui/explore/ArtistView.cpp b/src/ui/explore/ArtistView.cpp index 5dc0a609..489c5ca6 100644 --- a/src/ui/explore/ArtistView.cpp +++ b/src/ui/explore/ArtistView.cpp @@ -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::WString::tr("Lms.Explore.Artist.template")); + Wt::WTemplate* t {addNew(Wt::WString::tr("Lms.Explore.Artist.template"))}; t->addFunction("tr", &Wt::WTemplate::Functions::tr); Wt::WContainerWidget* clusterContainers = t->bindNew("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) diff --git a/src/ui/explore/ArtistsInfoView.cpp b/src/ui/explore/ArtistsInfoView.cpp index a7874501..fcf635a4 100644 --- a/src/ui/explore/ArtistsInfoView.cpp +++ b/src/ui/explore/ArtistsInfoView.cpp @@ -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 artists {Artist::getLastAdded(LmsApp->getDbSession(), after, 5)}; _recentlyAddedContainer->clear(); - for (auto artist : artists) + for (const Database::Artist::pointer& artist : artists) _recentlyAddedContainer->addNew(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 artists {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopArtists(5)}; _mostPlayedContainer->clear(); - for (auto artist : artists) + for (const Database::Artist::pointer& artist : artists) _mostPlayedContainer->addNew(artist); } diff --git a/src/ui/explore/ArtistsView.cpp b/src/ui/explore/ArtistsView.cpp index fadb2542..c207e551 100644 --- a/src/ui/explore/ArtistsView.cpp +++ b/src/ui/explore/ArtistsView.cpp @@ -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 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::WString::tr("Lms.Explore.Artists.template.entry")); + Wt::WTemplate* entry {_container->addNew(Wt::WString::tr("Lms.Explore.Artists.template.entry"))}; entry->bindWidget("name", LmsApplication::createArtistAnchor(artist)); } diff --git a/src/ui/explore/Explore.cpp b/src/ui/explore/Explore.cpp index ee37f418..c73b7515 100644 --- a/src/ui/explore/Explore.cpp +++ b/src/ui/explore/Explore.cpp @@ -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 getArtistTracks(Wt::Dbo::Session& session, Database::IdType artistId, std::set clusters) +static +std::vector +getArtistTracks(Database::Session& session, Database::IdType artistId, const std::set& clusters) { - std::vector 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 tracks {artist->getTracks()}; + std::vector 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 getReleaseTracks(Wt::Dbo::Session& session, Database::IdType releaseId, std::set clusters) +static +std::vector +getReleaseTracks(Database::Session& session, Database::IdType releaseId, std::set clusters) { - std::vector 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 tracks {release->getTracks(clusters)}; + std::vector 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 getTrack(Wt::Dbo::Session& session, Database::IdType trackId) +static +std::vector +getTrack(Database::Session& session, Database::IdType trackId) { - std::vector 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 tracks) +Explore::handleTracksAdd(const std::vector& trackIds) { - tracksAdd.emit(tracks); + tracksAdd.emit(trackIds); } void -Explore::handleTracksPlay(std::vector tracks) +Explore::handleTracksPlay(const std::vector& trackIds) { - tracksPlay.emit(tracks); + tracksPlay.emit(trackIds); } } // namespace UserInterface diff --git a/src/ui/explore/Explore.hpp b/src/ui/explore/Explore.hpp index ea538876..8f84de3d 100644 --- a/src/ui/explore/Explore.hpp +++ b/src/ui/explore/Explore.hpp @@ -21,7 +21,6 @@ #include -#include "database/Track.hpp" #include "database/Types.hpp" namespace UserInterface { @@ -33,19 +32,19 @@ class Explore : public Wt::WTemplate public: Explore(); - Wt::Signal> tracksAdd; - Wt::Signal> tracksPlay; + Wt::Signal> tracksAdd; + Wt::Signal> 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 tracks); - void handleTracksPlay(std::vector 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& trackIds); + void handleTracksPlay(const std::vector& trackIds); Filters* _filters; }; diff --git a/src/ui/explore/Filters.cpp b/src/ui/explore/Filters.cpp index 6d42d493..a3489737 100644 --- a/src/ui/explore/Filters.cpp +++ b/src/ui/explore/Filters.cpp @@ -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); diff --git a/src/ui/explore/ReleaseInfoView.cpp b/src/ui/explore/ReleaseInfoView.cpp index 11861075..17d55d51 100644 --- a/src/ui/explore/ReleaseInfoView.cpp +++ b/src/ui/explore/ReleaseInfoView.cpp @@ -68,11 +68,11 @@ ReleaseInfo::refresh() if (!releaseId) return; - std::vector releasesIds {getService()->getSimilarReleases(LmsApp->getDboSession(), *releaseId, 5)}; + const std::vector releasesIds {getService()->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 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(similarRelease); } diff --git a/src/ui/explore/ReleaseView.cpp b/src/ui/explore/ReleaseView.cpp index 8de243ba..7f8c6024 100644 --- a/src/ui/explore/ReleaseView.cpp +++ b/src/ui/explore/ReleaseView.cpp @@ -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("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))}; diff --git a/src/ui/explore/ReleasesInfoView.cpp b/src/ui/explore/ReleasesInfoView.cpp index 2c84277e..c3dcfdfb 100644 --- a/src/ui/explore/ReleasesInfoView.cpp +++ b/src/ui/explore/ReleasesInfoView.cpp @@ -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(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(release); } diff --git a/src/ui/explore/ReleasesView.cpp b/src/ui/explore/ReleasesView.cpp index 205748f5..8d68c620 100644 --- a/src/ui/explore/ReleasesView.cpp +++ b/src/ui/explore/ReleasesView.cpp @@ -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::WString::tr("Lms.Explore.Releases.template.entry")); entry->addFunction("tr", Wt::WTemplate::Functions::tr); diff --git a/src/ui/explore/TracksInfoView.cpp b/src/ui/explore/TracksInfoView.cpp index 509bf186..4f2a97a0 100644 --- a/src/ui/explore/TracksInfoView.cpp +++ b/src/ui/explore/TracksInfoView.cpp @@ -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); diff --git a/src/ui/explore/TracksView.cpp b/src/ui/explore/TracksView.cpp index 30347c0d..04ef4d6b 100644 --- a/src/ui/explore/TracksView.cpp +++ b/src/ui/explore/TracksView.cpp @@ -52,14 +52,12 @@ _filters(filters) Wt::WText* playBtn = bindNew("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("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 +std::vector Tracks::getTracks(boost::optional offset, boost::optional 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 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 +std::vector 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 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::WString::tr("Lms.Explore.Tracks.template.entry"))}; entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain); diff --git a/src/ui/explore/TracksView.hpp b/src/ui/explore/TracksView.hpp index c8dd0988..0efffec2 100644 --- a/src/ui/explore/TracksView.hpp +++ b/src/ui/explore/TracksView.hpp @@ -19,13 +19,14 @@ #pragma once +#include + #include #include #include #include #include "database/Types.hpp" -#include "database/Track.hpp" namespace UserInterface { @@ -38,15 +39,15 @@ class Tracks : public Wt::WTemplate Wt::Signal trackAdd; Wt::Signal trackPlay; - Wt::Signal> tracksAdd; - Wt::Signal> tracksPlay; + Wt::Signal> tracksAdd; + Wt::Signal> tracksPlay; private: void refresh(); void addSome(); - std::vector getTracks(boost::optional offset, boost::optional size, bool& moreResults); - std::vector getTracks(); + std::vector getTracks(boost::optional offset, boost::optional size, bool& moreResults); + std::vector getTracks(); Wt::WContainerWidget* _tracksContainer; Wt::WPushButton* _showMore; diff --git a/src/ui/resource/AudioResource.cpp b/src/ui/resource/AudioResource.cpp index 101e80d1..52b950a0 100644 --- a/src/ui/resource/AudioResource.cpp +++ b/src/ui/resource/AudioResource.cpp @@ -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"; diff --git a/src/ui/resource/AudioResource.hpp b/src/ui/resource/AudioResource.hpp index 0b8e4226..66cd07a5 100644 --- a/src/ui/resource/AudioResource.hpp +++ b/src/ui/resource/AudioResource.hpp @@ -23,7 +23,6 @@ #include "av/AvTranscoder.hpp" -#include "database/DatabaseHandler.hpp" #include "database/Types.hpp" diff --git a/src/ui/resource/ImageResource.cpp b/src/ui/resource/ImageResource.cpp index 3752179b..5f706fc6 100644 --- a/src/ui/resource/ImageResource.cpp +++ b/src/ui/resource/ImageResource.cpp @@ -65,7 +65,7 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons if (!sizeStr) return; - auto size = readAs(*sizeStr); + const auto size {readAs(*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(*trackIdStr); + const auto trackId {readAs(*trackIdStr)}; if (!trackId) return; - // transactions are not thread safe + // DbSession are not thread safe { - Wt::WApplication::UpdateLock lock(LmsApp); - cover = getService()->getFromTrack(LmsApp->getDboSession(), *trackId, Image::Format::JPEG, *size); + Wt::WApplication::UpdateLock lock {LmsApp}; + cover = getService()->getFromTrack(LmsApp->getDbSession(), *trackId, Image::Format::JPEG, *size); } } else if (releaseIdStr) { - auto releaseId = readAs(*releaseIdStr); + const auto releaseId {readAs(*releaseIdStr)}; if (!releaseId) return; - // transactions are not thread safe + // DbSession are not thread safe { - Wt::WApplication::UpdateLock lock(LmsApp); - cover = getService()->getFromRelease(LmsApp->getDboSession(), *releaseId, Image::Format::JPEG, *size); + Wt::WApplication::UpdateLock lock {LmsApp}; + cover = getService()->getFromRelease(LmsApp->getDbSession(), *releaseId, Image::Format::JPEG, *size); } } else diff --git a/src/ui/resource/ImageResource.hpp b/src/ui/resource/ImageResource.hpp index f9ded0fc..49b61559 100644 --- a/src/ui/resource/ImageResource.hpp +++ b/src/ui/resource/ImageResource.hpp @@ -24,7 +24,6 @@ #include -#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(); diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index 48a8e8a7..eab83c5f 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -64,9 +64,10 @@ readAs(const std::string& str) std::vector splitString(const std::string& string, const std::string& separators) { - std::vector res; + std::string str {stringTrim(string, separators)}; - boost::algorithm::split(res, string, boost::is_any_of(separators), boost::token_compress_on); + std::vector res; + boost::algorithm::split(res, str, boost::is_any_of(separators), boost::token_compress_on); return res; } diff --git a/src/utils/Utils.hpp b/src/utils/Utils.hpp index cc06157c..1cc5644c 100644 --- a/src/utils/Utils.hpp +++ b/src/utils/Utils.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -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 +void +shuffleContainer(Container& container) +{ + auto now {std::chrono::system_clock::now()}; + std::mt19937 randGenerator(std::chrono::duration_cast(now.time_since_epoch()).count()); + std::shuffle(std::begin(container), std::end(container), randGenerator); +} diff --git a/test/Makefile.am b/test/Makefile.am index ab7cd40b..ae832da1 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -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 \ diff --git a/test/database/DatabaseTest.cpp b/test/database/DatabaseTest.cpp index e1ab45c4..ed2945c7 100644 --- a/test/database/DatabaseTest.cpp +++ b/test/database/DatabaseTest.cpp @@ -23,7 +23,7 @@ #include "database/Artist.hpp" #include "database/Cluster.hpp" -#include "database/DatabaseHandler.hpp" +#include "database/Database.hpp" #include "database/TrackList.hpp" #include "database/Release.hpp" #include "database/Track.hpp" @@ -50,20 +50,33 @@ class ScopedFileDeleter final static void -testSingleTrack(Wt::Dbo::Session& session) +testRemoveDefaultEntries(Session& session) +{ + { + auto transaction {session.createUniqueTransaction()}; + + auto clusterTypes {ClusterType::getAll(session)}; + for (auto& clusterType : clusterTypes) + clusterType.remove(); + } +} + +static +void +testSingleTrack(Session& session) { IdType trackId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "MyTrackFile")}; CHECK(track); - session.flush(); + CHECK(IdIsValid(track.id())); trackId = track.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::getById(session, trackId)}; CHECK(track); @@ -75,20 +88,19 @@ testSingleTrack(Wt::Dbo::Session& session) static void -testSingleArtist(Wt::Dbo::Session& session) +testSingleArtist(Session& session) { IdType artistId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::create(session, "MyArtist")}; CHECK(artist); - session.flush(); artistId = artist.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Artist::getAll(session).size() == 1); @@ -98,7 +110,7 @@ testSingleArtist(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::getById(session, artistId)}; CHECK(artist); @@ -108,20 +120,19 @@ testSingleArtist(Wt::Dbo::Session& session) static void -testSingleRelease(Wt::Dbo::Session& session) +testSingleRelease(Session& session) { IdType releaseId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto release {Release::create(session, "MyRelease")}; CHECK(release); - session.flush(); releaseId = release.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto releases {Release::getAllOrphans(session)}; CHECK(releases.size() == 1); @@ -133,7 +144,7 @@ testSingleRelease(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto release {Release::getById(session, releaseId)}; CHECK(release); @@ -143,24 +154,23 @@ testSingleRelease(Wt::Dbo::Session& session) static void -testSingleCluster(Wt::Dbo::Session& session) +testSingleCluster(Session& session) { IdType clusterTypeId {}; IdType clusterId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto clusterType {ClusterType::create(session, "MyType")}; CHECK(clusterType); auto cluster {Cluster::create(session, clusterType, "MyCluster")}; CHECK(cluster); - session.flush(); clusterTypeId = clusterType.id(); clusterId = cluster.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto clusters {Cluster::getAll(session)}; CHECK(clusters.size() == 1); @@ -180,7 +190,7 @@ testSingleCluster(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto cluster {Cluster::getById(session, clusterId)}; CHECK(cluster); @@ -191,7 +201,7 @@ testSingleCluster(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto clusterType {ClusterType::getById(session, clusterTypeId)}; CHECK(clusterType); @@ -202,12 +212,12 @@ testSingleCluster(Wt::Dbo::Session& session) static void -testSingleTrackSingleArtist(Wt::Dbo::Session& session) +testSingleTrackSingleArtist(Session& session) { IdType trackId {}; IdType artistId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "dummy")}; CHECK(track); @@ -216,18 +226,17 @@ testSingleTrackSingleArtist(Wt::Dbo::Session& session) auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; - session.flush(); trackId = track.id(); artistId = artist.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Artist::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto track {Track::getById(session, trackId)}; CHECK(track); @@ -249,7 +258,7 @@ testSingleTrackSingleArtist(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::getById(session, artistId)}; CHECK(artist); @@ -268,12 +277,12 @@ testSingleTrackSingleArtist(Wt::Dbo::Session& session) static void -testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session) +testSingleTrackSingleArtistMultiRoles(Session& session) { IdType trackId {}; IdType artistId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "MyTrack")}; auto artist {Artist::create(session, "MyArtist")}; @@ -282,18 +291,17 @@ testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session) TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::ReleaseArtist); TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Writer); - session.flush(); trackId = track.id(); artistId = artist.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Artist::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto track {Track::getById(session, trackId)}; CHECK(track); @@ -317,7 +325,7 @@ testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::getById(session, artistId)}; auto track {Track::getById(session, trackId)}; @@ -329,13 +337,13 @@ testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session) static void -testSingleTrackMultiArtists(Wt::Dbo::Session& session) +testSingleTrackMultiArtists(Session& session) { IdType trackId {}; IdType artist1Id {}; IdType artist2Id {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "dummy")}; auto artist1 {Artist::create(session, "artist1")}; @@ -344,7 +352,6 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session) TrackArtistLink::create(session, track, artist1, TrackArtistLink::Type::Artist); TrackArtistLink::create(session, track, artist2, TrackArtistLink::Type::Artist); - session.flush(); trackId = track.id(); artist1Id = artist1.id(); artist2Id = artist2.id(); @@ -353,12 +360,12 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Artist::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto track {Track::getById(session, trackId)}; CHECK(track); @@ -374,7 +381,7 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist1 {Artist::getById(session, artist1Id)}; CHECK(artist1); @@ -386,7 +393,6 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session) CHECK(artist2); CHECK(artist2->getTracks().front() == track); - CHECK(artist1->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist1->getTracks(TrackArtistLink::Type::Artist).size() == 1); CHECK(artist2->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); @@ -400,12 +406,12 @@ testSingleTrackMultiArtists(Wt::Dbo::Session& session) static void -testSingleTrackSingleRelease(Wt::Dbo::Session& session) +testSingleTrackSingleRelease(Session& session) { IdType trackId {}; IdType releaseId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "dummy")}; CHECK(track); @@ -414,13 +420,12 @@ testSingleTrackSingleRelease(Wt::Dbo::Session& session) track.modify()->setRelease(release); - session.flush(); trackId = track.id(); releaseId = release.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Release::getAllOrphans(session).empty()); auto release {Release::getById(session, releaseId)}; @@ -431,7 +436,7 @@ testSingleTrackSingleRelease(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::getById(session, trackId)}; CHECK(track); @@ -441,7 +446,7 @@ testSingleTrackSingleRelease(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; CHECK(Release::getAllOrphans(session).size() == 1); auto release {Release::getById(session, releaseId)}; @@ -454,13 +459,13 @@ testSingleTrackSingleRelease(Wt::Dbo::Session& session) static void -testSingleTrackSingleCluster(Wt::Dbo::Session& session) +testSingleTrackSingleCluster(Session& session) { IdType trackId {}; IdType clusterId {}; IdType clusterTypeId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "dummy")}; auto clusterType {ClusterType::create(session, "MyType")}; @@ -468,19 +473,18 @@ testSingleTrackSingleCluster(Wt::Dbo::Session& session) cluster.modify()->addTrack(track); - session.flush(); trackId = track.id(); clusterTypeId = clusterType.id(); clusterId = cluster.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Cluster::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto tracks {Track::getByFilter(session, {clusterId})}; CHECK(tracks.size() == 1); @@ -492,7 +496,7 @@ testSingleTrackSingleCluster(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::getById(session, trackId)}; auto cluster {Cluster::getById(session, clusterId)}; @@ -506,14 +510,14 @@ testSingleTrackSingleCluster(Wt::Dbo::Session& session) static void -testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session) +testSingleTrackSingleReleaseSingleCluster(Session& session) { IdType trackId {}; IdType releaseId {}; IdType clusterId {}; IdType clusterTypeId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "MyTrackFile")}; auto release {Release::create(session, "MyRelease")}; @@ -523,7 +527,6 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session) track.modify()->setRelease(release); cluster.modify()->addTrack(track); - session.flush(); trackId = track.id(); releaseId = release.id(); clusterTypeId = clusterType.id(); @@ -531,13 +534,13 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto releases {Release::getByFilter(session, {clusterId})}; CHECK(releases.size() == 1); @@ -549,7 +552,7 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto cluster {Cluster::getById(session, clusterId)}; CHECK(cluster->getReleasesCount() == 1); @@ -557,7 +560,7 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::getById(session, trackId)}; auto release {Release::getById(session, releaseId)}; @@ -573,7 +576,7 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session) static void -testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session) +testSingleTrackSingleArtistMultiClusters(Session& session) { IdType trackId {}; IdType artistId {}; @@ -581,7 +584,7 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session) IdType cluster2Id {}; IdType clusterTypeId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "MyTrackFile")}; auto artist {Artist::create(session, "MyArtist")}; @@ -592,7 +595,6 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session) auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; cluster1.modify()->addTrack(track); - session.flush(); trackId = track.id(); artistId = artist.id(); clusterTypeId = clusterType.id(); @@ -601,14 +603,14 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto artists {Artist::getByFilter(session, {cluster1Id})}; CHECK(artists.size() == 1); @@ -627,7 +629,7 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto artists {Artist::getByFilter(session, {cluster1Id})}; CHECK(artists.size() == 1); @@ -643,7 +645,7 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::getById(session, trackId)}; auto artist {Artist::getById(session, artistId)}; @@ -661,14 +663,14 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session) static void -testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session) +testSingleTrackSingleArtistMultiRolesMultiClusters(Session& session) { IdType trackId {}; IdType artistId {}; IdType clusterId {}; IdType clusterTypeId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "MyTrackFile")}; auto artist {Artist::create(session, "MyArtist")}; @@ -679,7 +681,6 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session) TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::ReleaseArtist); cluster.modify()->addTrack(track); - session.flush(); trackId = track.id(); artistId = artist.id(); clusterTypeId = clusterType.id(); @@ -687,14 +688,14 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto artists {Artist::getByFilter(session, {clusterId})}; CHECK(artists.size() == 1); @@ -702,7 +703,7 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::getById(session, trackId)}; auto artist {Artist::getById(session, artistId)}; @@ -718,14 +719,14 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session) static void -testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session) +testMultiTracksSingleArtistMultiClusters(Session& session) { const std::size_t nbTracks {10}; const std::size_t nbClusters {5}; IdType artistId {}; IdType clusterTypeId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::create(session, "MyArtist")}; @@ -744,19 +745,18 @@ testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session) cluster.modify()->addTrack(track); } - session.flush(); artistId = artist.id(); clusterTypeId = clusterType.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; std::vector clusters {Cluster::getAll(session)}; CHECK(clusters.size() == nbClusters); @@ -770,7 +770,7 @@ testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; std::vector clusters {Cluster::getAll(session)}; for (auto& cluster : clusters) @@ -790,13 +790,13 @@ testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session) static void -testMultiTracksSingleArtistSingleRelease(Wt::Dbo::Session& session) +testMultiTracksSingleArtistSingleRelease(Session& session) { const std::size_t nbTracks {10}; IdType artistId {}; IdType releaseId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::create(session, "MyArtist")}; auto release {Release::create(session, "MyRelease")}; @@ -808,19 +808,18 @@ testMultiTracksSingleArtistSingleRelease(Wt::Dbo::Session& session) track.modify()->setRelease(release); } - session.flush(); artistId = artist.id(); releaseId = release.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; CHECK(Release::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto artist {Artist::getById(session, artistId)}; CHECK(artist); @@ -834,7 +833,7 @@ testMultiTracksSingleArtistSingleRelease(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; std::vector tracks {Track::getAll(session)}; for (auto& track : tracks) @@ -849,13 +848,13 @@ testMultiTracksSingleArtistSingleRelease(Wt::Dbo::Session& session) static void -testSingleTrackSingleReleaseSingleArtist(Wt::Dbo::Session& session) +testSingleTrackSingleReleaseSingleArtist(Session& session) { IdType trackId {}; IdType releaseId {}; IdType artistId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "dummy")}; auto release {Release::create(session, "dummy")}; @@ -864,14 +863,13 @@ testSingleTrackSingleReleaseSingleArtist(Wt::Dbo::Session& session) auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; track.modify()->setRelease(release); - session.flush(); trackId = track.id(); releaseId = release.id(); artistId = artist.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::getById(session, artistId)}; CHECK(artist); @@ -897,7 +895,7 @@ testSingleTrackSingleReleaseSingleArtist(Wt::Dbo::Session& session) static void -testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session) +testSingleTrackSingleReleaseSingleArtistSingleCluster(Session& session) { IdType trackId {}; IdType releaseId {}; @@ -906,7 +904,7 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session) IdType clusterTypeId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "MyTrackFile")}; auto release {Release::create(session, "MyRelease")}; @@ -919,7 +917,6 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session) track.modify()->setRelease(release); cluster.modify()->addTrack(track); - session.flush(); trackId = track.id(); releaseId = release.id(); artistId = artist.id(); @@ -928,14 +925,15 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; + CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty()); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto artists {Artist::getByFilter(session, {clusterId})}; CHECK(artists.size() == 1); @@ -952,7 +950,7 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::getById(session, artistId)}; auto release {Release::getById(session, releaseId)}; @@ -970,7 +968,7 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session) static void -testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session) +testSingleTrackSingleReleaseSingleArtistMultiClusters(Session& session) { IdType trackId {}; IdType releaseId {}; @@ -980,7 +978,7 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session) IdType clusterTypeId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "MyTrackFile")}; auto release {Release::create(session, "MyRelease")}; @@ -995,7 +993,6 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session) cluster1.modify()->addTrack(track); cluster2.modify()->addTrack(track); - session.flush(); trackId = track.id(); releaseId = release.id(); artistId = artist.id(); @@ -1005,7 +1002,7 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto artist {Artist::getById(session, artistId)}; auto releases {artist->getReleases()}; @@ -1018,7 +1015,7 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::getById(session, artistId)}; auto release {Release::getById(session, releaseId)}; @@ -1038,25 +1035,59 @@ testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session) static void -testSingleStarredArtist(Wt::Dbo::Session& session) +testSingleUser(Session& session) +{ + IdType userId {}; + { + auto transaction {session.createUniqueTransaction()}; + + auto user {User::create(session)}; + CHECK(user); + + userId = user.id(); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto user {User::getById(session, userId)}; + CHECK(user); + CHECK(user->getPlayedTrackList(session)->getCount() == 0); + CHECK(user->getPlayedTrackList(session)->getTopTracks(1).empty()); + CHECK(user->getPlayedTrackList(session)->getTopArtists(1).empty()); + CHECK(user->getPlayedTrackList(session)->getTopReleases(1).empty()); + CHECK(user->getQueuedTrackList(session)->getCount() == 0); + } + + { + auto transaction {session.createUniqueTransaction()}; + + auto user {User::getById(session, userId)}; + CHECK(user); + user.remove(); + } +} + +static +void +testSingleStarredArtist(Session& session) { IdType artistId {}; IdType userId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto artist {Artist::create(session, "MyArtist")}; CHECK(artist); auto user {User::create(session)}; CHECK(user); - session.flush(); artistId = artist.id(); userId = user.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1067,7 +1098,7 @@ testSingleStarredArtist(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1080,7 +1111,7 @@ testSingleStarredArtist(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1094,25 +1125,24 @@ testSingleStarredArtist(Wt::Dbo::Session& session) static void -testSingleStarredRelease(Wt::Dbo::Session& session) +testSingleStarredRelease(Session& session) { IdType releaseId {}; IdType userId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto release {Release::create(session, "MyRelease")}; CHECK(release); auto user {User::create(session)}; CHECK(user); - session.flush(); releaseId = release.id(); userId = user.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1123,7 +1153,7 @@ testSingleStarredRelease(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1136,7 +1166,7 @@ testSingleStarredRelease(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1150,25 +1180,24 @@ testSingleStarredRelease(Wt::Dbo::Session& session) static void -testSingleStarredTrack(Wt::Dbo::Session& session) +testSingleStarredTrack(Session& session) { IdType trackId {}; IdType userId {}; { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto track {Track::create(session, "MyTrackFile")}; CHECK(track); auto user {User::create(session)}; CHECK(user); - session.flush(); trackId = track.id(); userId = user.id(); } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1179,7 +1208,7 @@ testSingleStarredTrack(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createSharedTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1192,7 +1221,7 @@ testSingleStarredTrack(Wt::Dbo::Session& session) } { - Wt::Dbo::Transaction transaction {session}; + auto transaction {session.createUniqueTransaction()}; auto user {User::getById(session, userId)}; CHECK(user); @@ -1206,9 +1235,9 @@ testSingleStarredTrack(Wt::Dbo::Session& session) static void -testDatabaseEmpty(Wt::Dbo::Session& session) +testDatabaseEmpty(Session& session) { - Wt::Dbo::Transaction transaction {session}; + auto uniqueTransaction {session.createUniqueTransaction()}; CHECK(Artist::getAll(session).empty()); CHECK(Cluster::getAll(session).empty()); @@ -1222,53 +1251,60 @@ int main(int argc, char* argv[]) try { - boost::filesystem::path tmpFile {boost::filesystem::temp_directory_path() / boost::filesystem::unique_path()}; + const boost::filesystem::path tmpFile {boost::filesystem::temp_directory_path() / boost::filesystem::unique_path()}; ScopedFileDeleter tmpFileDeleter {tmpFile}; std::cout << "Database test file: '" << tmpFile.string() << "'" << std::endl; - std::unique_ptr connectionPool{ Handler::createConnectionPool(tmpFile) }; - - Handler db {*connectionPool}; - Wt::Dbo::Session& session {db.getSession()}; - - auto runTest = [&session](const std::string& name, std::function testFunc) + for (std::size_t i = 0; i < 2; ++i) { - std::cout << "Running test '" << name << "'..." << std::endl; - testFunc(session); - testDatabaseEmpty(session); - std::cout << "Running test '" << name << "': SUCCESS" << std::endl; - }; + Database::Database db {tmpFile}; + std::unique_ptr session {db.createSession()}; + + auto runTest = [&session](const std::string& name, std::function testFunc) + { + std::cout << "Running test '" << name << "'..." << std::endl; + testFunc(*session); + testDatabaseEmpty(*session); + std::cout << "Running test '" << name << "': SUCCESS" << std::endl; + }; #define RUN_TEST(test) runTest(#test, test) - RUN_TEST(testSingleTrack); - RUN_TEST(testSingleArtist); - RUN_TEST(testSingleRelease); - RUN_TEST(testSingleCluster); + // Special test to remove any default created entries + RUN_TEST(testRemoveDefaultEntries); - RUN_TEST(testSingleTrackSingleArtist); - RUN_TEST(testSingleTrackSingleArtistMultiRoles); - RUN_TEST(testSingleTrackMultiArtists); + RUN_TEST(testSingleTrack); + RUN_TEST(testSingleArtist); + RUN_TEST(testSingleRelease); + RUN_TEST(testSingleCluster); - RUN_TEST(testSingleTrackSingleRelease); + RUN_TEST(testSingleTrackSingleArtist); + RUN_TEST(testSingleTrackSingleArtistMultiRoles); + RUN_TEST(testSingleTrackMultiArtists); - RUN_TEST(testSingleTrackSingleCluster); + RUN_TEST(testSingleTrackSingleRelease); - RUN_TEST(testSingleTrackSingleReleaseSingleCluster); - RUN_TEST(testSingleTrackSingleArtistMultiClusters); - RUN_TEST(testSingleTrackSingleArtistMultiRolesMultiClusters); - RUN_TEST(testMultiTracksSingleArtistMultiClusters); - RUN_TEST(testMultiTracksSingleArtistSingleRelease); + RUN_TEST(testSingleTrackSingleCluster); - RUN_TEST(testSingleTrackSingleReleaseSingleArtist); + RUN_TEST(testSingleTrackSingleReleaseSingleCluster); + RUN_TEST(testSingleTrackSingleArtistMultiClusters); + RUN_TEST(testSingleTrackSingleArtistMultiRolesMultiClusters); + RUN_TEST(testMultiTracksSingleArtistMultiClusters); + RUN_TEST(testMultiTracksSingleArtistSingleRelease); - RUN_TEST(testSingleTrackSingleReleaseSingleArtistSingleCluster); - RUN_TEST(testSingleTrackSingleReleaseSingleArtistMultiClusters); + RUN_TEST(testSingleTrackSingleReleaseSingleArtist); - RUN_TEST(testSingleStarredArtist); - RUN_TEST(testSingleStarredRelease); - RUN_TEST(testSingleStarredTrack); + RUN_TEST(testSingleTrackSingleReleaseSingleArtistSingleCluster); + RUN_TEST(testSingleTrackSingleReleaseSingleArtistMultiClusters); + + RUN_TEST(testSingleUser); + + RUN_TEST(testSingleStarredArtist); + RUN_TEST(testSingleStarredRelease); + RUN_TEST(testSingleStarredTrack); + + } } catch (std::exception& e) {